summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Core')
-rw-r--r--Xamarin.Forms.Core/AbsoluteLayout.cs313
-rw-r--r--Xamarin.Forms.Core/AbsoluteLayoutFlags.cs17
-rw-r--r--Xamarin.Forms.Core/ActionSheetArguments.cs45
-rw-r--r--Xamarin.Forms.Core/ActivityIndicator.cs24
-rw-r--r--Xamarin.Forms.Core/AlertArguments.cs43
-rw-r--r--Xamarin.Forms.Core/AnimatableKey.cs82
-rw-r--r--Xamarin.Forms.Core/Animation.cs138
-rw-r--r--Xamarin.Forms.Core/AnimationExtensions.cs259
-rw-r--r--Xamarin.Forms.Core/Application.cs308
-rw-r--r--Xamarin.Forms.Core/Aspect.cs9
-rw-r--r--Xamarin.Forms.Core/BackButtonPressedEventArgs.cs9
-rw-r--r--Xamarin.Forms.Core/BaseMenuItem.cs6
-rw-r--r--Xamarin.Forms.Core/BindableObject.cs647
-rw-r--r--Xamarin.Forms.Core/BindableObjectExtensions.cs34
-rw-r--r--Xamarin.Forms.Core/BindableProperty.cs331
-rw-r--r--Xamarin.Forms.Core/BindablePropertyConverter.cs105
-rw-r--r--Xamarin.Forms.Core/BindablePropertyKey.cs17
-rw-r--r--Xamarin.Forms.Core/Binding.cs233
-rw-r--r--Xamarin.Forms.Core/BindingBase.cs111
-rw-r--r--Xamarin.Forms.Core/BindingBaseExtensions.cs17
-rw-r--r--Xamarin.Forms.Core/BindingExpression.cs506
-rw-r--r--Xamarin.Forms.Core/BindingMode.cs10
-rw-r--r--Xamarin.Forms.Core/BindingTypeConverter.cs10
-rw-r--r--Xamarin.Forms.Core/BoundsConstraint.cs34
-rw-r--r--Xamarin.Forms.Core/BoundsTypeConverter.cs42
-rw-r--r--Xamarin.Forms.Core/BoxView.cs23
-rw-r--r--Xamarin.Forms.Core/Button.cs251
-rw-r--r--Xamarin.Forms.Core/CarouselPage.cs17
-rw-r--r--Xamarin.Forms.Core/CarouselView.cs86
-rw-r--r--Xamarin.Forms.Core/CastingEnumerator.cs46
-rw-r--r--Xamarin.Forms.Core/Cells/Cell.cs209
-rw-r--r--Xamarin.Forms.Core/Cells/EntryCell.cs80
-rw-r--r--Xamarin.Forms.Core/Cells/INativeElementView.cs7
-rw-r--r--Xamarin.Forms.Core/Cells/ImageCell.cs56
-rw-r--r--Xamarin.Forms.Core/Cells/SwitchCell.cs31
-rw-r--r--Xamarin.Forms.Core/Cells/TextCell.cs94
-rw-r--r--Xamarin.Forms.Core/Cells/ViewCell.cs48
-rw-r--r--Xamarin.Forms.Core/ChatKeyboard.cs6
-rw-r--r--Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs15
-rw-r--r--Xamarin.Forms.Core/CollectionSynchronizationCallback.cs7
-rw-r--r--Xamarin.Forms.Core/CollectionSynchronizationContext.cs22
-rw-r--r--Xamarin.Forms.Core/Color.cs375
-rw-r--r--Xamarin.Forms.Core/ColorTypeConverter.cs72
-rw-r--r--Xamarin.Forms.Core/ColumnDefinition.cs34
-rw-r--r--Xamarin.Forms.Core/ColumnDefinitionCollection.cs6
-rw-r--r--Xamarin.Forms.Core/Command.cs80
-rw-r--r--Xamarin.Forms.Core/ConcurrentDictionary.cs426
-rw-r--r--Xamarin.Forms.Core/Constraint.cs56
-rw-r--r--Xamarin.Forms.Core/ConstraintExpression.cs53
-rw-r--r--Xamarin.Forms.Core/ConstraintType.cs9
-rw-r--r--Xamarin.Forms.Core/ConstraintTypeConverter.cs17
-rw-r--r--Xamarin.Forms.Core/ContentPage.cs26
-rw-r--r--Xamarin.Forms.Core/ContentPresenter.cs91
-rw-r--r--Xamarin.Forms.Core/ContentPropertyAttribute.cs26
-rw-r--r--Xamarin.Forms.Core/ContentView.cs26
-rw-r--r--Xamarin.Forms.Core/ControlTemplate.cs15
-rw-r--r--Xamarin.Forms.Core/CustomKeyboard.cs12
-rw-r--r--Xamarin.Forms.Core/DataTemplate.cs80
-rw-r--r--Xamarin.Forms.Core/DataTemplateExtensions.cs15
-rw-r--r--Xamarin.Forms.Core/DataTemplateSelector.cs17
-rw-r--r--Xamarin.Forms.Core/DateChangedEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/DatePicker.cs99
-rw-r--r--Xamarin.Forms.Core/DefinitionCollection.cs109
-rw-r--r--Xamarin.Forms.Core/DelegateLogListener.cs22
-rw-r--r--Xamarin.Forms.Core/DependencyAttribute.cs15
-rw-r--r--Xamarin.Forms.Core/DependencyFetchTarget.cs8
-rw-r--r--Xamarin.Forms.Core/DependencyService.cs102
-rw-r--r--Xamarin.Forms.Core/Device.cs159
-rw-r--r--Xamarin.Forms.Core/DeviceInfo.cs51
-rw-r--r--Xamarin.Forms.Core/DeviceOrientation.cs13
-rw-r--r--Xamarin.Forms.Core/DeviceOrientationExtensions.cs15
-rw-r--r--Xamarin.Forms.Core/Easing.cs98
-rw-r--r--Xamarin.Forms.Core/Editor.cs67
-rw-r--r--Xamarin.Forms.Core/Effect.cs70
-rw-r--r--Xamarin.Forms.Core/Element.cs584
-rw-r--r--Xamarin.Forms.Core/ElementCollection.cs11
-rw-r--r--Xamarin.Forms.Core/ElementEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/ElementTemplate.cs96
-rw-r--r--Xamarin.Forms.Core/EmailKeyboard.cs6
-rw-r--r--Xamarin.Forms.Core/Entry.cs99
-rw-r--r--Xamarin.Forms.Core/EnumerableExtensions.cs81
-rw-r--r--Xamarin.Forms.Core/EventArg.cs18
-rw-r--r--Xamarin.Forms.Core/ExportEffectAttribute.cs20
-rw-r--r--Xamarin.Forms.Core/ExpressionSearch.cs7
-rw-r--r--Xamarin.Forms.Core/FileAccess.cs9
-rw-r--r--Xamarin.Forms.Core/FileImageSource.cs38
-rw-r--r--Xamarin.Forms.Core/FileImageSourceConverter.cs15
-rw-r--r--Xamarin.Forms.Core/FileMode.cs12
-rw-r--r--Xamarin.Forms.Core/FileShare.cs15
-rw-r--r--Xamarin.Forms.Core/FocusEventArgs.cs20
-rw-r--r--Xamarin.Forms.Core/Font.cs145
-rw-r--r--Xamarin.Forms.Core/FontAttributes.cs12
-rw-r--r--Xamarin.Forms.Core/FontSizeConverter.cs48
-rw-r--r--Xamarin.Forms.Core/FontTypeConverter.cs88
-rw-r--r--Xamarin.Forms.Core/FormattedString.cs99
-rw-r--r--Xamarin.Forms.Core/Frame.cs30
-rw-r--r--Xamarin.Forms.Core/GestureRecognizer.cs9
-rw-r--r--Xamarin.Forms.Core/GestureState.cs12
-rw-r--r--Xamarin.Forms.Core/GestureStatus.cs10
-rw-r--r--Xamarin.Forms.Core/Grid.cs359
-rw-r--r--Xamarin.Forms.Core/GridCalc.cs698
-rw-r--r--Xamarin.Forms.Core/GridLength.cs74
-rw-r--r--Xamarin.Forms.Core/GridLengthTypeConverter.cs27
-rw-r--r--Xamarin.Forms.Core/GridUnitType.cs9
-rw-r--r--Xamarin.Forms.Core/HandlerAttribute.cs23
-rw-r--r--Xamarin.Forms.Core/HtmlWebViewSource.cs28
-rw-r--r--Xamarin.Forms.Core/IAnimatable.cs34
-rw-r--r--Xamarin.Forms.Core/IApplicationController.cs6
-rw-r--r--Xamarin.Forms.Core/IButtonController.cs7
-rw-r--r--Xamarin.Forms.Core/ICarouselViewController.cs10
-rw-r--r--Xamarin.Forms.Core/IControlTemplated.cs11
-rw-r--r--Xamarin.Forms.Core/IDefinition.cs9
-rw-r--r--Xamarin.Forms.Core/IDeserializer.cs11
-rw-r--r--Xamarin.Forms.Core/IEffectControlProvider.cs7
-rw-r--r--Xamarin.Forms.Core/IElement.cs13
-rw-r--r--Xamarin.Forms.Core/IElementController.cs10
-rw-r--r--Xamarin.Forms.Core/IExpressionSearch.cs10
-rw-r--r--Xamarin.Forms.Core/IExtendedTypeConverter.cs13
-rw-r--r--Xamarin.Forms.Core/IFontElement.cs11
-rw-r--r--Xamarin.Forms.Core/IGestureRecognizer.cs8
-rw-r--r--Xamarin.Forms.Core/IIsolatedStorageFile.cs18
-rw-r--r--Xamarin.Forms.Core/IItemViewController.cs10
-rw-r--r--Xamarin.Forms.Core/IItemsView.cs9
-rw-r--r--Xamarin.Forms.Core/ILayout.cs9
-rw-r--r--Xamarin.Forms.Core/ILayoutController.cs9
-rw-r--r--Xamarin.Forms.Core/IListViewController.cs15
-rw-r--r--Xamarin.Forms.Core/IMarkupExtension.cs14
-rw-r--r--Xamarin.Forms.Core/INavigation.cs28
-rw-r--r--Xamarin.Forms.Core/IOpenGlViewController.cs9
-rw-r--r--Xamarin.Forms.Core/IPageContainer.cs7
-rw-r--r--Xamarin.Forms.Core/IPanGestureController.cs13
-rw-r--r--Xamarin.Forms.Core/IPinchGestureController.cs15
-rw-r--r--Xamarin.Forms.Core/IPlatform.cs7
-rw-r--r--Xamarin.Forms.Core/IPlatformServices.cs36
-rw-r--r--Xamarin.Forms.Core/IProvideParentValues.cs9
-rw-r--r--Xamarin.Forms.Core/IProvideValueTarget.cs9
-rw-r--r--Xamarin.Forms.Core/IRegisterable.cs6
-rw-r--r--Xamarin.Forms.Core/IResourceDictionary.cs12
-rw-r--r--Xamarin.Forms.Core/IResourcesProvider.cs7
-rw-r--r--Xamarin.Forms.Core/IRootObjectProvider.cs7
-rw-r--r--Xamarin.Forms.Core/IScrollViewController.cs15
-rw-r--r--Xamarin.Forms.Core/IStyle.cs12
-rw-r--r--Xamarin.Forms.Core/ISystemResourcesProvider.cs7
-rw-r--r--Xamarin.Forms.Core/ITimer.cs13
-rw-r--r--Xamarin.Forms.Core/IValueConverter.cs11
-rw-r--r--Xamarin.Forms.Core/IValueConverterProvider.cs10
-rw-r--r--Xamarin.Forms.Core/IValueProvider.cs9
-rw-r--r--Xamarin.Forms.Core/IViewContainer.cs9
-rw-r--r--Xamarin.Forms.Core/IViewController.cs6
-rw-r--r--Xamarin.Forms.Core/IVisualElementController.cs7
-rw-r--r--Xamarin.Forms.Core/IWebViewRenderer.cs8
-rw-r--r--Xamarin.Forms.Core/IXamlTypeResolver.cs10
-rw-r--r--Xamarin.Forms.Core/IXmlLineInfoProvider.cs9
-rw-r--r--Xamarin.Forms.Core/Image.cs145
-rw-r--r--Xamarin.Forms.Core/ImageSource.cs142
-rw-r--r--Xamarin.Forms.Core/ImageSourceConverter.cs18
-rw-r--r--Xamarin.Forms.Core/InputView.cs18
-rw-r--r--Xamarin.Forms.Core/Interactivity/AttachedCollection.cs127
-rw-r--r--Xamarin.Forms.Core/Interactivity/Behavior.cs65
-rw-r--r--Xamarin.Forms.Core/Interactivity/BindingCondition.cs100
-rw-r--r--Xamarin.Forms.Core/Interactivity/Condition.cs51
-rw-r--r--Xamarin.Forms.Core/Interactivity/DataTrigger.cs57
-rw-r--r--Xamarin.Forms.Core/Interactivity/EventTrigger.cs91
-rw-r--r--Xamarin.Forms.Core/Interactivity/IAttachedObject.cs8
-rw-r--r--Xamarin.Forms.Core/Interactivity/MultiCondition.cs66
-rw-r--r--Xamarin.Forms.Core/Interactivity/MultiTrigger.cs23
-rw-r--r--Xamarin.Forms.Core/Interactivity/PropertyCondition.cs100
-rw-r--r--Xamarin.Forms.Core/Interactivity/Trigger.cs60
-rw-r--r--Xamarin.Forms.Core/Interactivity/TriggerAction.cs37
-rw-r--r--Xamarin.Forms.Core/Interactivity/TriggerBase.cs212
-rw-r--r--Xamarin.Forms.Core/Internals/DynamicResource.cs12
-rw-r--r--Xamarin.Forms.Core/Internals/IDataTemplate.cs10
-rw-r--r--Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs7
-rw-r--r--Xamarin.Forms.Core/Internals/INameScope.cs12
-rw-r--r--Xamarin.Forms.Core/Internals/NameScope.cs56
-rw-r--r--Xamarin.Forms.Core/InvalidNavigationException.cs11
-rw-r--r--Xamarin.Forms.Core/InvalidationEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/InvalidationTrigger.cs16
-rw-r--r--Xamarin.Forms.Core/ItemTappedEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/ItemVisibilityEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/ItemsView.cs92
-rw-r--r--Xamarin.Forms.Core/ItemsViewSimple.cs335
-rw-r--r--Xamarin.Forms.Core/Keyboard.cs64
-rw-r--r--Xamarin.Forms.Core/KeyboardFlags.cs13
-rw-r--r--Xamarin.Forms.Core/KeyboardTypeConverter.cs29
-rw-r--r--Xamarin.Forms.Core/Label.cs270
-rw-r--r--Xamarin.Forms.Core/Layout.cs433
-rw-r--r--Xamarin.Forms.Core/LayoutAlignment.cs13
-rw-r--r--Xamarin.Forms.Core/LayoutAlignmentExtensions.cs21
-rw-r--r--Xamarin.Forms.Core/LayoutConstraint.cs13
-rw-r--r--Xamarin.Forms.Core/LayoutExpandFlag.cs10
-rw-r--r--Xamarin.Forms.Core/LayoutOptions.cs39
-rw-r--r--Xamarin.Forms.Core/LayoutOptionsConverter.cs25
-rw-r--r--Xamarin.Forms.Core/LineBreakMode.cs12
-rw-r--r--Xamarin.Forms.Core/ListProxy.cs488
-rw-r--r--Xamarin.Forms.Core/ListView.cs540
-rw-r--r--Xamarin.Forms.Core/ListViewCachingStrategy.cs8
-rw-r--r--Xamarin.Forms.Core/LockingSemaphore.cs51
-rw-r--r--Xamarin.Forms.Core/Log.cs25
-rw-r--r--Xamarin.Forms.Core/LogListener.cs7
-rw-r--r--Xamarin.Forms.Core/MasterBehavior.cs11
-rw-r--r--Xamarin.Forms.Core/MasterDetailPage.cs229
-rw-r--r--Xamarin.Forms.Core/MeasureFlags.cs11
-rw-r--r--Xamarin.Forms.Core/MenuItem.cs117
-rw-r--r--Xamarin.Forms.Core/MergedStyle.cs162
-rw-r--r--Xamarin.Forms.Core/MessagingCenter.cs131
-rw-r--r--Xamarin.Forms.Core/ModalEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/ModalPoppedEventArgs.cs9
-rw-r--r--Xamarin.Forms.Core/ModalPoppingEventArgs.cs11
-rw-r--r--Xamarin.Forms.Core/ModalPushedEventArgs.cs9
-rw-r--r--Xamarin.Forms.Core/ModalPushingEventArgs.cs9
-rw-r--r--Xamarin.Forms.Core/MultiPage.cs359
-rw-r--r--Xamarin.Forms.Core/NameScopeExtensions.cs17
-rw-r--r--Xamarin.Forms.Core/NamedSize.cs11
-rw-r--r--Xamarin.Forms.Core/NavigationEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/NavigationMenu.cs71
-rw-r--r--Xamarin.Forms.Core/NavigationModel.cs177
-rw-r--r--Xamarin.Forms.Core/NavigationPage.cs411
-rw-r--r--Xamarin.Forms.Core/NavigationProxy.cs233
-rw-r--r--Xamarin.Forms.Core/NavigationRequestedEventArgs.cs26
-rw-r--r--Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs65
-rw-r--r--Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs105
-rw-r--r--Xamarin.Forms.Core/NullEffect.cs13
-rw-r--r--Xamarin.Forms.Core/NumericExtensions.cs17
-rw-r--r--Xamarin.Forms.Core/NumericKeyboard.cs6
-rw-r--r--Xamarin.Forms.Core/ObservableList.cs112
-rw-r--r--Xamarin.Forms.Core/ObservableWrapper.cs256
-rw-r--r--Xamarin.Forms.Core/OnIdiom.cs21
-rw-r--r--Xamarin.Forms.Core/OnPlatform.cs27
-rw-r--r--Xamarin.Forms.Core/OpenGLView.cs38
-rw-r--r--Xamarin.Forms.Core/OrderedDictionary.cs451
-rw-r--r--Xamarin.Forms.Core/Page.cs403
-rw-r--r--Xamarin.Forms.Core/PanGestureRecognizer.cs37
-rw-r--r--Xamarin.Forms.Core/PanUpdatedEventArgs.cs27
-rw-r--r--Xamarin.Forms.Core/ParameterAttribute.cs15
-rw-r--r--Xamarin.Forms.Core/Performance.cs93
-rw-r--r--Xamarin.Forms.Core/Picker.cs54
-rw-r--r--Xamarin.Forms.Core/PinchGestureRecognizer.cs51
-rw-r--r--Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs24
-rw-r--r--Xamarin.Forms.Core/PlatformEffect.cs28
-rw-r--r--Xamarin.Forms.Core/Point.cs95
-rw-r--r--Xamarin.Forms.Core/PointTypeConverter.cs21
-rw-r--r--Xamarin.Forms.Core/PreserveAttribute.cs23
-rw-r--r--Xamarin.Forms.Core/ProgressBar.cs26
-rw-r--r--Xamarin.Forms.Core/Properties/AssemblyInfo.cs56
-rw-r--r--Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs8
-rw-r--r--Xamarin.Forms.Core/PropertyChangingEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/PropertyChangingEventHandler.cs4
-rw-r--r--Xamarin.Forms.Core/ReadOnlyCastingList.cs35
-rw-r--r--Xamarin.Forms.Core/ReadOnlyListAdapter.cs100
-rw-r--r--Xamarin.Forms.Core/Rectangle.cs244
-rw-r--r--Xamarin.Forms.Core/RectangleTypeConverter.cs22
-rw-r--r--Xamarin.Forms.Core/ReflectionExtensions.cs68
-rw-r--r--Xamarin.Forms.Core/Registrar.cs147
-rw-r--r--Xamarin.Forms.Core/RelativeLayout.cs317
-rw-r--r--Xamarin.Forms.Core/RenderWithAttribute.cs15
-rw-r--r--Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs15
-rw-r--r--Xamarin.Forms.Core/ResourceDictionary.cs135
-rw-r--r--Xamarin.Forms.Core/ResourcesChangedEventArgs.cs15
-rw-r--r--Xamarin.Forms.Core/ResourcesExtensions.cs62
-rw-r--r--Xamarin.Forms.Core/RoutingEffect.cs42
-rw-r--r--Xamarin.Forms.Core/RowDefinition.cs34
-rw-r--r--Xamarin.Forms.Core/RowDefinitionCollection.cs6
-rw-r--r--Xamarin.Forms.Core/ScrollOrientation.cs9
-rw-r--r--Xamarin.Forms.Core/ScrollToMode.cs10
-rw-r--r--Xamarin.Forms.Core/ScrollToPosition.cs10
-rw-r--r--Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs56
-rw-r--r--Xamarin.Forms.Core/ScrollView.cs287
-rw-r--r--Xamarin.Forms.Core/ScrolledEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/SearchBar.cs156
-rw-r--r--Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/SeparatorMenuItem.cs6
-rw-r--r--Xamarin.Forms.Core/SeparatorVisibility.cs8
-rw-r--r--Xamarin.Forms.Core/Setter.cs88
-rw-r--r--Xamarin.Forms.Core/SettersExtensions.cs29
-rw-r--r--Xamarin.Forms.Core/Size.cs110
-rw-r--r--Xamarin.Forms.Core/SizeRequest.cs29
-rw-r--r--Xamarin.Forms.Core/Slider.cs85
-rw-r--r--Xamarin.Forms.Core/Span.cs169
-rw-r--r--Xamarin.Forms.Core/SplitOrderedList.cs497
-rw-r--r--Xamarin.Forms.Core/StackLayout.cs460
-rw-r--r--Xamarin.Forms.Core/StackOrientation.cs8
-rw-r--r--Xamarin.Forms.Core/Stepper.cs94
-rw-r--r--Xamarin.Forms.Core/StreamImageSource.cs47
-rw-r--r--Xamarin.Forms.Core/StreamWrapper.cs81
-rw-r--r--Xamarin.Forms.Core/Style.cs184
-rw-r--r--Xamarin.Forms.Core/Switch.cs24
-rw-r--r--Xamarin.Forms.Core/SynchronizedList.cs130
-rw-r--r--Xamarin.Forms.Core/TabbedPage.cs17
-rw-r--r--Xamarin.Forms.Core/TableIntent.cs10
-rw-r--r--Xamarin.Forms.Core/TableModel.cs76
-rw-r--r--Xamarin.Forms.Core/TableRoot.cs60
-rw-r--r--Xamarin.Forms.Core/TableSection.cs137
-rw-r--r--Xamarin.Forms.Core/TableSectionBase.cs33
-rw-r--r--Xamarin.Forms.Core/TableView.cs228
-rw-r--r--Xamarin.Forms.Core/TapGestureRecognizer.cs95
-rw-r--r--Xamarin.Forms.Core/TappedEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/TargetIdiom.cs10
-rw-r--r--Xamarin.Forms.Core/TargetPlatform.cs11
-rw-r--r--Xamarin.Forms.Core/TelephoneKeyboard.cs6
-rw-r--r--Xamarin.Forms.Core/TemplateBinding.cs132
-rw-r--r--Xamarin.Forms.Core/TemplateExtensions.cs15
-rw-r--r--Xamarin.Forms.Core/TemplateUtilities.cs123
-rw-r--r--Xamarin.Forms.Core/TemplatedItemsList.cs1325
-rw-r--r--Xamarin.Forms.Core/TemplatedPage.cs38
-rw-r--r--Xamarin.Forms.Core/TemplatedView.cs68
-rw-r--r--Xamarin.Forms.Core/TextAlignment.cs9
-rw-r--r--Xamarin.Forms.Core/TextChangedEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/TextKeyboard.cs6
-rw-r--r--Xamarin.Forms.Core/Thickness.cs92
-rw-r--r--Xamarin.Forms.Core/ThicknessTypeConverter.cs35
-rw-r--r--Xamarin.Forms.Core/Ticker.cs117
-rw-r--r--Xamarin.Forms.Core/TimePicker.cs29
-rw-r--r--Xamarin.Forms.Core/ToggledEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/Toolbar.cs46
-rw-r--r--Xamarin.Forms.Core/ToolbarItem.cs57
-rw-r--r--Xamarin.Forms.Core/ToolbarItemEventArgs.cs14
-rw-r--r--Xamarin.Forms.Core/ToolbarItemOrder.cs9
-rw-r--r--Xamarin.Forms.Core/ToolbarTracker.cs184
-rw-r--r--Xamarin.Forms.Core/TrackableCollection.cs16
-rw-r--r--Xamarin.Forms.Core/Tweener.cs124
-rw-r--r--Xamarin.Forms.Core/TypeConverter.cs33
-rw-r--r--Xamarin.Forms.Core/TypeConverterAttribute.cs74
-rw-r--r--Xamarin.Forms.Core/TypeTypeConverter.cs31
-rw-r--r--Xamarin.Forms.Core/UnsolvableConstraintsException.cs11
-rw-r--r--Xamarin.Forms.Core/UriImageSource.cs223
-rw-r--r--Xamarin.Forms.Core/UriTypeConverter.cs24
-rw-r--r--Xamarin.Forms.Core/UrlKeyboard.cs6
-rw-r--r--Xamarin.Forms.Core/UrlWebViewSource.cs19
-rw-r--r--Xamarin.Forms.Core/ValueChangedEventArgs.cs17
-rw-r--r--Xamarin.Forms.Core/Vec2.cs14
-rw-r--r--Xamarin.Forms.Core/View.cs114
-rw-r--r--Xamarin.Forms.Core/ViewExtensions.cs197
-rw-r--r--Xamarin.Forms.Core/ViewState.cs12
-rw-r--r--Xamarin.Forms.Core/VisualElement.cs764
-rw-r--r--Xamarin.Forms.Core/WeakReferenceExtensions.cs16
-rw-r--r--Xamarin.Forms.Core/WebNavigatedEventArgs.cs12
-rw-r--r--Xamarin.Forms.Core/WebNavigatingEventArgs.cs11
-rw-r--r--Xamarin.Forms.Core/WebNavigationEvent.cs10
-rw-r--r--Xamarin.Forms.Core/WebNavigationEventArgs.cs20
-rw-r--r--Xamarin.Forms.Core/WebNavigationResult.cs10
-rw-r--r--Xamarin.Forms.Core/WebView.cs126
-rw-r--r--Xamarin.Forms.Core/WebViewSource.cs28
-rw-r--r--Xamarin.Forms.Core/WebViewSourceTypeConverter.cs15
-rw-r--r--Xamarin.Forms.Core/Xamarin.Forms.Core.csproj408
-rw-r--r--Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec14
-rw-r--r--Xamarin.Forms.Core/XamlParseException.cs30
-rw-r--r--Xamarin.Forms.Core/XmlLineInfo.cs29
349 files changed, 27299 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/AbsoluteLayout.cs b/Xamarin.Forms.Core/AbsoluteLayout.cs
new file mode 100644
index 00000000..51b5ca12
--- /dev/null
+++ b/Xamarin.Forms.Core/AbsoluteLayout.cs
@@ -0,0 +1,313 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace Xamarin.Forms
+{
+ public class AbsoluteLayout : Layout<View>
+ {
+ public static readonly BindableProperty LayoutFlagsProperty = BindableProperty.CreateAttached("LayoutFlags", typeof(AbsoluteLayoutFlags), typeof(AbsoluteLayout), AbsoluteLayoutFlags.None);
+
+ public static readonly BindableProperty LayoutBoundsProperty = BindableProperty.CreateAttached("LayoutBounds", typeof(Rectangle), typeof(AbsoluteLayout), new Rectangle(0, 0, AutoSize, AutoSize));
+
+ readonly AbsoluteElementCollection _children;
+
+ public AbsoluteLayout()
+ {
+ _children = new AbsoluteElementCollection(InternalChildren, this);
+ }
+
+ public static double AutoSize
+ {
+ get { return -1; }
+ }
+
+ public new IAbsoluteList<View> Children
+ {
+ get { return _children; }
+ }
+
+ [TypeConverter(typeof(BoundsTypeConverter))]
+ public static Rectangle GetLayoutBounds(BindableObject bindable)
+ {
+ return (Rectangle)bindable.GetValue(LayoutBoundsProperty);
+ }
+
+ public static AbsoluteLayoutFlags GetLayoutFlags(BindableObject bindable)
+ {
+ return (AbsoluteLayoutFlags)bindable.GetValue(LayoutFlagsProperty);
+ }
+
+ public static void SetLayoutBounds(BindableObject bindable, Rectangle bounds)
+ {
+ bindable.SetValue(LayoutBoundsProperty, bounds);
+ }
+
+ public static void SetLayoutFlags(BindableObject bindable, AbsoluteLayoutFlags flags)
+ {
+ bindable.SetValue(LayoutFlagsProperty, flags);
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ foreach (View child in LogicalChildren)
+ {
+ Rectangle rect = ComputeLayoutForRegion(child, new Size(width, height));
+ rect.X += x;
+ rect.Y += y;
+
+ LayoutChildIntoBoundingRegion(child, rect);
+ }
+ }
+
+ protected override void OnChildAdded(Element child)
+ {
+ base.OnChildAdded(child);
+ child.PropertyChanged += ChildOnPropertyChanged;
+ }
+
+ protected override void OnChildRemoved(Element child)
+ {
+ child.PropertyChanged -= ChildOnPropertyChanged;
+ base.OnChildRemoved(child);
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ var bestFitSize = new Size();
+ var minimum = new Size();
+ foreach (View child in LogicalChildren)
+ {
+ SizeRequest desiredSize = ComputeBoundingRegionDesiredSize(child);
+
+ bestFitSize.Width = Math.Max(bestFitSize.Width, desiredSize.Request.Width);
+ bestFitSize.Height = Math.Max(bestFitSize.Height, desiredSize.Request.Height);
+ minimum.Width = Math.Max(minimum.Width, desiredSize.Minimum.Width);
+ minimum.Height = Math.Max(minimum.Height, desiredSize.Minimum.Height);
+ }
+
+ return new SizeRequest(bestFitSize, minimum);
+ }
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ AbsoluteLayoutFlags layoutFlags = GetLayoutFlags(view);
+
+ if ((layoutFlags & AbsoluteLayoutFlags.SizeProportional) == AbsoluteLayoutFlags.SizeProportional)
+ {
+ view.ComputedConstraint = Constraint;
+ return;
+ }
+
+ var result = LayoutConstraint.None;
+ Rectangle layoutBounds = GetLayoutBounds(view);
+ if ((layoutFlags & AbsoluteLayoutFlags.HeightProportional) != 0)
+ {
+ bool widthLocked = layoutBounds.Width != AutoSize;
+ result = Constraint & LayoutConstraint.VerticallyFixed;
+ if (widthLocked)
+ result |= LayoutConstraint.HorizontallyFixed;
+ }
+ else if ((layoutFlags & AbsoluteLayoutFlags.WidthProportional) != 0)
+ {
+ bool heightLocked = layoutBounds.Height != AutoSize;
+ result = Constraint & LayoutConstraint.HorizontallyFixed;
+ if (heightLocked)
+ result |= LayoutConstraint.VerticallyFixed;
+ }
+ else
+ {
+ if (layoutBounds.Width != AutoSize)
+ result |= LayoutConstraint.HorizontallyFixed;
+ if (layoutBounds.Height != AutoSize)
+ result |= LayoutConstraint.VerticallyFixed;
+ }
+
+ view.ComputedConstraint = result;
+ }
+
+ void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == LayoutFlagsProperty.PropertyName || e.PropertyName == LayoutBoundsProperty.PropertyName)
+ {
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ UpdateChildrenLayout();
+ }
+ }
+
+ static SizeRequest ComputeBoundingRegionDesiredSize(View view)
+ {
+ var width = 0.0;
+ var height = 0.0;
+
+ var sizeRequest = new Lazy<SizeRequest>(() => view.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins));
+
+ Rectangle bounds = GetLayoutBounds(view);
+ AbsoluteLayoutFlags absFlags = GetLayoutFlags(view);
+ bool widthIsProportional = (absFlags & AbsoluteLayoutFlags.WidthProportional) != 0;
+ bool heightIsProportional = (absFlags & AbsoluteLayoutFlags.HeightProportional) != 0;
+ bool xIsProportional = (absFlags & AbsoluteLayoutFlags.XProportional) != 0;
+ bool yIsProportional = (absFlags & AbsoluteLayoutFlags.YProportional) != 0;
+
+ // add in required x values
+ if (!xIsProportional)
+ {
+ width += bounds.X;
+ }
+
+ if (!yIsProportional)
+ {
+ height += bounds.Y;
+ }
+
+ double minWidth = width;
+ double minHeight = height;
+
+ if (!widthIsProportional && bounds.Width != AutoSize)
+ {
+ // fixed size
+ width += bounds.Width;
+ minWidth += bounds.Width;
+ }
+ else if (!widthIsProportional)
+ {
+ // auto size
+ width += sizeRequest.Value.Request.Width;
+ minWidth += sizeRequest.Value.Minimum.Width;
+ }
+ else
+ {
+ // proportional size
+ width += sizeRequest.Value.Request.Width / Math.Max(0.25, bounds.Width);
+ //minWidth += 0;
+ }
+
+ if (!heightIsProportional && bounds.Height != AutoSize)
+ {
+ // fixed size
+ height += bounds.Height;
+ minHeight += bounds.Height;
+ }
+ else if (!heightIsProportional)
+ {
+ // auto size
+ height += sizeRequest.Value.Request.Height;
+ minHeight += sizeRequest.Value.Minimum.Height;
+ }
+ else
+ {
+ // proportional size
+ height += sizeRequest.Value.Request.Height / Math.Max(0.25, bounds.Height);
+ //minHeight += 0;
+ }
+
+ return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight));
+ }
+
+ static Rectangle ComputeLayoutForRegion(View view, Size region)
+ {
+ var result = new Rectangle();
+
+ SizeRequest sizeRequest;
+ Rectangle bounds = GetLayoutBounds(view);
+ AbsoluteLayoutFlags absFlags = GetLayoutFlags(view);
+ bool widthIsProportional = (absFlags & AbsoluteLayoutFlags.WidthProportional) != 0;
+ bool heightIsProportional = (absFlags & AbsoluteLayoutFlags.HeightProportional) != 0;
+ bool xIsProportional = (absFlags & AbsoluteLayoutFlags.XProportional) != 0;
+ bool yIsProportional = (absFlags & AbsoluteLayoutFlags.YProportional) != 0;
+
+ if (widthIsProportional)
+ {
+ result.Width = Math.Round(region.Width * bounds.Width);
+ }
+ else if (bounds.Width != AutoSize)
+ {
+ result.Width = bounds.Width;
+ }
+
+ if (heightIsProportional)
+ {
+ result.Height = Math.Round(region.Height * bounds.Height);
+ }
+ else if (bounds.Height != AutoSize)
+ {
+ result.Height = bounds.Height;
+ }
+
+ if (!widthIsProportional && bounds.Width == AutoSize)
+ {
+ if (!heightIsProportional && bounds.Width == AutoSize)
+ {
+ // Width and Height are auto
+ sizeRequest = view.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
+ result.Width = sizeRequest.Request.Width;
+ result.Height = sizeRequest.Request.Height;
+ }
+ else
+ {
+ // Only width is auto
+ sizeRequest = view.Measure(region.Width, result.Height, MeasureFlags.IncludeMargins);
+ result.Width = sizeRequest.Request.Width;
+ }
+ }
+ else if (!heightIsProportional && bounds.Height == AutoSize)
+ {
+ // Only height is auto
+ sizeRequest = view.Measure(result.Width, region.Height, MeasureFlags.IncludeMargins);
+ result.Height = sizeRequest.Request.Height;
+ }
+
+ if (xIsProportional)
+ {
+ result.X = Math.Round((region.Width - result.Width) * bounds.X);
+ }
+ else
+ {
+ result.X = bounds.X;
+ }
+
+ if (yIsProportional)
+ {
+ result.Y = Math.Round((region.Height - result.Height) * bounds.Y);
+ }
+ else
+ {
+ result.Y = bounds.Y;
+ }
+
+ return result;
+ }
+
+ public interface IAbsoluteList<T> : IList<T> where T : View
+ {
+ void Add(View view, Rectangle bounds, AbsoluteLayoutFlags flags = AbsoluteLayoutFlags.None);
+
+ void Add(View view, Point position);
+ }
+
+ class AbsoluteElementCollection : ElementCollection<View>, IAbsoluteList<View>
+ {
+ public AbsoluteElementCollection(ObservableCollection<Element> inner, AbsoluteLayout parent) : base(inner)
+ {
+ Parent = parent;
+ }
+
+ internal AbsoluteLayout Parent { get; set; }
+
+ public void Add(View view, Rectangle bounds, AbsoluteLayoutFlags flags = AbsoluteLayoutFlags.None)
+ {
+ SetLayoutBounds(view, bounds);
+ SetLayoutFlags(view, flags);
+ Add(view);
+ }
+
+ public void Add(View view, Point position)
+ {
+ SetLayoutBounds(view, new Rectangle(position.X, position.Y, AutoSize, AutoSize));
+ Add(view);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/AbsoluteLayoutFlags.cs b/Xamarin.Forms.Core/AbsoluteLayoutFlags.cs
new file mode 100644
index 00000000..dcc81c10
--- /dev/null
+++ b/Xamarin.Forms.Core/AbsoluteLayoutFlags.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ public enum AbsoluteLayoutFlags
+ {
+ None = 0,
+ XProportional = 1 << 0,
+ YProportional = 1 << 1,
+ WidthProportional = 1 << 2,
+ HeightProportional = 1 << 3,
+ PositionProportional = 1 | 1 << 1,
+ SizeProportional = 1 << 2 | 1 << 3,
+ All = ~0
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ActionSheetArguments.cs b/Xamarin.Forms.Core/ActionSheetArguments.cs
new file mode 100644
index 00000000..3417ed61
--- /dev/null
+++ b/Xamarin.Forms.Core/ActionSheetArguments.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal class ActionSheetArguments
+ {
+ public ActionSheetArguments(string title, string cancel, string destruction, IEnumerable<string> buttons)
+ {
+ Title = title;
+ Cancel = cancel;
+ Destruction = destruction;
+ Buttons = buttons;
+ Result = new TaskCompletionSource<string>();
+ }
+
+ /// <summary>
+ /// Gets titles of any buttons on the action sheet that aren't <see cref="Cancel" /> or <see cref="Destruction" />. Can
+ /// be <c>null</c>.
+ /// </summary>
+ public IEnumerable<string> Buttons { get; private set; }
+
+ /// <summary>
+ /// Gets the text for a cancel button. Can be null.
+ /// </summary>
+ public string Cancel { get; private set; }
+
+ /// <summary>
+ /// Gets the text for a destructive button. Can be null.
+ /// </summary>
+ public string Destruction { get; private set; }
+
+ public TaskCompletionSource<string> Result { get; }
+
+ /// <summary>
+ /// Gets the title for the action sheet. Can be null.
+ /// </summary>
+ public string Title { get; private set; }
+
+ public void SetResult(string result)
+ {
+ Result.TrySetResult(result);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ActivityIndicator.cs b/Xamarin.Forms.Core/ActivityIndicator.cs
new file mode 100644
index 00000000..3689609a
--- /dev/null
+++ b/Xamarin.Forms.Core/ActivityIndicator.cs
@@ -0,0 +1,24 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_ActivityIndicatorRenderer))]
+ public class ActivityIndicator : View
+ {
+ public static readonly BindableProperty IsRunningProperty = BindableProperty.Create("IsRunning", typeof(bool), typeof(ActivityIndicator), default(bool));
+
+ public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof(Color), typeof(ActivityIndicator), Color.Default);
+
+ public Color Color
+ {
+ get { return (Color)GetValue(ColorProperty); }
+ set { SetValue(ColorProperty, value); }
+ }
+
+ public bool IsRunning
+ {
+ get { return (bool)GetValue(IsRunningProperty); }
+ set { SetValue(IsRunningProperty, value); }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/AlertArguments.cs b/Xamarin.Forms.Core/AlertArguments.cs
new file mode 100644
index 00000000..87224cb4
--- /dev/null
+++ b/Xamarin.Forms.Core/AlertArguments.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal class AlertArguments
+ {
+ public AlertArguments(string title, string message, string accept, string cancel)
+ {
+ Title = title;
+ Message = message;
+ Accept = accept;
+ Cancel = cancel;
+ Result = new TaskCompletionSource<bool>();
+ }
+
+ /// <summary>
+ /// Gets the text for the accept button. Can be null.
+ /// </summary>
+ public string Accept { get; private set; }
+
+ /// <summary>
+ /// Gets the text of the cancel button.
+ /// </summary>
+ public string Cancel { get; private set; }
+
+ /// <summary>
+ /// Gets the message for the alert. Can be null.
+ /// </summary>
+ public string Message { get; private set; }
+
+ public TaskCompletionSource<bool> Result { get; }
+
+ /// <summary>
+ /// Gets the title for the alert. Can be null.
+ /// </summary>
+ public string Title { get; private set; }
+
+ public void SetResult(bool result)
+ {
+ Result.TrySetResult(result);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/AnimatableKey.cs b/Xamarin.Forms.Core/AnimatableKey.cs
new file mode 100644
index 00000000..2a73ef6d
--- /dev/null
+++ b/Xamarin.Forms.Core/AnimatableKey.cs
@@ -0,0 +1,82 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class AnimatableKey
+ {
+ public AnimatableKey(IAnimatable animatable, string handle)
+ {
+ if (animatable == null)
+ {
+ throw new ArgumentNullException(nameof(animatable));
+ }
+
+ if (string.IsNullOrEmpty(handle))
+ {
+ throw new ArgumentException("Argument is null or empty", nameof(handle));
+ }
+
+ Animatable = new WeakReference<IAnimatable>(animatable);
+ Handle = handle;
+ }
+
+ public WeakReference<IAnimatable> Animatable { get; }
+
+ public string Handle { get; }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+ if (obj.GetType() != GetType())
+ {
+ return false;
+ }
+ return Equals((AnimatableKey)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ IAnimatable target;
+ if (!Animatable.TryGetTarget(out target))
+ {
+ return Handle?.GetHashCode() ?? 0;
+ }
+
+ return ((target?.GetHashCode() ?? 0) * 397) ^ (Handle?.GetHashCode() ?? 0);
+ }
+ }
+
+ protected bool Equals(AnimatableKey other)
+ {
+ if (!string.Equals(Handle, other.Handle))
+ {
+ return false;
+ }
+
+ IAnimatable thisAnimatable;
+
+ if (!Animatable.TryGetTarget(out thisAnimatable))
+ {
+ return false;
+ }
+
+ IAnimatable thatAnimatable;
+
+ if (!other.Animatable.TryGetTarget(out thatAnimatable))
+ {
+ return false;
+ }
+
+ return Equals(thisAnimatable, thatAnimatable);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Animation.cs b/Xamarin.Forms.Core/Animation.cs
new file mode 100644
index 00000000..03cded1e
--- /dev/null
+++ b/Xamarin.Forms.Core/Animation.cs
@@ -0,0 +1,138 @@
+//
+// Tweener.cs
+//
+// Author:
+// Jason Smith <jason.smith@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public class Animation : IEnumerable
+ {
+ readonly List<Animation> _children;
+ readonly Easing _easing;
+ readonly Action _finished;
+ readonly Action<double> _step;
+ double _beginAt;
+ double _finishAt;
+ bool _finishedTriggered;
+
+ public Animation()
+ {
+ _children = new List<Animation>();
+ _easing = Easing.Linear;
+ _step = f => { };
+ }
+
+ public Animation(Action<double> callback, double start = 0.0f, double end = 1.0f, Easing easing = null, Action finished = null)
+ {
+ _children = new List<Animation>();
+ _easing = easing ?? Easing.Linear;
+ _finished = finished;
+
+ Func<double, double> transform = AnimationExtensions.Interpolate(start, end);
+ _step = f => callback(transform(f));
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _children.GetEnumerator();
+ }
+
+ public void Add(double beginAt, double finishAt, Animation animation)
+ {
+ if (beginAt < 0 || beginAt > 1)
+ throw new ArgumentOutOfRangeException("beginAt");
+
+ if (finishAt < 0 || finishAt > 1)
+ throw new ArgumentOutOfRangeException("finishAt");
+
+ if (finishAt <= beginAt)
+ throw new ArgumentException("finishAt must be greater than beginAt");
+
+ animation._beginAt = beginAt;
+ animation._finishAt = finishAt;
+ _children.Add(animation);
+ }
+
+ public void Commit(IAnimatable owner, string name, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null, Func<bool> repeat = null)
+ {
+ owner.Animate(name, this, rate, length, easing, finished, repeat);
+ }
+
+ public Action<double> GetCallback()
+ {
+ Action<double> result = f =>
+ {
+ _step(_easing.Ease(f));
+ foreach (Animation animation in _children)
+ {
+ if (animation._finishedTriggered)
+ continue;
+
+ double val = Math.Max(0.0f, Math.Min(1.0f, (f - animation._beginAt) / (animation._finishAt - animation._beginAt)));
+
+ if (val <= 0.0f) // not ready to process yet
+ continue;
+
+ Action<double> callback = animation.GetCallback();
+ callback(val);
+
+ if (val >= 1.0f)
+ {
+ animation._finishedTriggered = true;
+ if (animation._finished != null)
+ animation._finished();
+ }
+ }
+ };
+ return result;
+ }
+
+ public Animation Insert(double beginAt, double finishAt, Animation animation)
+ {
+ Add(beginAt, finishAt, animation);
+ return this;
+ }
+
+ public Animation WithConcurrent(Animation animation, double beginAt = 0.0f, double finishAt = 1.0f)
+ {
+ animation._beginAt = beginAt;
+ animation._finishAt = finishAt;
+ _children.Add(animation);
+ return this;
+ }
+
+ public Animation WithConcurrent(Action<double> callback, double start = 0.0f, double end = 1.0f, Easing easing = null, double beginAt = 0.0f, double finishAt = 1.0f)
+ {
+ var child = new Animation(callback, start, end, easing);
+ child._beginAt = beginAt;
+ child._finishAt = finishAt;
+ _children.Add(child);
+ return this;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/AnimationExtensions.cs b/Xamarin.Forms.Core/AnimationExtensions.cs
new file mode 100644
index 00000000..efc3e405
--- /dev/null
+++ b/Xamarin.Forms.Core/AnimationExtensions.cs
@@ -0,0 +1,259 @@
+//
+// Tweener.cs
+//
+// Author:
+// Jason Smith <jason.smith@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public static class AnimationExtensions
+ {
+ static readonly Dictionary<AnimatableKey, Info> s_animations;
+ static readonly Dictionary<AnimatableKey, int> s_kinetics;
+
+ static AnimationExtensions()
+ {
+ s_animations = new Dictionary<AnimatableKey, Info>();
+ s_kinetics = new Dictionary<AnimatableKey, int>();
+ }
+
+ public static bool AbortAnimation(this IAnimatable self, string handle)
+ {
+ CheckAccess();
+
+ var key = new AnimatableKey(self, handle);
+
+ return AbortAnimation(key) && AbortKinetic(key);
+ }
+
+ public static void Animate(this IAnimatable self, string name, Animation animation, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null,
+ Func<bool> repeat = null)
+ {
+ self.Animate(name, animation.GetCallback(), rate, length, easing, finished, repeat);
+ }
+
+ public static void Animate(this IAnimatable self, string name, Action<double> callback, double start, double end, uint rate = 16, uint length = 250, Easing easing = null,
+ Action<double, bool> finished = null, Func<bool> repeat = null)
+ {
+ self.Animate(name, Interpolate(start, end), callback, rate, length, easing, finished, repeat);
+ }
+
+ public static void Animate(this IAnimatable self, string name, Action<double> callback, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null,
+ Func<bool> repeat = null)
+ {
+ self.Animate(name, x => x, callback, rate, length, easing, finished, repeat);
+ }
+
+ public static void Animate<T>(this IAnimatable self, string name, Func<double, T> transform, Action<T> callback, uint rate = 16, uint length = 250, Easing easing = null,
+ Action<T, bool> finished = null, Func<bool> repeat = null)
+ {
+ if (transform == null)
+ throw new ArgumentNullException(nameof(transform));
+ if (callback == null)
+ throw new ArgumentNullException(nameof(callback));
+ if (self == null)
+ throw new ArgumentNullException(nameof(self));
+
+ CheckAccess();
+
+ var key = new AnimatableKey(self, name);
+
+ AbortAnimation(key);
+
+ Action<double> step = f => callback(transform(f));
+ Action<double, bool> final = null;
+ if (finished != null)
+ final = (f, b) => finished(transform(f), b);
+
+ var info = new Info { Rate = rate, Length = length, Easing = easing ?? Easing.Linear };
+
+ var tweener = new Tweener(info.Length);
+ tweener.Handle = key;
+ tweener.ValueUpdated += HandleTweenerUpdated;
+ tweener.Finished += HandleTweenerFinished;
+
+ info.Tweener = tweener;
+ info.Callback = step;
+ info.Finished = final;
+ info.Repeat = repeat;
+ info.Owner = new WeakReference<IAnimatable>(self);
+
+ s_animations[key] = info;
+
+ info.Callback(0.0f);
+ tweener.Start();
+ }
+
+ public static void AnimateKinetic(this IAnimatable self, string name, Func<double, double, bool> callback, double velocity, double drag, Action finished = null)
+ {
+ CheckAccess();
+
+ var key = new AnimatableKey(self, name);
+
+ AbortKinetic(key);
+
+ double sign = velocity / Math.Abs(velocity);
+ velocity = Math.Abs(velocity);
+
+ int tick = Ticker.Default.Insert(step =>
+ {
+ long ms = step;
+
+ velocity -= drag * ms;
+ velocity = Math.Max(0, velocity);
+
+ var result = false;
+ if (velocity > 0)
+ {
+ result = callback(sign * velocity * ms, velocity);
+ }
+
+ if (!result)
+ {
+ finished?.Invoke();
+ s_kinetics.Remove(key);
+ }
+ return result;
+ });
+
+ s_kinetics[key] = tick;
+ }
+
+ public static bool AnimationIsRunning(this IAnimatable self, string handle)
+ {
+ CheckAccess();
+
+ var key = new AnimatableKey(self, handle);
+
+ return s_animations.ContainsKey(key);
+ }
+
+ public static Func<double, double> Interpolate(double start, double end = 1.0f, double reverseVal = 0.0f, bool reverse = false)
+ {
+ double target = reverse ? reverseVal : end;
+ return x => start + (target - start) * x;
+ }
+
+ static bool AbortAnimation(AnimatableKey key)
+ {
+ if (!s_animations.ContainsKey(key))
+ {
+ return false;
+ }
+
+ Info info = s_animations[key];
+ info.Tweener.ValueUpdated -= HandleTweenerUpdated;
+ info.Tweener.Finished -= HandleTweenerFinished;
+ info.Tweener.Stop();
+ info.Finished?.Invoke(1.0f, true);
+
+ return s_animations.Remove(key);
+ }
+
+ static bool AbortKinetic(AnimatableKey key)
+ {
+ if (!s_kinetics.ContainsKey(key))
+ {
+ return false;
+ }
+
+ Ticker.Default.Remove(s_kinetics[key]);
+ return s_kinetics.Remove(key);
+ }
+
+ static void CheckAccess()
+ {
+ if (Device.IsInvokeRequired)
+ {
+ throw new InvalidOperationException("Animation operations must be invoked on the UI thread");
+ }
+ }
+
+ static void HandleTweenerFinished(object o, EventArgs args)
+ {
+ var tweener = o as Tweener;
+ Info info;
+ if (tweener != null && s_animations.TryGetValue(tweener.Handle, out info))
+ {
+ var repeat = false;
+ if (info.Repeat != null)
+ repeat = info.Repeat();
+
+ IAnimatable owner;
+ if (info.Owner.TryGetTarget(out owner))
+ owner.BatchBegin();
+ info.Callback(tweener.Value);
+
+ if (!repeat)
+ {
+ s_animations.Remove(tweener.Handle);
+ tweener.ValueUpdated -= HandleTweenerUpdated;
+ tweener.Finished -= HandleTweenerFinished;
+ }
+
+ info.Finished?.Invoke(tweener.Value, false);
+
+ if (info.Owner.TryGetTarget(out owner))
+ owner.BatchCommit();
+
+ if (repeat)
+ {
+ tweener.Start();
+ }
+ }
+ }
+
+ static void HandleTweenerUpdated(object o, EventArgs args)
+ {
+ var tweener = o as Tweener;
+ Info info;
+ IAnimatable owner;
+
+ if (tweener != null && s_animations.TryGetValue(tweener.Handle, out info) && info.Owner.TryGetTarget(out owner))
+ {
+ owner.BatchBegin();
+ info.Callback(info.Easing.Ease(tweener.Value));
+ owner.BatchCommit();
+ }
+ }
+
+ class Info
+ {
+ public Action<double> Callback;
+ public Action<double, bool> Finished;
+ public Func<bool> Repeat;
+ public Tweener Tweener;
+
+ public Easing Easing { get; set; }
+
+ public uint Length { get; set; }
+
+ public WeakReference<IAnimatable> Owner { get; set; }
+
+ public uint Rate { get; set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Application.cs b/Xamarin.Forms.Core/Application.cs
new file mode 100644
index 00000000..1dfe258b
--- /dev/null
+++ b/Xamarin.Forms.Core/Application.cs
@@ -0,0 +1,308 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ public class Application : Element, IResourcesProvider, IApplicationController
+ {
+ static Application s_current;
+ readonly Task<IDictionary<string, object>> _propertiesTask;
+
+ bool _isSaving;
+
+ ReadOnlyCollection<Element> _logicalChildren;
+
+ Page _mainPage;
+
+ ResourceDictionary _resources;
+ bool _saveAgain;
+
+ protected Application()
+ {
+ var f = false;
+ if (f)
+ Loader.Load();
+ NavigationProxy = new NavigationImpl(this);
+ Current = this;
+ _propertiesTask = GetPropertiesAsync();
+
+ SystemResources = DependencyService.Get<ISystemResourcesProvider>().GetSystemResources();
+ SystemResources.ValuesChanged += OnParentResourcesChanged;
+ }
+
+ public static Application Current
+ {
+ get { return s_current; }
+ internal set
+ {
+ if (s_current == value)
+ return;
+ if (value == null)
+ s_current = null; //Allow to reset current for unittesting
+ s_current = value;
+ }
+ }
+
+ public Page MainPage
+ {
+ get { return _mainPage; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException("value");
+
+ if (_mainPage == value)
+ return;
+
+ OnPropertyChanging();
+ if (_mainPage != null)
+ {
+ InternalChildren.Remove(_mainPage);
+ _mainPage.Parent = null;
+ }
+
+ _mainPage = value;
+
+ if (_mainPage != null)
+ {
+ _mainPage.Parent = this;
+ _mainPage.NavigationProxy.Inner = NavigationProxy;
+ InternalChildren.Add(_mainPage);
+ }
+ OnPropertyChanged();
+ }
+ }
+
+ public IDictionary<string, object> Properties
+ {
+ get { return _propertiesTask.Result; }
+ }
+
+ internal override ReadOnlyCollection<Element> LogicalChildren
+ {
+ get { return _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(InternalChildren)); }
+ }
+
+ internal NavigationProxy NavigationProxy { get; }
+
+ internal int PanGestureId { get; set; }
+
+ internal IResourceDictionary SystemResources { get; }
+
+ ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>();
+
+ public ResourceDictionary Resources
+ {
+ get { return _resources; }
+ set
+ {
+ if (_resources == value)
+ return;
+ OnPropertyChanging();
+ if (_resources != null)
+ ((IResourceDictionary)_resources).ValuesChanged -= OnResourcesChanged;
+ _resources = value;
+ OnResourcesChanged(value);
+ if (_resources != null)
+ ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged;
+ OnPropertyChanged();
+ }
+ }
+
+ public event EventHandler<ModalPoppedEventArgs> ModalPopped;
+
+ public event EventHandler<ModalPoppingEventArgs> ModalPopping;
+
+ public event EventHandler<ModalPushedEventArgs> ModalPushed;
+
+ public event EventHandler<ModalPushingEventArgs> ModalPushing;
+
+ public async Task SavePropertiesAsync()
+ {
+ if (Device.IsInvokeRequired)
+ Device.BeginInvokeOnMainThread(async () => await SetPropertiesAsync());
+ else
+ await SetPropertiesAsync();
+ }
+
+ protected override void OnParentSet()
+ {
+ throw new InvalidOperationException("Setting a Parent on Application is invalid.");
+ }
+
+ protected virtual void OnResume()
+ {
+ }
+
+ protected virtual void OnSleep()
+ {
+ }
+
+ protected virtual void OnStart()
+ {
+ }
+
+ internal static void ClearCurrent()
+ {
+ s_current = null;
+ }
+
+ internal static bool IsApplicationOrNull(Element element)
+ {
+ return element == null || element is Application;
+ }
+
+ internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
+ {
+ if (Resources == null || Resources.Count == 0)
+ {
+ base.OnParentResourcesChanged(values);
+ return;
+ }
+
+ var innerKeys = new HashSet<string>();
+ var changedResources = new List<KeyValuePair<string, object>>();
+ foreach (KeyValuePair<string, object> c in Resources)
+ innerKeys.Add(c.Key);
+ foreach (KeyValuePair<string, object> value in values)
+ {
+ if (innerKeys.Add(value.Key))
+ changedResources.Add(value);
+ }
+ OnResourcesChanged(changedResources);
+ }
+
+ internal event EventHandler PopCanceled;
+
+ internal void SendResume()
+ {
+ s_current = this;
+ OnResume();
+ }
+
+ internal Task SendSleepAsync()
+ {
+ OnSleep();
+ return SavePropertiesAsync();
+ }
+
+ internal void SendStart()
+ {
+ OnStart();
+ }
+
+ async Task<IDictionary<string, object>> GetPropertiesAsync()
+ {
+ var deserializer = DependencyService.Get<IDeserializer>();
+ if (deserializer == null)
+ {
+ Log.Warning("Startup", "No IDeserialzier was found registered");
+ return new Dictionary<string, object>(4);
+ }
+
+ IDictionary<string, object> properties = await deserializer.DeserializePropertiesAsync().ConfigureAwait(false);
+ if (properties == null)
+ properties = new Dictionary<string, object>(4);
+
+ return properties;
+ }
+
+ void OnModalPopped(Page modalPage)
+ {
+ EventHandler<ModalPoppedEventArgs> handler = ModalPopped;
+ if (handler != null)
+ handler(this, new ModalPoppedEventArgs(modalPage));
+ }
+
+ bool OnModalPopping(Page modalPage)
+ {
+ EventHandler<ModalPoppingEventArgs> handler = ModalPopping;
+ var args = new ModalPoppingEventArgs(modalPage);
+ if (handler != null)
+ handler(this, args);
+ return args.Cancel;
+ }
+
+ void OnModalPushed(Page modalPage)
+ {
+ EventHandler<ModalPushedEventArgs> handler = ModalPushed;
+ if (handler != null)
+ handler(this, new ModalPushedEventArgs(modalPage));
+ }
+
+ void OnModalPushing(Page modalPage)
+ {
+ EventHandler<ModalPushingEventArgs> handler = ModalPushing;
+ if (handler != null)
+ handler(this, new ModalPushingEventArgs(modalPage));
+ }
+
+ void OnPopCanceled()
+ {
+ EventHandler handler = PopCanceled;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ async Task SetPropertiesAsync()
+ {
+ if (_isSaving)
+ {
+ _saveAgain = true;
+ return;
+ }
+ _isSaving = true;
+ await DependencyService.Get<IDeserializer>().SerializePropertiesAsync(Properties);
+ if (_saveAgain)
+ await DependencyService.Get<IDeserializer>().SerializePropertiesAsync(Properties);
+ _isSaving = _saveAgain = false;
+ }
+
+ class NavigationImpl : NavigationProxy
+ {
+ readonly Application _owner;
+
+ public NavigationImpl(Application owner)
+ {
+ _owner = owner;
+ }
+
+ protected override async Task<Page> OnPopModal(bool animated)
+ {
+ Page modal = ModalStack[ModalStack.Count - 1];
+ if (_owner.OnModalPopping(modal))
+ {
+ _owner.OnPopCanceled();
+ return null;
+ }
+ Page result = await base.OnPopModal(animated);
+ result.Parent = null;
+ _owner.OnModalPopped(result);
+ return result;
+ }
+
+ protected override async Task OnPushModal(Page modal, bool animated)
+ {
+ _owner.OnModalPushing(modal);
+
+ modal.Parent = _owner;
+
+ if (modal.NavigationProxy.ModalStack.Count == 0)
+ {
+ modal.NavigationProxy.Inner = this;
+ await base.OnPushModal(modal, animated);
+ }
+ else
+ {
+ await base.OnPushModal(modal, animated);
+ modal.NavigationProxy.Inner = this;
+ }
+
+ _owner.OnModalPushed(modal);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Aspect.cs b/Xamarin.Forms.Core/Aspect.cs
new file mode 100644
index 00000000..6a0085ca
--- /dev/null
+++ b/Xamarin.Forms.Core/Aspect.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public enum Aspect
+ {
+ AspectFit,
+ AspectFill,
+ Fill
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BackButtonPressedEventArgs.cs b/Xamarin.Forms.Core/BackButtonPressedEventArgs.cs
new file mode 100644
index 00000000..2cc9a185
--- /dev/null
+++ b/Xamarin.Forms.Core/BackButtonPressedEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class BackButtonPressedEventArgs : EventArgs
+ {
+ public bool Handled { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BaseMenuItem.cs b/Xamarin.Forms.Core/BaseMenuItem.cs
new file mode 100644
index 00000000..58831aa5
--- /dev/null
+++ b/Xamarin.Forms.Core/BaseMenuItem.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ public abstract class BaseMenuItem : Element
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindableObject.cs b/Xamarin.Forms.Core/BindableObject.cs
new file mode 100644
index 00000000..683d390a
--- /dev/null
+++ b/Xamarin.Forms.Core/BindableObject.cs
@@ -0,0 +1,647 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+ public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
+ {
+ public static readonly BindableProperty BindingContextProperty = BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object), BindingMode.OneWay, null,
+ BindingContextPropertyBindingPropertyChanged, null, null, BindingContextPropertyBindingChanging);
+
+ readonly List<BindablePropertyContext> _properties = new List<BindablePropertyContext>(4);
+
+ bool _applying;
+
+ object _inheritedContext;
+
+ public object BindingContext
+ {
+ get { return _inheritedContext ?? GetValue(BindingContextProperty); }
+ set { SetValue(BindingContextProperty, value); }
+ }
+
+ void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key)
+ {
+ SetDynamicResource(property, key, false);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public event EventHandler BindingContextChanged;
+
+ public void ClearValue(BindableProperty property)
+ {
+ ClearValue(property, true);
+ }
+
+ public void ClearValue(BindablePropertyKey propertyKey)
+ {
+ if (propertyKey == null)
+ throw new ArgumentNullException("propertyKey");
+
+ ClearValue(propertyKey.BindableProperty, false);
+ }
+
+ public object GetValue(BindableProperty property)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ BindablePropertyContext context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
+
+ if (context == null)
+ return property.DefaultValue;
+
+ return context.Value;
+ }
+
+ public event PropertyChangingEventHandler PropertyChanging;
+
+ public void RemoveBinding(BindableProperty property)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ BindablePropertyContext context = GetContext(property);
+ if (context == null || context.Binding == null)
+ return;
+
+ RemoveBinding(property, context);
+ }
+
+ public void SetBinding(BindableProperty targetProperty, BindingBase binding)
+ {
+ SetBinding(targetProperty, binding, false);
+ }
+
+ public void SetValue(BindableProperty property, object value)
+ {
+ SetValue(property, value, false, true);
+ }
+
+ public void SetValue(BindablePropertyKey propertyKey, object value)
+ {
+ if (propertyKey == null)
+ throw new ArgumentNullException("propertyKey");
+
+ SetValue(propertyKey.BindableProperty, value, false, false);
+ }
+
+ protected internal static void SetInheritedBindingContext(BindableObject bindable, object value)
+ {
+ BindablePropertyContext bpContext = bindable.GetContext(BindingContextProperty);
+ if (bpContext != null && ((bpContext.Attributes & BindableContextAttributes.IsManuallySet) != 0))
+ return;
+
+ object oldContext = bindable._inheritedContext;
+ if (bpContext != null && oldContext == null)
+ oldContext = bpContext.Value;
+
+ if (bpContext != null && bpContext.Binding != null)
+ {
+ bpContext.Binding.Context = value;
+ bindable._inheritedContext = null;
+ }
+ else
+ {
+ if (ReferenceEquals(oldContext, value))
+ return;
+
+ bindable._inheritedContext = value;
+ }
+
+ bindable.ApplyBindings(oldContext);
+ bindable.OnBindingContextChanged();
+ }
+
+ protected void ApplyBindings(object oldContext = null)
+ {
+ ApplyBindings(oldContext, false);
+ }
+
+ protected virtual void OnBindingContextChanged()
+ {
+ EventHandler change = BindingContextChanged;
+ if (change != null)
+ change(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangingEventHandler changing = PropertyChanging;
+ if (changing != null)
+ changing(this, new PropertyChangingEventArgs(propertyName));
+ }
+
+ protected void UnapplyBindings()
+ {
+ foreach (BindablePropertyContext context in _properties)
+ {
+ if (context.Binding == null)
+ continue;
+
+ context.Binding.Unapply();
+ }
+ }
+
+ internal bool GetIsBound(BindableProperty targetProperty)
+ {
+ if (targetProperty == null)
+ throw new ArgumentNullException("targetProperty");
+
+ BindablePropertyContext bpcontext = GetContext(targetProperty);
+ return bpcontext != null && bpcontext.Binding != null;
+ }
+
+ internal object[] GetValues(BindableProperty property0, BindableProperty property1)
+ {
+ var values = new object[2];
+
+ for (var i = 0; i < _properties.Count; i++)
+ {
+ BindablePropertyContext context = _properties[i];
+
+ if (ReferenceEquals(context.Property, property0))
+ {
+ values[0] = context.Value;
+ property0 = null;
+ }
+ else if (ReferenceEquals(context.Property, property1))
+ {
+ values[1] = context.Value;
+ property1 = null;
+ }
+
+ if (property0 == null && property1 == null)
+ return values;
+ }
+
+ if (!ReferenceEquals(property0, null))
+ values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
+ if (!ReferenceEquals(property1, null))
+ values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
+
+ return values;
+ }
+
+ internal object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2)
+ {
+ var values = new object[3];
+
+ for (var i = 0; i < _properties.Count; i++)
+ {
+ BindablePropertyContext context = _properties[i];
+
+ if (ReferenceEquals(context.Property, property0))
+ {
+ values[0] = context.Value;
+ property0 = null;
+ }
+ else if (ReferenceEquals(context.Property, property1))
+ {
+ values[1] = context.Value;
+ property1 = null;
+ }
+ else if (ReferenceEquals(context.Property, property2))
+ {
+ values[2] = context.Value;
+ property2 = null;
+ }
+
+ if (property0 == null && property1 == null && property2 == null)
+ return values;
+ }
+
+ if (!ReferenceEquals(property0, null))
+ values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
+ if (!ReferenceEquals(property1, null))
+ values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
+ if (!ReferenceEquals(property2, null))
+ values[2] = property2.DefaultValueCreator == null ? property2.DefaultValue : CreateAndAddContext(property2).Value;
+
+ return values;
+ }
+
+ internal virtual void OnRemoveDynamicResource(BindableProperty property)
+ {
+ }
+
+ internal virtual void OnSetDynamicResource(BindableProperty property, string key)
+ {
+ }
+
+ internal void RemoveDynamicResource(BindableProperty property)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ OnRemoveDynamicResource(property);
+ BindablePropertyContext context = GetOrCreateContext(property);
+ context.Attributes &= ~BindableContextAttributes.IsDynamicResource;
+ }
+
+ internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle)
+ {
+ if (targetProperty == null)
+ throw new ArgumentNullException("targetProperty");
+ if (binding == null)
+ throw new ArgumentNullException("binding");
+
+ BindablePropertyContext context = null;
+ if (fromStyle && (context = GetContext(targetProperty)) != null && (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0 &&
+ (context.Attributes & BindableContextAttributes.IsSetFromStyle) == 0)
+ return;
+
+ context = context ?? GetOrCreateContext(targetProperty);
+ if (fromStyle)
+ context.Attributes |= BindableContextAttributes.IsSetFromStyle;
+ else
+ context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
+
+ if (context.Binding != null)
+ context.Binding.Unapply();
+
+ BindingBase oldBinding = context.Binding;
+ context.Binding = binding;
+
+ if (targetProperty.BindingChanging != null)
+ targetProperty.BindingChanging(this, oldBinding, binding);
+
+ binding.Apply(BindingContext, this, targetProperty);
+ }
+
+ internal void SetDynamicResource(BindableProperty property, string key)
+ {
+ SetDynamicResource(property, key, false);
+ }
+
+ internal void SetDynamicResource(BindableProperty property, string key, bool fromStyle)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException("key");
+
+ BindablePropertyContext context = null;
+ if (fromStyle && (context = GetContext(property)) != null && (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0 &&
+ (context.Attributes & BindableContextAttributes.IsSetFromStyle) == 0)
+ return;
+
+ context = context ?? GetOrCreateContext(property);
+
+ context.Attributes |= BindableContextAttributes.IsDynamicResource;
+ if (fromStyle)
+ context.Attributes |= BindableContextAttributes.IsSetFromStyle;
+ else
+ context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
+
+ OnSetDynamicResource(property, key);
+ }
+
+ internal void SetValue(BindableProperty property, object value, bool fromStyle)
+ {
+ SetValue(property, value, fromStyle, true);
+ }
+
+ internal void SetValueCore(BindablePropertyKey propertyKey, object value, SetValueFlags attributes = SetValueFlags.None)
+ {
+ SetValueCore(propertyKey.BindableProperty, value, attributes, SetValuePrivateFlags.None);
+ }
+
+ internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes = SetValueFlags.None)
+ {
+ SetValueCore(property, value, attributes, SetValuePrivateFlags.Default);
+ }
+
+ internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
+ {
+ bool checkAccess = (privateAttributes & SetValuePrivateFlags.CheckAccess) != 0;
+ bool manuallySet = (privateAttributes & SetValuePrivateFlags.ManuallySet) != 0;
+ bool silent = (privateAttributes & SetValuePrivateFlags.Silent) != 0;
+ bool fromStyle = (privateAttributes & SetValuePrivateFlags.FromStyle) != 0;
+ bool converted = (privateAttributes & SetValuePrivateFlags.Converted) != 0;
+
+ if (property == null)
+ throw new ArgumentNullException("property");
+ if (checkAccess && property.IsReadOnly)
+ {
+ Debug.WriteLine("Can not set the BindableProperty \"{0}\" because it is readonly.", property.PropertyName);
+ return;
+ }
+
+ if (!converted && !property.TryConvert(ref value))
+ {
+ Log.Warning("SetValue", "Can not convert {0} to type '{1}'", value, property.ReturnType);
+ return;
+ }
+
+ if (property.ValidateValue != null && !property.ValidateValue(this, value))
+ throw new ArgumentException("Value was an invalid value for " + property.PropertyName, "value");
+
+ if (property.CoerceValue != null)
+ value = property.CoerceValue(this, value);
+
+ BindablePropertyContext context = GetOrCreateContext(property);
+ if (manuallySet)
+ context.Attributes |= BindableContextAttributes.IsManuallySet;
+ else
+ context.Attributes &= ~BindableContextAttributes.IsManuallySet;
+
+ if (fromStyle)
+ context.Attributes |= BindableContextAttributes.IsSetFromStyle;
+ // else ommitted on purpose
+
+ bool currentlyApplying = _applying;
+
+ if ((context.Attributes & BindableContextAttributes.IsBeingSet) != 0)
+ {
+ Queue<SetValueArgs> delayQueue = context.DelayedSetters;
+ if (delayQueue == null)
+ context.DelayedSetters = delayQueue = new Queue<SetValueArgs>();
+
+ delayQueue.Enqueue(new SetValueArgs(property, context, value, currentlyApplying, attributes));
+ }
+ else
+ {
+ context.Attributes |= BindableContextAttributes.IsBeingSet;
+ SetValueActual(property, context, value, currentlyApplying, attributes, silent);
+
+ Queue<SetValueArgs> delayQueue = context.DelayedSetters;
+ if (delayQueue != null)
+ {
+ while (delayQueue.Count > 0)
+ {
+ SetValueArgs s = delayQueue.Dequeue();
+ SetValueActual(s.Property, s.Context, s.Value, s.CurrentlyApplying, s.Attributes);
+ }
+
+ context.DelayedSetters = null;
+ }
+
+ context.Attributes &= ~BindableContextAttributes.IsBeingSet;
+ }
+ }
+
+ void ApplyBindings(object oldContext, bool skipBindingContext)
+ {
+ foreach (BindablePropertyContext context in _properties.ToArray())
+ {
+ BindingBase binding = context.Binding;
+ if (binding == null)
+ continue;
+
+ if (skipBindingContext && context.Property == BindingContextProperty)
+ continue;
+
+ binding.Unapply();
+ binding.Apply(BindingContext, this, context.Property);
+ }
+ }
+
+ static void BindingContextPropertyBindingChanging(BindableObject bindable, BindingBase oldBindingBase, BindingBase newBindingBase)
+ {
+ object context = bindable._inheritedContext;
+ var oldBinding = oldBindingBase as Binding;
+ var newBinding = newBindingBase as Binding;
+
+ if (context == null && oldBinding != null)
+ context = oldBinding.Context;
+ if (context != null && newBinding != null)
+ newBinding.Context = context;
+ }
+
+ static void BindingContextPropertyBindingPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ object oldInheritedContext = bindable._inheritedContext;
+ bindable._inheritedContext = null;
+ bindable.ApplyBindings(oldInheritedContext ?? oldvalue, true);
+ bindable.OnBindingContextChanged();
+ }
+
+ void ClearValue(BindableProperty property, bool checkaccess)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ if (checkaccess && property.IsReadOnly)
+ throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
+
+ BindablePropertyContext bpcontext = GetContext(property);
+ if (bpcontext == null)
+ return;
+
+ object original = bpcontext.Value;
+
+ object newValue = property.GetDefaultValue(this);
+
+ bool same = Equals(original, newValue);
+ if (!same)
+ {
+ if (property.PropertyChanging != null)
+ property.PropertyChanging(this, original, newValue);
+
+ OnPropertyChanging(property.PropertyName);
+ }
+
+ bpcontext.Attributes &= ~BindableContextAttributes.IsManuallySet;
+ bpcontext.Value = newValue;
+ bpcontext.Attributes |= BindableContextAttributes.IsDefaultValue;
+
+ if (!same)
+ {
+ OnPropertyChanged(property.PropertyName);
+ if (property.PropertyChanged != null)
+ property.PropertyChanged(this, original, newValue);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ BindablePropertyContext CreateAndAddContext(BindableProperty property)
+ {
+ var context = new BindablePropertyContext { Property = property, Value = property.DefaultValueCreator != null ? property.DefaultValueCreator(this) : property.DefaultValue };
+
+ if (property.DefaultValueCreator != null)
+ context.Attributes = BindableContextAttributes.IsDefaultValue;
+
+ _properties.Add(context);
+ return context;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ BindablePropertyContext GetContext(BindableProperty property)
+ {
+ List<BindablePropertyContext> properties = _properties;
+
+ for (var i = 0; i < properties.Count; i++)
+ {
+ BindablePropertyContext context = properties[i];
+ if (ReferenceEquals(context.Property, property))
+ return context;
+ }
+
+ return null;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ BindablePropertyContext GetOrCreateContext(BindableProperty property)
+ {
+ BindablePropertyContext context = GetContext(property);
+ if (context == null)
+ {
+ context = CreateAndAddContext(property);
+ }
+
+ return context;
+ }
+
+ void RemoveBinding(BindableProperty property, BindablePropertyContext context)
+ {
+ context.Binding.Unapply();
+
+ if (property.BindingChanging != null)
+ property.BindingChanging(this, context.Binding, null);
+
+ context.Binding = null;
+ }
+
+ void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ if (checkAccess && property.IsReadOnly)
+ throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
+
+ BindablePropertyContext context = null;
+ if (fromStyle && (context = GetContext(property)) != null && (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0 &&
+ (context.Attributes & BindableContextAttributes.IsSetFromStyle) == 0)
+ return;
+
+ SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
+ (fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0));
+ }
+
+ void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
+ {
+ object original = context.Value;
+ bool raiseOnEqual = (attributes & SetValueFlags.RaiseOnEqual) != 0;
+ bool clearDynamicResources = (attributes & SetValueFlags.ClearDynamicResource) != 0;
+ bool clearOneWayBindings = (attributes & SetValueFlags.ClearOneWayBindings) != 0;
+ bool clearTwoWayBindings = (attributes & SetValueFlags.ClearTwoWayBindings) != 0;
+
+ bool same = Equals(value, original);
+ if (!silent && (!same || raiseOnEqual))
+ {
+ if (property.PropertyChanging != null)
+ property.PropertyChanging(this, original, value);
+
+ OnPropertyChanging(property.PropertyName);
+ }
+
+ if (!same || raiseOnEqual)
+ {
+ context.Value = value;
+ }
+
+ context.Attributes &= ~BindableContextAttributes.IsDefaultValue;
+
+ if ((context.Attributes & BindableContextAttributes.IsDynamicResource) != 0 && clearDynamicResources)
+ RemoveDynamicResource(property);
+
+ BindingBase binding = context.Binding;
+ if (binding != null)
+ {
+ if (clearOneWayBindings && binding.GetRealizedMode(property) == BindingMode.OneWay || clearTwoWayBindings && binding.GetRealizedMode(property) == BindingMode.TwoWay)
+ {
+ RemoveBinding(property, context);
+ binding = null;
+ }
+ }
+
+ if (!silent && (!same || raiseOnEqual))
+ {
+ if (binding != null && !currentlyApplying)
+ {
+ _applying = true;
+ binding.Apply(true);
+ _applying = false;
+ }
+
+ OnPropertyChanged(property.PropertyName);
+
+ if (property.PropertyChanged != null)
+ property.PropertyChanged(this, original, value);
+ }
+ }
+
+ [Flags]
+ enum BindableContextAttributes
+ {
+ IsManuallySet = 1 << 0,
+ IsBeingSet = 1 << 1,
+ IsDynamicResource = 1 << 2,
+ IsSetFromStyle = 1 << 3,
+ IsDefaultValue = 1 << 4
+ }
+
+ class BindablePropertyContext
+ {
+ public BindableContextAttributes Attributes;
+ public BindingBase Binding;
+ public Queue<SetValueArgs> DelayedSetters;
+ public BindableProperty Property;
+ public object Value;
+ }
+
+ [Flags]
+ internal enum SetValueFlags
+ {
+ None = 0,
+ ClearOneWayBindings = 1 << 0,
+ ClearTwoWayBindings = 1 << 1,
+ ClearDynamicResource = 1 << 2,
+ RaiseOnEqual = 1 << 3
+ }
+
+ [Flags]
+ internal enum SetValuePrivateFlags
+ {
+ None = 0,
+ CheckAccess = 1 << 0,
+ Silent = 1 << 1,
+ ManuallySet = 1 << 2,
+ FromStyle = 1 << 3,
+ Converted = 1 << 4,
+ Default = CheckAccess
+ }
+
+ class SetValueArgs
+ {
+ public readonly SetValueFlags Attributes;
+ public readonly BindablePropertyContext Context;
+ public readonly bool CurrentlyApplying;
+ public readonly BindableProperty Property;
+ public readonly object Value;
+
+ public SetValueArgs(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes)
+ {
+ Property = property;
+ Context = context;
+ Value = value;
+ CurrentlyApplying = currentlyApplying;
+ Attributes = attributes;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindableObjectExtensions.cs b/Xamarin.Forms.Core/BindableObjectExtensions.cs
new file mode 100644
index 00000000..2eab2380
--- /dev/null
+++ b/Xamarin.Forms.Core/BindableObjectExtensions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Xamarin.Forms
+{
+ public static class BindableObjectExtensions
+ {
+ public static void SetBinding(this BindableObject self, BindableProperty targetProperty, string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null,
+ string stringFormat = null)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+ if (targetProperty == null)
+ throw new ArgumentNullException("targetProperty");
+
+ var binding = new Binding(path, mode, converter, stringFormat: stringFormat);
+ self.SetBinding(targetProperty, binding);
+ }
+
+ public static void SetBinding<TSource>(this BindableObject self, BindableProperty targetProperty, Expression<Func<TSource, object>> sourceProperty, BindingMode mode = BindingMode.Default,
+ IValueConverter converter = null, string stringFormat = null)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+ if (targetProperty == null)
+ throw new ArgumentNullException("targetProperty");
+ if (sourceProperty == null)
+ throw new ArgumentNullException("sourceProperty");
+
+ Binding binding = Binding.Create(sourceProperty, mode, converter, stringFormat: stringFormat);
+ self.SetBinding(targetProperty, binding);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindableProperty.cs b/Xamarin.Forms.Core/BindableProperty.cs
new file mode 100644
index 00000000..f95d38ab
--- /dev/null
+++ b/Xamarin.Forms.Core/BindableProperty.cs
@@ -0,0 +1,331 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("{PropertyName}")]
+ [TypeConverter(typeof(BindablePropertyConverter))]
+ public sealed class BindableProperty
+ {
+ public delegate void BindingPropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue);
+
+ public delegate void BindingPropertyChangedDelegate<in TPropertyType>(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue);
+
+ public delegate void BindingPropertyChangingDelegate(BindableObject bindable, object oldValue, object newValue);
+
+ public delegate void BindingPropertyChangingDelegate<in TPropertyType>(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue);
+
+ public delegate object CoerceValueDelegate(BindableObject bindable, object value);
+
+ public delegate TPropertyType CoerceValueDelegate<TPropertyType>(BindableObject bindable, TPropertyType value);
+
+ public delegate object CreateDefaultValueDelegate(BindableObject bindable);
+
+ public delegate TPropertyType CreateDefaultValueDelegate<in TDeclarer, out TPropertyType>(TDeclarer bindable);
+
+ public delegate bool ValidateValueDelegate(BindableObject bindable, object value);
+
+ public delegate bool ValidateValueDelegate<in TPropertyType>(BindableObject bindable, TPropertyType value);
+
+ // more or less the encoding of this, without the need to reflect
+ // http://msdn.microsoft.com/en-us/library/y5b434w4.aspx
+ static readonly Dictionary<Type, Type[]> SimpleConvertTypes = new Dictionary<Type, Type[]>
+ {
+ { typeof(sbyte), new[] { typeof(string), typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(byte), new[] { typeof(string), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(short), new[] { typeof(string), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(ushort), new[] { typeof(string), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(int), new[] { typeof(string), typeof(long), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(uint), new[] { typeof(string), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(long), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(char), new[] { typeof(string), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
+ { typeof(float), new[] { typeof(string), typeof(double) } },
+ { typeof(ulong), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } }
+ };
+
+ BindableProperty(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
+ ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
+ CoerceValueDelegate coerceValue = null, BindablePropertyBindingChanging bindingChanging = null, bool isReadOnly = false, CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ if (propertyName == null)
+ throw new ArgumentNullException("propertyName");
+ if (ReferenceEquals(returnType, null))
+ throw new ArgumentNullException("returnType");
+ if (ReferenceEquals(declaringType, null))
+ throw new ArgumentNullException("declaringType");
+
+ // don't use Enum.IsDefined as its redonkulously expensive for what it does
+ if (defaultBindingMode != BindingMode.Default && defaultBindingMode != BindingMode.OneWay && defaultBindingMode != BindingMode.OneWayToSource && defaultBindingMode != BindingMode.TwoWay)
+ throw new ArgumentException("Not a valid type of BindingMode", "defaultBindingMode");
+ if (defaultValue == null && Nullable.GetUnderlyingType(returnType) == null && returnType.GetTypeInfo().IsValueType)
+ throw new ArgumentException("Not a valid default value", "defaultValue");
+ if (defaultValue != null && !returnType.IsInstanceOfType(defaultValue))
+ throw new ArgumentException("Default value did not match return type", "defaultValue");
+ if (defaultBindingMode == BindingMode.Default)
+ defaultBindingMode = BindingMode.OneWay;
+
+ PropertyName = propertyName;
+ ReturnType = returnType;
+ ReturnTypeInfo = returnType.GetTypeInfo();
+ DeclaringType = declaringType;
+ DefaultValue = defaultValue;
+ DefaultBindingMode = defaultBindingMode;
+ PropertyChanged = propertyChanged;
+ PropertyChanging = propertyChanging;
+ ValidateValue = validateValue;
+ CoerceValue = coerceValue;
+ BindingChanging = bindingChanging;
+ IsReadOnly = isReadOnly;
+ DefaultValueCreator = defaultValueCreator;
+ }
+
+ public Type DeclaringType { get; private set; }
+
+ public BindingMode DefaultBindingMode { get; private set; }
+
+ public object DefaultValue { get; }
+
+ public bool IsReadOnly { get; private set; }
+
+ public string PropertyName { get; }
+
+ public Type ReturnType { get; }
+
+ internal BindablePropertyBindingChanging BindingChanging { get; private set; }
+
+ internal CoerceValueDelegate CoerceValue { get; private set; }
+
+ internal CreateDefaultValueDelegate DefaultValueCreator { get; }
+
+ internal BindingPropertyChangedDelegate PropertyChanged { get; private set; }
+
+ internal BindingPropertyChangingDelegate PropertyChanging { get; private set; }
+
+ internal TypeInfo ReturnTypeInfo { get; }
+
+ internal ValidateValueDelegate ValidateValue { get; private set; }
+
+ [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")]
+ public static BindableProperty Create<TDeclarer, TPropertyType>(Expression<Func<TDeclarer, TPropertyType>> getter, TPropertyType defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
+ ValidateValueDelegate<TPropertyType> validateValue = null, BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null,
+ BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, CoerceValueDelegate<TPropertyType> coerceValue = null,
+ CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject
+ {
+ return Create(getter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, defaultValueCreator: defaultValueCreator);
+ }
+
+ public static BindableProperty Create(string propertyName, Type returnType, Type declaringType, object defaultValue = null, BindingMode defaultBindingMode = BindingMode.OneWay,
+ ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
+ CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue,
+ defaultValueCreator: defaultValueCreator);
+ }
+
+ [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")]
+ public static BindableProperty CreateAttached<TDeclarer, TPropertyType>(Expression<Func<BindableObject, TPropertyType>> staticgetter, TPropertyType defaultValue,
+ BindingMode defaultBindingMode = BindingMode.OneWay, ValidateValueDelegate<TPropertyType> validateValue = null, BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null,
+ BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, CoerceValueDelegate<TPropertyType> coerceValue = null,
+ CreateDefaultValueDelegate<BindableObject, TPropertyType> defaultValueCreator = null)
+ {
+ return CreateAttached<TDeclarer, TPropertyType>(staticgetter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null,
+ defaultValueCreator: defaultValueCreator);
+ }
+
+ public static BindableProperty CreateAttached(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
+ ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
+ CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ return CreateAttached(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, false, defaultValueCreator);
+ }
+
+ [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")]
+ public static BindablePropertyKey CreateAttachedReadOnly<TDeclarer, TPropertyType>(Expression<Func<BindableObject, TPropertyType>> staticgetter, TPropertyType defaultValue,
+ BindingMode defaultBindingMode = BindingMode.OneWayToSource, ValidateValueDelegate<TPropertyType> validateValue = null,
+ BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null,
+ CoerceValueDelegate<TPropertyType> coerceValue = null, CreateDefaultValueDelegate<BindableObject, TPropertyType> defaultValueCreator = null)
+
+ {
+ return
+ new BindablePropertyKey(CreateAttached<TDeclarer, TPropertyType>(staticgetter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true,
+ defaultValueCreator));
+ }
+
+ public static BindablePropertyKey CreateAttachedReadOnly(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource,
+ ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
+ CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ return
+ new BindablePropertyKey(CreateAttached(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true,
+ defaultValueCreator));
+ }
+
+ [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")]
+ public static BindablePropertyKey CreateReadOnly<TDeclarer, TPropertyType>(Expression<Func<TDeclarer, TPropertyType>> getter, TPropertyType defaultValue,
+ BindingMode defaultBindingMode = BindingMode.OneWayToSource, ValidateValueDelegate<TPropertyType> validateValue = null,
+ BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null,
+ CoerceValueDelegate<TPropertyType> coerceValue = null, CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject
+ {
+ return new BindablePropertyKey(Create(getter, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true, defaultValueCreator));
+ }
+
+ public static BindablePropertyKey CreateReadOnly(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource,
+ ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
+ CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ return
+ new BindablePropertyKey(new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue,
+ isReadOnly: true, defaultValueCreator: defaultValueCreator));
+ }
+
+ [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")]
+ internal static BindableProperty Create<TDeclarer, TPropertyType>(Expression<Func<TDeclarer, TPropertyType>> getter, TPropertyType defaultValue, BindingMode defaultBindingMode,
+ ValidateValueDelegate<TPropertyType> validateValue, BindingPropertyChangedDelegate<TPropertyType> propertyChanged, BindingPropertyChangingDelegate<TPropertyType> propertyChanging,
+ CoerceValueDelegate<TPropertyType> coerceValue, BindablePropertyBindingChanging bindingChanging, bool isReadOnly = false,
+ CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject
+ {
+ if (getter == null)
+ throw new ArgumentNullException("getter");
+
+ Expression expr = getter.Body;
+
+ var unary = expr as UnaryExpression;
+ if (unary != null)
+ expr = unary.Operand;
+
+ var member = expr as MemberExpression;
+ if (member == null)
+ throw new ArgumentException("getter must be a MemberExpression", "getter");
+
+ var property = (PropertyInfo)member.Member;
+
+ ValidateValueDelegate untypedValidateValue = null;
+ BindingPropertyChangedDelegate untypedBindingPropertyChanged = null;
+ BindingPropertyChangingDelegate untypedBindingPropertyChanging = null;
+ CoerceValueDelegate untypedCoerceValue = null;
+ CreateDefaultValueDelegate untypedDefaultValueCreator = null;
+ if (validateValue != null)
+ untypedValidateValue = (bindable, value) => validateValue(bindable, (TPropertyType)value);
+ if (propertyChanged != null)
+ untypedBindingPropertyChanged = (bindable, oldValue, newValue) => propertyChanged(bindable, (TPropertyType)oldValue, (TPropertyType)newValue);
+ if (propertyChanging != null)
+ untypedBindingPropertyChanging = (bindable, oldValue, newValue) => propertyChanging(bindable, (TPropertyType)oldValue, (TPropertyType)newValue);
+ if (coerceValue != null)
+ untypedCoerceValue = (bindable, value) => coerceValue(bindable, (TPropertyType)value);
+ if (defaultValueCreator != null)
+ untypedDefaultValueCreator = o => defaultValueCreator((TDeclarer)o);
+
+ return new BindableProperty(property.Name, property.PropertyType, typeof(TDeclarer), defaultValue, defaultBindingMode, untypedValidateValue, untypedBindingPropertyChanged,
+ untypedBindingPropertyChanging, untypedCoerceValue, bindingChanging, isReadOnly, untypedDefaultValueCreator);
+ }
+
+ internal static BindableProperty Create(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
+ BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
+ CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging,
+ defaultValueCreator: defaultValueCreator);
+ }
+
+ [Obsolete("Generic versions of Create () are no longer supported and deprecated. They will be removed soon.")]
+ internal static BindableProperty CreateAttached<TDeclarer, TPropertyType>(Expression<Func<BindableObject, TPropertyType>> staticgetter, TPropertyType defaultValue, BindingMode defaultBindingMode,
+ ValidateValueDelegate<TPropertyType> validateValue, BindingPropertyChangedDelegate<TPropertyType> propertyChanged, BindingPropertyChangingDelegate<TPropertyType> propertyChanging,
+ CoerceValueDelegate<TPropertyType> coerceValue, BindablePropertyBindingChanging bindingChanging, bool isReadOnly = false,
+ CreateDefaultValueDelegate<BindableObject, TPropertyType> defaultValueCreator = null)
+ {
+ if (staticgetter == null)
+ throw new ArgumentNullException("staticgetter");
+
+ Expression expr = staticgetter.Body;
+
+ var unary = expr as UnaryExpression;
+ if (unary != null)
+ expr = unary.Operand;
+
+ var methodcall = expr as MethodCallExpression;
+ if (methodcall == null)
+ throw new ArgumentException("staticgetter must be a MethodCallExpression", "staticgetter");
+
+ MethodInfo method = methodcall.Method;
+ if (!method.Name.StartsWith("Get", StringComparison.Ordinal))
+ throw new ArgumentException("staticgetter name must start with Get", "staticgetter");
+
+ string propertyname = method.Name.Substring(3);
+
+ ValidateValueDelegate untypedValidateValue = null;
+ BindingPropertyChangedDelegate untypedBindingPropertyChanged = null;
+ BindingPropertyChangingDelegate untypedBindingPropertyChanging = null;
+ CoerceValueDelegate untypedCoerceValue = null;
+ CreateDefaultValueDelegate untypedDefaultValueCreator = null;
+ if (validateValue != null)
+ untypedValidateValue = (bindable, value) => validateValue(bindable, (TPropertyType)value);
+ if (propertyChanged != null)
+ untypedBindingPropertyChanged = (bindable, oldValue, newValue) => propertyChanged(bindable, (TPropertyType)oldValue, (TPropertyType)newValue);
+ if (propertyChanging != null)
+ untypedBindingPropertyChanging = (bindable, oldValue, newValue) => propertyChanging(bindable, (TPropertyType)oldValue, (TPropertyType)newValue);
+ if (coerceValue != null)
+ untypedCoerceValue = (bindable, value) => coerceValue(bindable, (TPropertyType)value);
+ if (defaultValueCreator != null)
+ untypedDefaultValueCreator = o => defaultValueCreator(o);
+
+ return new BindableProperty(propertyname, method.ReturnType, typeof(TDeclarer), defaultValue, defaultBindingMode, untypedValidateValue, untypedBindingPropertyChanged, untypedBindingPropertyChanging,
+ untypedCoerceValue, bindingChanging, isReadOnly, untypedDefaultValueCreator);
+ }
+
+ internal static BindableProperty CreateAttached(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
+ BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
+ bool isReadOnly, CreateDefaultValueDelegate defaultValueCreator = null)
+ {
+ return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging, isReadOnly,
+ defaultValueCreator);
+ }
+
+ internal object GetDefaultValue(BindableObject bindable)
+ {
+ if (DefaultValueCreator != null)
+ return DefaultValueCreator(bindable);
+
+ return DefaultValue;
+ }
+
+ internal bool TryConvert(ref object value)
+ {
+ if (value == null)
+ {
+ return !ReturnTypeInfo.IsValueType || ReturnTypeInfo.IsGenericType && ReturnTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+
+ Type valueType = value.GetType();
+ Type type = ReturnType;
+
+ // Dont support arbitrary IConvertible by limiting which types can use this
+ Type[] convertableTo;
+ if (SimpleConvertTypes.TryGetValue(valueType, out convertableTo) && Array.IndexOf(convertableTo, type) != -1)
+ {
+ value = Convert.ChangeType(value, type);
+ }
+ else if (!ReturnTypeInfo.IsAssignableFrom(valueType.GetTypeInfo()))
+ {
+ // Is there an implicit cast operator ?
+ MethodInfo cast = type.GetRuntimeMethod("op_Implicit", new[] { valueType });
+ if (cast != null && cast.ReturnType != type)
+ cast = null;
+ if (cast == null)
+ cast = valueType.GetRuntimeMethod("op_Implicit", new[] { valueType });
+ if (cast != null && cast.ReturnType != type)
+ cast = null;
+ if (cast == null)
+ return false;
+
+ value = cast.Invoke(null, new[] { value });
+ }
+
+ return true;
+ }
+
+ internal delegate void BindablePropertyBindingChanging(BindableObject bindable, BindingBase oldValue, BindingBase newValue);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindablePropertyConverter.cs b/Xamarin.Forms.Core/BindablePropertyConverter.cs
new file mode 100644
index 00000000..af3fd5b6
--- /dev/null
+++ b/Xamarin.Forms.Core/BindablePropertyConverter.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ public sealed class BindablePropertyConverter : TypeConverter, IExtendedTypeConverter
+ {
+ object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
+ {
+ return ((IExtendedTypeConverter)this).ConvertFromInvariantString(value as string, serviceProvider);
+ }
+
+ object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return null;
+ if (serviceProvider == null)
+ return null;
+ var parentValuesProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideParentValues;
+ var typeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
+ if (typeResolver == null)
+ return null;
+ IXmlLineInfo lineinfo = null;
+ var xmlLineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ if (xmlLineInfoProvider != null)
+ lineinfo = xmlLineInfoProvider.XmlLineInfo;
+ string[] parts = value.Split('.');
+ Type type = null;
+ if (parts.Length == 1)
+ {
+ if (parentValuesProvider == null)
+ {
+ string msg = string.Format("Can't resolve {0}", parts[0]);
+ throw new XamlParseException(msg, lineinfo);
+ }
+ object parent = parentValuesProvider.ParentObjects.Skip(1).FirstOrDefault();
+ if (parentValuesProvider.TargetObject is Setter)
+ {
+ var style = parent as Style;
+ var triggerBase = parent as TriggerBase;
+ if (style != null)
+ type = style.TargetType;
+ else if (triggerBase != null)
+ type = triggerBase.TargetType;
+ }
+ else if (parentValuesProvider.TargetObject is Trigger)
+ type = (parentValuesProvider.TargetObject as Trigger).TargetType;
+
+ if (type == null)
+ {
+ string msg = string.Format("Can't resolve {0}", parts[0]);
+ throw new XamlParseException(msg, lineinfo);
+ }
+
+ return ConvertFrom(type, parts[0], lineinfo);
+ }
+ if (parts.Length == 2)
+ {
+ if (!typeResolver.TryResolve(parts[0], out type))
+ {
+ string msg = string.Format("Can't resolve {0}", parts[0]);
+ throw new XamlParseException(msg, lineinfo);
+ }
+ return ConvertFrom(type, parts[1], lineinfo);
+ }
+ string emsg = string.Format("Can't resolve {0}. Syntax is [[ns:]Type.]PropertyName.", value);
+ throw new XamlParseException(emsg, lineinfo);
+ }
+
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return null;
+ if (value.Contains(":"))
+ {
+ Log.Warning(null, "Can't resolve properties with xml namespace prefix.");
+ return null;
+ }
+ string[] parts = value.Split('.');
+ if (parts.Length != 2)
+ {
+ Log.Warning(null, "Can't resolve {0}. Accepted syntax is Type.PropertyName.", value);
+ return null;
+ }
+ Type type = Type.GetType("Xamarin.Forms." + parts[0]);
+ return ConvertFrom(type, parts[1], null);
+ }
+
+ BindableProperty ConvertFrom(Type type, string propertyName, IXmlLineInfo lineinfo)
+ {
+ string name = propertyName + "Property";
+ FieldInfo bpinfo = type.GetField(fi => fi.Name == name && fi.IsStatic && fi.IsPublic && fi.FieldType == typeof(BindableProperty));
+ if (bpinfo == null)
+ throw new XamlParseException(string.Format("Can't resolve {0} on {1}", name, type.Name), lineinfo);
+ var bp = bpinfo.GetValue(null) as BindableProperty;
+ if (bp.PropertyName != propertyName)
+ throw new XamlParseException(string.Format("The PropertyName of {0}.{1} is not {2}", type.Name, name, propertyName), lineinfo);
+ return bp;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindablePropertyKey.cs b/Xamarin.Forms.Core/BindablePropertyKey.cs
new file mode 100644
index 00000000..a4b9f4a4
--- /dev/null
+++ b/Xamarin.Forms.Core/BindablePropertyKey.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class BindablePropertyKey
+ {
+ internal BindablePropertyKey(BindableProperty property)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ BindableProperty = property;
+ }
+
+ public BindableProperty BindableProperty { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs
new file mode 100644
index 00000000..5fa1dd65
--- /dev/null
+++ b/Xamarin.Forms.Core/Binding.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace Xamarin.Forms
+{
+ public sealed class Binding : BindingBase
+ {
+ internal const string SelfPath = ".";
+ IValueConverter _converter;
+ object _converterParameter;
+
+ BindingExpression _expression;
+ string _path;
+ object _source;
+
+ public Binding()
+ {
+ }
+
+ public Binding(string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, string stringFormat = null, object source = null)
+ {
+ if (path == null)
+ throw new ArgumentNullException("path");
+ if (string.IsNullOrWhiteSpace(path))
+ throw new ArgumentException("path can not be an empty string", "path");
+
+ Path = path;
+ Converter = converter;
+ ConverterParameter = converterParameter;
+ Mode = mode;
+ StringFormat = stringFormat;
+ Source = source;
+ }
+
+ public IValueConverter Converter
+ {
+ get { return _converter; }
+ set
+ {
+ ThrowIfApplied();
+
+ _converter = value;
+ }
+ }
+
+ public object ConverterParameter
+ {
+ get { return _converterParameter; }
+ set
+ {
+ ThrowIfApplied();
+
+ _converterParameter = value;
+ }
+ }
+
+ public string Path
+ {
+ get { return _path; }
+ set
+ {
+ ThrowIfApplied();
+
+ _path = value;
+ _expression = new BindingExpression(this, !string.IsNullOrWhiteSpace(value) ? value : SelfPath);
+ }
+ }
+
+ public object Source
+ {
+ get { return _source; }
+ set
+ {
+ ThrowIfApplied();
+ _source = value;
+ }
+ }
+
+ public static Binding Create<TSource>(Expression<Func<TSource, object>> propertyGetter, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null,
+ string stringFormat = null)
+ {
+ if (propertyGetter == null)
+ throw new ArgumentNullException("propertyGetter");
+
+ string path = GetBindingPath(propertyGetter);
+ return new Binding(path, mode, converter, converterParameter, stringFormat);
+ }
+
+ internal override void Apply(bool fromTarget)
+ {
+ base.Apply(fromTarget);
+
+ if (_expression == null)
+ _expression = new BindingExpression(this, SelfPath);
+
+ _expression.Apply(fromTarget);
+ }
+
+ internal override void Apply(object newContext, BindableObject bindObj, BindableProperty targetProperty)
+ {
+ object src = _source;
+ base.Apply(src ?? newContext, bindObj, targetProperty);
+
+ object bindingContext = src ?? Context ?? newContext;
+ if (_expression == null && bindingContext != null)
+ _expression = new BindingExpression(this, SelfPath);
+
+ _expression.Apply(bindingContext, bindObj, targetProperty);
+ }
+
+ internal override BindingBase Clone()
+ {
+ return new Binding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat, Source = Source };
+ }
+
+ internal override object GetSourceValue(object value, Type targetPropertyType)
+ {
+ if (Converter != null)
+ value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ return base.GetSourceValue(value, targetPropertyType);
+ }
+
+ internal override object GetTargetValue(object value, Type sourcePropertyType)
+ {
+ if (Converter != null)
+ value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ return base.GetTargetValue(value, sourcePropertyType);
+ }
+
+ internal override void Unapply()
+ {
+ base.Unapply();
+
+ if (_expression != null)
+ _expression.Unapply();
+ }
+
+ static string GetBindingPath<TSource>(Expression<Func<TSource, object>> propertyGetter)
+ {
+ Expression expr = propertyGetter.Body;
+
+ var unary = expr as UnaryExpression;
+ if (unary != null)
+ expr = unary.Operand;
+
+ var builder = new StringBuilder();
+
+ var indexed = false;
+
+ var member = expr as MemberExpression;
+ if (member == null)
+ {
+ var methodCall = expr as MethodCallExpression;
+ if (methodCall != null)
+ {
+ if (methodCall.Arguments.Count == 0)
+ throw new ArgumentException("Method calls are not allowed in binding expression");
+
+ var arguments = new List<string>(methodCall.Arguments.Count);
+ foreach (Expression arg in methodCall.Arguments)
+ {
+ if (arg.NodeType != ExpressionType.Constant)
+ throw new ArgumentException("Only constants can be used as indexer arguments");
+
+ object value = ((ConstantExpression)arg).Value;
+ arguments.Add(value != null ? value.ToString() : "null");
+ }
+
+ Type declarerType = methodCall.Method.DeclaringType;
+ DefaultMemberAttribute defaultMember = declarerType.GetTypeInfo().GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault();
+ string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
+
+ MethodInfo getterInfo =
+ declarerType.GetProperties().Where(pi => pi.Name == indexerName && pi.CanRead && pi.GetMethod.IsPublic && !pi.GetMethod.IsStatic).Select(pi => pi.GetMethod).FirstOrDefault();
+ if (getterInfo != null)
+ {
+ if (getterInfo == methodCall.Method)
+ {
+ indexed = true;
+ builder.Append("[");
+
+ var first = true;
+ foreach (string argument in arguments)
+ {
+ if (!first)
+ builder.Append(",");
+
+ builder.Append(argument);
+ first = false;
+ }
+
+ builder.Append("]");
+
+ member = methodCall.Object as MemberExpression;
+ }
+ else
+ throw new ArgumentException("Method calls are not allowed in binding expressions");
+ }
+ else
+ throw new ArgumentException("Public indexer not found");
+ }
+ else
+ throw new ArgumentException("Invalid expression type");
+ }
+
+ while (member != null)
+ {
+ var property = (PropertyInfo)member.Member;
+ if (builder.Length != 0)
+ {
+ if (!indexed)
+ builder.Insert(0, ".");
+ else
+ indexed = false;
+ }
+
+ builder.Insert(0, property.Name);
+
+ // member = member.Expression as MemberExpression ?? (member.Expression as UnaryExpression)?.Operand as MemberExpression;
+ member = member.Expression as MemberExpression ?? (member.Expression is UnaryExpression ? (member.Expression as UnaryExpression).Operand as MemberExpression : null);
+ }
+
+ return builder.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindingBase.cs b/Xamarin.Forms.Core/BindingBase.cs
new file mode 100644
index 00000000..0810cbcf
--- /dev/null
+++ b/Xamarin.Forms.Core/BindingBase.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ public abstract class BindingBase
+ {
+ static readonly ConditionalWeakTable<IEnumerable, CollectionSynchronizationContext> SynchronizedCollections = new ConditionalWeakTable<IEnumerable, CollectionSynchronizationContext>();
+
+ BindingMode _mode = BindingMode.Default;
+ string _stringFormat;
+
+ internal BindingBase()
+ {
+ }
+
+ public BindingMode Mode
+ {
+ get { return _mode; }
+ set
+ {
+ if (value != BindingMode.Default && value != BindingMode.OneWay && value != BindingMode.OneWayToSource && value != BindingMode.TwoWay)
+ throw new ArgumentException("mode is not a valid BindingMode", "mode");
+
+ ThrowIfApplied();
+
+ _mode = value;
+ }
+ }
+
+ public string StringFormat
+ {
+ get { return _stringFormat; }
+ set
+ {
+ ThrowIfApplied();
+
+ _stringFormat = value;
+ }
+ }
+
+ internal bool AllowChaining { get; set; }
+
+ internal object Context { get; set; }
+
+ internal bool IsApplied { get; private set; }
+
+ public static void DisableCollectionSynchronization(IEnumerable collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException("collection");
+
+ SynchronizedCollections.Remove(collection);
+ }
+
+ public static void EnableCollectionSynchronization(IEnumerable collection, object context, CollectionSynchronizationCallback callback)
+ {
+ if (collection == null)
+ throw new ArgumentNullException("collection");
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ SynchronizedCollections.Add(collection, new CollectionSynchronizationContext(context, callback));
+ }
+
+ protected void ThrowIfApplied()
+ {
+ if (IsApplied)
+ throw new InvalidOperationException("Can not change a binding while it's applied");
+ }
+
+ internal virtual void Apply(bool fromTarget)
+ {
+ IsApplied = true;
+ }
+
+ internal virtual void Apply(object context, BindableObject bindObj, BindableProperty targetProperty)
+ {
+ IsApplied = true;
+ }
+
+ internal abstract BindingBase Clone();
+
+ internal virtual object GetSourceValue(object value, Type targetPropertyType)
+ {
+ if (StringFormat != null)
+ return string.Format(StringFormat, value);
+
+ return value;
+ }
+
+ internal virtual object GetTargetValue(object value, Type sourcePropertyType)
+ {
+ return value;
+ }
+
+ internal static bool TryGetSynchronizedCollection(IEnumerable collection, out CollectionSynchronizationContext synchronizationContext)
+ {
+ if (collection == null)
+ throw new ArgumentNullException("collection");
+
+ return SynchronizedCollections.TryGetValue(collection, out synchronizationContext);
+ }
+
+ internal virtual void Unapply()
+ {
+ IsApplied = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindingBaseExtensions.cs b/Xamarin.Forms.Core/BindingBaseExtensions.cs
new file mode 100644
index 00000000..a52c5cb1
--- /dev/null
+++ b/Xamarin.Forms.Core/BindingBaseExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal static class BindingBaseExtensions
+ {
+ internal static BindingMode GetRealizedMode(this BindingBase self, BindableProperty property)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ return self.Mode != BindingMode.Default ? self.Mode : property.DefaultBindingMode;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs
new file mode 100644
index 00000000..505fc584
--- /dev/null
+++ b/Xamarin.Forms.Core/BindingExpression.cs
@@ -0,0 +1,506 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ internal class BindingExpression
+ {
+ internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'";
+
+ readonly List<BindingExpressionPart> _parts = new List<BindingExpressionPart>();
+
+ BindableProperty _targetProperty;
+ WeakReference<object> _weakSource;
+ WeakReference<BindableObject> _weakTarget;
+
+ internal BindingExpression(BindingBase binding, string path)
+ {
+ if (binding == null)
+ throw new ArgumentNullException("binding");
+ if (path == null)
+ throw new ArgumentNullException("path");
+
+ Binding = binding;
+ Path = path;
+
+ ParsePath();
+ }
+
+ internal BindingBase Binding { get; }
+
+ internal string Path { get; }
+
+ /// <summary>
+ /// Applies the binding expression to a previously set source and target.
+ /// </summary>
+ internal void Apply(bool fromTarget = false)
+ {
+ if (_weakSource == null || _weakTarget == null)
+ return;
+
+ BindableObject target;
+ if (!_weakTarget.TryGetTarget(out target))
+ {
+ Unapply();
+ return;
+ }
+
+ object source;
+ if (_weakSource.TryGetTarget(out source) && _targetProperty != null)
+ ApplyCore(source, target, _targetProperty, fromTarget);
+ }
+
+ /// <summary>
+ /// Applies the binding expression to a new source or target.
+ /// </summary>
+ internal void Apply(object sourceObject, BindableObject target, BindableProperty property)
+ {
+ _targetProperty = property;
+
+ BindableObject prevTarget;
+ if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target))
+ throw new InvalidOperationException("Binding instances can not be reused");
+
+ object previousSource;
+ if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject))
+ throw new InvalidOperationException("Binding instances can not be reused");
+
+ _weakSource = new WeakReference<object>(sourceObject);
+ _weakTarget = new WeakReference<BindableObject>(target);
+
+ ApplyCore(sourceObject, target, property);
+ }
+
+ internal void Unapply()
+ {
+ object sourceObject;
+ if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject))
+ {
+ for (var i = 0; i < _parts.Count - 1; i++)
+ {
+ BindingExpressionPart part = _parts[i];
+
+ if (!part.IsSelf)
+ {
+ part.TryGetValue(sourceObject, out sourceObject);
+ }
+
+ var inpc = sourceObject as INotifyPropertyChanged;
+ if (inpc != null)
+ inpc.PropertyChanged -= part.ChangeHandler;
+ }
+ }
+
+ _weakSource = null;
+ _weakTarget = null;
+ }
+
+ /// <summary>
+ /// Applies the binding expression to a previously set source or target.
+ /// </summary>
+ void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
+ {
+ BindingMode mode = Binding.GetRealizedMode(_targetProperty);
+ if (mode == BindingMode.OneWay && fromTarget)
+ return;
+
+ bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay;
+ bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource);
+
+ object current = sourceObject;
+ object previous = null;
+ BindingExpressionPart part = null;
+
+ for (var i = 0; i < _parts.Count; i++)
+ {
+ part = _parts[i];
+ bool isLast = i + 1 == _parts.Count;
+
+ if (!part.IsSelf && current != null)
+ {
+ // Allow the object instance itself to provide its own TypeInfo
+ var reflectable = current as IReflectableType;
+ TypeInfo currentType = reflectable != null ? reflectable.GetTypeInfo() : current.GetType().GetTypeInfo();
+ if (part.LastGetter == null || !part.LastGetter.DeclaringType.GetTypeInfo().IsAssignableFrom(currentType))
+ SetupPart(currentType, part);
+
+ if (!isLast)
+ part.TryGetValue(current, out current);
+ }
+
+ if (!part.IsSelf && current != null)
+ {
+ if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null))
+ {
+ Log.Warning("Binding", PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName);
+ break;
+ }
+ }
+
+ if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
+ {
+ var inpc = current as INotifyPropertyChanged;
+ if (inpc != null && !ReferenceEquals(current, previous))
+ {
+ // If we're reapplying, we don't want to double subscribe
+ inpc.PropertyChanged -= part.ChangeHandler;
+ inpc.PropertyChanged += part.ChangeHandler;
+ }
+ }
+
+ previous = current;
+ }
+
+ Debug.Assert(part != null, "There should always be at least the self part in the expression.");
+
+ if (needsGetter)
+ {
+ object value = property.DefaultValue;
+ if (part.TryGetValue(current, out value) || part.IsSelf)
+ {
+ value = Binding.GetSourceValue(value, property.ReturnType);
+ }
+ else
+ value = property.DefaultValue;
+
+ if (!TryConvert(part, ref value, property.ReturnType, true))
+ {
+ Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
+ return;
+ }
+
+ target.SetValueCore(property, value, BindableObject.SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
+ }
+ else if (needsSetter && part.LastSetter != null && current != null)
+ {
+ object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType);
+
+ if (!TryConvert(part, ref value, part.SetterType, false))
+ {
+ Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, part.SetterType);
+ return;
+ }
+
+ object[] args;
+ if (part.IsIndexer)
+ {
+ args = new object[part.Arguments.Length + 1];
+ part.Arguments.CopyTo(args, 0);
+ args[args.Length - 1] = value;
+ }
+ else if (part.IsBindablePropertySetter)
+ {
+ args = new[] { part.BindablePropertyField, value };
+ }
+ else
+ {
+ args = new[] { value };
+ }
+
+ part.LastSetter.Invoke(current, args);
+ }
+ }
+
+ IEnumerable<BindingExpressionPart> GetPart(string part)
+ {
+ part = part.Trim();
+ if (part == string.Empty)
+ throw new FormatException("Path contains an empty part");
+
+ BindingExpressionPart indexer = null;
+
+ int lbIndex = part.IndexOf('[');
+ if (lbIndex != -1)
+ {
+ int rbIndex = part.LastIndexOf(']');
+ if (rbIndex == -1)
+ throw new FormatException("Indexer did not contain closing bracket");
+
+ int argLength = rbIndex - lbIndex - 1;
+ if (argLength == 0)
+ throw new FormatException("Indexer did not contain arguments");
+
+ string argString = part.Substring(lbIndex + 1, argLength);
+ indexer = new BindingExpressionPart(this, argString, true);
+
+ part = part.Substring(0, lbIndex);
+ part = part.Trim();
+ }
+
+ if (part.Length > 0)
+ yield return new BindingExpressionPart(this, part);
+ if (indexer != null)
+ yield return indexer;
+ }
+
+ void ParsePath()
+ {
+ string p = Path.Trim();
+
+ var last = new BindingExpressionPart(this, ".");
+ _parts.Add(last);
+
+ if (p[0] == '.')
+ {
+ if (p.Length == 1)
+ return;
+
+ p = p.Substring(1);
+ }
+
+ string[] pathParts = p.Split('.');
+ for (var i = 0; i < pathParts.Length; i++)
+ {
+ foreach (BindingExpressionPart part in GetPart(pathParts[i]))
+ {
+ last.NextPart = part;
+ _parts.Add(part);
+ last = part;
+ }
+ }
+ }
+
+ void SetupPart(TypeInfo sourceType, BindingExpressionPart part)
+ {
+ part.Arguments = null;
+ part.LastGetter = null;
+ part.LastSetter = null;
+
+ PropertyInfo property = null;
+ if (part.IsIndexer)
+ {
+ if (sourceType.IsArray)
+ {
+ int index;
+ if (!int.TryParse(part.Content, out index))
+ Log.Warning("Binding", "{0} could not be parsed as an index for a {1}", part.Content, sourceType);
+ else
+ part.Arguments = new object[] { index };
+
+ part.LastGetter = sourceType.GetDeclaredMethod("Get");
+ part.LastSetter = sourceType.GetDeclaredMethod("Set");
+ part.SetterType = sourceType.GetElementType();
+ }
+
+ DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault();
+ string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
+
+ part.IndexerName = indexerName;
+
+ property = sourceType.GetDeclaredProperty(indexerName);
+ if (property == null)
+ property = sourceType.BaseType.GetProperty(indexerName);
+
+ if (property != null)
+ {
+ ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault();
+ if (parameter != null)
+ {
+ try
+ {
+ object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture);
+ part.Arguments = new[] { arg };
+ }
+ catch (FormatException)
+ {
+ }
+ catch (InvalidCastException)
+ {
+ }
+ catch (OverflowException)
+ {
+ }
+ }
+ }
+ }
+ else
+ {
+ property = sourceType.GetDeclaredProperty(part.Content);
+ if (property == null)
+ property = sourceType.BaseType.GetProperty(part.Content);
+ }
+
+ if (property != null)
+ {
+ if (property.CanRead && property.GetMethod.IsPublic && !property.GetMethod.IsStatic)
+ part.LastGetter = property.GetMethod;
+ if (property.CanWrite && property.SetMethod.IsPublic && !property.SetMethod.IsStatic)
+ {
+ part.LastSetter = property.SetMethod;
+ part.SetterType = part.LastSetter.GetParameters().Last().ParameterType;
+
+ if (Binding.AllowChaining)
+ {
+ FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property");
+ if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController)))
+ {
+ MethodInfo setValueMethod = null;
+ foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods())
+ {
+ if (m.Name.EndsWith("IElementController.SetValueFromRenderer"))
+ {
+ ParameterInfo[] parameters = m.GetParameters();
+ if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty))
+ {
+ setValueMethod = m;
+ break;
+ }
+ }
+ }
+ if (setValueMethod != null)
+ {
+ part.LastSetter = setValueMethod;
+ part.IsBindablePropertySetter = true;
+ part.BindablePropertyField = bindablePropertyField.GetValue(null);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ bool TryConvert(BindingExpressionPart part, ref object value, Type convertTo, bool toTarget)
+ {
+ if (value == null)
+ return true;
+ if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
+ return true;
+
+ object original = value;
+ try
+ {
+ value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
+ return true;
+ }
+ catch (InvalidCastException)
+ {
+ value = original;
+ return false;
+ }
+ catch (FormatException)
+ {
+ value = original;
+ return false;
+ }
+ catch (OverflowException)
+ {
+ value = original;
+ return false;
+ }
+ }
+
+ class BindingPair
+ {
+ public BindingPair(BindingExpressionPart part, object source, bool isLast)
+ {
+ Part = part;
+ Source = source;
+ IsLast = isLast;
+ }
+
+ public bool IsLast { get; set; }
+
+ public BindingExpressionPart Part { get; private set; }
+
+ public object Source { get; private set; }
+ }
+
+ class BindingExpressionPart
+ {
+ readonly BindingExpression _expression;
+
+ public readonly PropertyChangedEventHandler ChangeHandler;
+
+ public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false)
+ {
+ _expression = expression;
+ IsSelf = content == Forms.Binding.SelfPath;
+ Content = content;
+ IsIndexer = isIndexer;
+
+ ChangeHandler = PropertyChanged;
+ }
+
+ public object[] Arguments { get; set; }
+
+ public object BindablePropertyField { get; set; }
+
+ public string Content { get; }
+
+ public string IndexerName { get; set; }
+
+ public bool IsBindablePropertySetter { get; set; }
+
+ public bool IsIndexer { get; }
+
+ public bool IsSelf { get; }
+
+ public MethodInfo LastGetter { get; set; }
+
+ public MethodInfo LastSetter { get; set; }
+
+ public BindingExpressionPart NextPart { get; set; }
+
+ public Type SetterType { get; set; }
+
+ public void PropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ BindingExpressionPart part = NextPart ?? this;
+
+ string name = args.PropertyName;
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ if (part.IsIndexer)
+ {
+ if (name.Contains("["))
+ {
+ if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content))
+ return;
+ }
+ else if (name != part.IndexerName)
+ return;
+ }
+ else if (name != part.Content)
+ {
+ return;
+ }
+ }
+
+ Device.BeginInvokeOnMainThread(() => _expression.Apply());
+ }
+
+ public bool TryGetValue(object source, out object value)
+ {
+ value = source;
+
+ if (LastGetter != null && value != null)
+ {
+ if (IsIndexer)
+ {
+ try
+ {
+ value = LastGetter.Invoke(value, Arguments);
+ }
+ catch (TargetInvocationException ex)
+ {
+ if (!(ex.InnerException is KeyNotFoundException))
+ throw;
+ value = null;
+ }
+ return true;
+ }
+ value = LastGetter.Invoke(value, Arguments);
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindingMode.cs b/Xamarin.Forms.Core/BindingMode.cs
new file mode 100644
index 00000000..89396acb
--- /dev/null
+++ b/Xamarin.Forms.Core/BindingMode.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum BindingMode
+ {
+ Default,
+ TwoWay,
+ OneWay,
+ OneWayToSource
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BindingTypeConverter.cs b/Xamarin.Forms.Core/BindingTypeConverter.cs
new file mode 100644
index 00000000..07dda896
--- /dev/null
+++ b/Xamarin.Forms.Core/BindingTypeConverter.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public sealed class BindingTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ return new Binding(value);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BoundsConstraint.cs b/Xamarin.Forms.Core/BoundsConstraint.cs
new file mode 100644
index 00000000..43be86eb
--- /dev/null
+++ b/Xamarin.Forms.Core/BoundsConstraint.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Xamarin.Forms
+{
+ public class BoundsConstraint
+ {
+ Func<Rectangle> _measureFunc;
+
+ BoundsConstraint()
+ {
+ }
+
+ internal IEnumerable<View> RelativeTo { get; set; }
+
+ public static BoundsConstraint FromExpression(Expression<Func<Rectangle>> expression, IEnumerable<View> parents = null)
+ {
+ Func<Rectangle> compiled = expression.Compile();
+ var result = new BoundsConstraint
+ {
+ _measureFunc = compiled,
+ RelativeTo = parents ?? ExpressionSearch.Default.FindObjects<View>(expression).ToArray() // make sure we have our own copy
+ };
+
+ return result;
+ }
+
+ internal Rectangle Compute()
+ {
+ return _measureFunc();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BoundsTypeConverter.cs b/Xamarin.Forms.Core/BoundsTypeConverter.cs
new file mode 100644
index 00000000..549b7b63
--- /dev/null
+++ b/Xamarin.Forms.Core/BoundsTypeConverter.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class BoundsTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ double x = -1, y = -1, w = -1, h = -1;
+ string[] xywh = value.Split(',');
+ bool hasX, hasY, hasW, hasH;
+
+ hasX = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x);
+ hasY = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y);
+ hasW = xywh.Length == 4 && double.TryParse(xywh[2], NumberStyles.Number, CultureInfo.InvariantCulture, out w);
+ hasH = xywh.Length == 4 && double.TryParse(xywh[3], NumberStyles.Number, CultureInfo.InvariantCulture, out h);
+
+ if (!hasW && xywh.Length == 4 && string.Compare("AutoSize", xywh[2].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ hasW = true;
+ w = AbsoluteLayout.AutoSize;
+ }
+
+ if (!hasH && xywh.Length == 4 && string.Compare("AutoSize", xywh[3].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ hasH = true;
+ h = AbsoluteLayout.AutoSize;
+ }
+
+ if (hasX && hasY && xywh.Length == 2)
+ return new Rectangle(x, y, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize);
+ if (hasX && hasY && hasW && hasH && xywh.Length == 4)
+ return new Rectangle(x, y, w, h);
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Rectangle)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/BoxView.cs b/Xamarin.Forms.Core/BoxView.cs
new file mode 100644
index 00000000..79fef390
--- /dev/null
+++ b/Xamarin.Forms.Core/BoxView.cs
@@ -0,0 +1,23 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_BoxViewRenderer))]
+ public class BoxView : View
+ {
+ public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof(Color), typeof(BoxView), Color.Default);
+
+ public Color Color
+ {
+ get { return (Color)GetValue(ColorProperty); }
+ set { SetValue(ColorProperty, value); }
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ return new SizeRequest(new Size(40, 40));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Button.cs b/Xamarin.Forms.Core/Button.cs
new file mode 100644
index 00000000..4662105f
--- /dev/null
+++ b/Xamarin.Forms.Core/Button.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Windows.Input;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_ButtonRenderer))]
+ public class Button : View, IFontElement, IButtonController
+ {
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(Button), null, propertyChanged: (bo, o, n) => ((Button)bo).OnCommandChanged());
+
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(Button), null,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).CommandCanExecuteChanged(bindable, EventArgs.Empty));
+
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Button), null,
+ propertyChanged: (bindable, oldVal, newVal) => ((Button)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged));
+
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Button), Color.Default);
+
+ public static readonly BindableProperty FontProperty = BindableProperty.Create("Font", typeof(Font), typeof(Button), default(Font), propertyChanged: FontStructPropertyChanged);
+
+ public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Button), default(string), propertyChanged: SpecificFontPropertyChanged);
+
+ public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Button), -1.0, propertyChanged: SpecificFontPropertyChanged,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Button)bindable));
+
+ public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Button), FontAttributes.None,
+ propertyChanged: SpecificFontPropertyChanged);
+
+ public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create("BorderWidth", typeof(double), typeof(Button), 0d);
+
+ public static readonly BindableProperty BorderColorProperty = BindableProperty.Create("BorderColor", typeof(Color), typeof(Button), Color.Default);
+
+ public static readonly BindableProperty BorderRadiusProperty = BindableProperty.Create("BorderRadius", typeof(int), typeof(Button), 5);
+
+ public static readonly BindableProperty ImageProperty = BindableProperty.Create("Image", typeof(FileImageSource), typeof(Button), default(FileImageSource),
+ propertyChanging: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue));
+
+ bool _cancelEvents;
+
+ public Color BorderColor
+ {
+ get { return (Color)GetValue(BorderColorProperty); }
+ set { SetValue(BorderColorProperty, value); }
+ }
+
+ public int BorderRadius
+ {
+ get { return (int)GetValue(BorderRadiusProperty); }
+ set { SetValue(BorderRadiusProperty, value); }
+ }
+
+ public double BorderWidth
+ {
+ get { return (double)GetValue(BorderWidthProperty); }
+ set { SetValue(BorderWidthProperty, value); }
+ }
+
+ public ICommand Command
+ {
+ get { return (ICommand)GetValue(CommandProperty); }
+ set { SetValue(CommandProperty, value); }
+ }
+
+ public object CommandParameter
+ {
+ get { return GetValue(CommandParameterProperty); }
+ set { SetValue(CommandParameterProperty, value); }
+ }
+
+ public Font Font
+ {
+ get { return (Font)GetValue(FontProperty); }
+ set { SetValue(FontProperty, value); }
+ }
+
+ public FileImageSource Image
+ {
+ get { return (FileImageSource)GetValue(ImageProperty); }
+ set { SetValue(ImageProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ bool IsEnabledCore
+ {
+ set { SetValueCore(IsEnabledProperty, value); }
+ }
+
+ void IButtonController.SendClicked()
+ {
+ ICommand cmd = Command;
+ if (cmd != null)
+ cmd.Execute(CommandParameter);
+
+ EventHandler handler = Clicked;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ public event EventHandler Clicked;
+
+ protected override void OnBindingContextChanged()
+ {
+ FileImageSource image = Image;
+ if (image != null)
+ SetInheritedBindingContext(image, BindingContext);
+
+ base.OnBindingContextChanged();
+ }
+
+ protected override void OnPropertyChanging(string propertyName = null)
+ {
+ if (propertyName == CommandProperty.PropertyName)
+ {
+ ICommand cmd = Command;
+ if (cmd != null)
+ cmd.CanExecuteChanged -= CommandCanExecuteChanged;
+ }
+
+ base.OnPropertyChanging(propertyName);
+ }
+
+ void CommandCanExecuteChanged(object sender, EventArgs eventArgs)
+ {
+ ICommand cmd = Command;
+ if (cmd != null)
+ IsEnabledCore = cmd.CanExecute(CommandParameter);
+ }
+
+ static void FontStructPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var button = (Button)bindable;
+
+ if (button._cancelEvents)
+ return;
+
+ button.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+
+ button._cancelEvents = true;
+
+ if (button.Font == Font.Default)
+ {
+ button.FontFamily = null;
+ button.FontSize = Device.GetNamedSize(NamedSize.Default, button);
+ button.FontAttributes = FontAttributes.None;
+ }
+ else
+ {
+ button.FontFamily = button.Font.FontFamily;
+ if (button.Font.UseNamedSize)
+ {
+ button.FontSize = Device.GetNamedSize(button.Font.NamedSize, button.GetType(), true);
+ }
+ else
+ {
+ button.FontSize = button.Font.FontSize;
+ }
+ button.FontAttributes = button.Font.FontAttributes;
+ }
+
+ button._cancelEvents = false;
+ }
+
+ void OnCommandChanged()
+ {
+ if (Command != null)
+ {
+ Command.CanExecuteChanged += CommandCanExecuteChanged;
+ CommandCanExecuteChanged(this, EventArgs.Empty);
+ }
+ else
+ IsEnabledCore = true;
+ }
+
+ void OnSourceChanged(object sender, EventArgs eventArgs)
+ {
+ OnPropertyChanged(ImageProperty.PropertyName);
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
+ {
+ if (newvalue != null)
+ {
+ newvalue.SourceChanged += OnSourceChanged;
+ SetInheritedBindingContext(newvalue, BindingContext);
+ }
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
+ {
+ if (oldvalue != null)
+ oldvalue.SourceChanged -= OnSourceChanged;
+ }
+
+ static void SpecificFontPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var button = (Button)bindable;
+
+ if (button._cancelEvents)
+ return;
+
+ button.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+
+ button._cancelEvents = true;
+
+ if (button.FontFamily != null)
+ {
+ button.Font = Font.OfSize(button.FontFamily, button.FontSize).WithAttributes(button.FontAttributes);
+ }
+ else
+ {
+ button.Font = Font.SystemFontOfSize(button.FontSize, button.FontAttributes);
+ }
+
+ button._cancelEvents = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/CarouselPage.cs b/Xamarin.Forms.Core/CarouselPage.cs
new file mode 100644
index 00000000..a6f769e5
--- /dev/null
+++ b/Xamarin.Forms.Core/CarouselPage.cs
@@ -0,0 +1,17 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_CarouselPageRenderer))]
+ public class CarouselPage : MultiPage<ContentPage>
+ {
+ protected override ContentPage CreateDefault(object item)
+ {
+ var page = new ContentPage();
+ if (item != null)
+ page.Title = item.ToString();
+
+ return page;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/CarouselView.cs b/Xamarin.Forms.Core/CarouselView.cs
new file mode 100644
index 00000000..62e9393e
--- /dev/null
+++ b/Xamarin.Forms.Core/CarouselView.cs
@@ -0,0 +1,86 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_CarouselViewRenderer))]
+ public class CarouselView : ItemsView, ICarouselViewController
+ {
+ public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(int), typeof(CarouselView), 0, BindingMode.TwoWay);
+
+ public static readonly BindableProperty ItemProperty = BindableProperty.Create(nameof(Item), typeof(object), typeof(CarouselView), 0, BindingMode.TwoWay);
+
+ object _lastItem;
+
+ int _lastPosition;
+
+ public CarouselView()
+ {
+ _lastPosition = 0;
+ _lastItem = null;
+ VerticalOptions = LayoutOptions.FillAndExpand;
+ HorizontalOptions = LayoutOptions.FillAndExpand;
+ }
+
+ public int Item
+ {
+ get { return (int)GetValue(ItemProperty); }
+ }
+
+ public int Position
+ {
+ get { return (int)GetValue(PositionProperty); }
+ set { SetValue(PositionProperty, value); }
+ }
+
+ void ICarouselViewController.SendPositionAppearing(int position)
+ {
+ ItemAppearing?.Invoke(this, new ItemVisibilityEventArgs(GetItem(position)));
+ }
+
+ void ICarouselViewController.SendPositionDisappearing(int position)
+ {
+ ItemDisappearing?.Invoke(this, new ItemVisibilityEventArgs(GetItem(position)));
+ }
+
+ void ICarouselViewController.SendSelectedItemChanged(object item)
+ {
+ if (item.Equals(_lastItem))
+ return;
+
+ ItemSelected?.Invoke(this, new SelectedItemChangedEventArgs(item));
+ _lastItem = item;
+ }
+
+ void ICarouselViewController.SendSelectedPositionChanged(int position)
+ {
+ if (_lastPosition == position)
+ return;
+
+ _lastPosition = position;
+ PositionSelected?.Invoke(this, new SelectedPositionChangedEventArgs(position));
+ }
+
+ public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
+
+ public event EventHandler<SelectedPositionChangedEventArgs> PositionSelected;
+
+ protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+ {
+ var minimumSize = new Size(40, 40);
+ return new SizeRequest(minimumSize, minimumSize);
+ }
+
+ // non-public bc unable to implement on iOS
+ internal event EventHandler<ItemVisibilityEventArgs> ItemAppearing;
+
+ internal event EventHandler<ItemVisibilityEventArgs> ItemDisappearing;
+
+ object GetItem(int position)
+ {
+ var controller = (IItemViewController)this;
+ object item = controller.GetItem(position);
+ return item;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/CastingEnumerator.cs b/Xamarin.Forms.Core/CastingEnumerator.cs
new file mode 100644
index 00000000..62a823b1
--- /dev/null
+++ b/Xamarin.Forms.Core/CastingEnumerator.cs
@@ -0,0 +1,46 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal class CastingEnumerator<T, TFrom> : IEnumerator<T> where T : class where TFrom : class
+ {
+ readonly IEnumerator<TFrom> _enumerator;
+
+ bool _disposed;
+
+ public CastingEnumerator(IEnumerator<TFrom> enumerator)
+ {
+ _enumerator = enumerator;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ _enumerator.Dispose();
+ }
+
+ object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ public bool MoveNext()
+ {
+ return _enumerator.MoveNext();
+ }
+
+ public void Reset()
+ {
+ _enumerator.Reset();
+ }
+
+ public T Current
+ {
+ get { return _enumerator.Current as T; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/Cell.cs b/Xamarin.Forms.Core/Cells/Cell.cs
new file mode 100644
index 00000000..3b16d06a
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/Cell.cs
@@ -0,0 +1,209 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ public abstract class Cell : Element
+ {
+ public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(Cell), true, propertyChanged: OnIsEnabledPropertyChanged);
+
+ ObservableCollection<MenuItem> _contextActions;
+
+ double _height = -1;
+
+ bool _nextCallToForceUpdateSizeQueued;
+
+ public IList<MenuItem> ContextActions
+ {
+ get
+ {
+ if (_contextActions == null)
+ {
+ _contextActions = new ObservableCollection<MenuItem>();
+ _contextActions.CollectionChanged += OnContextActionsChanged;
+ }
+
+ return _contextActions;
+ }
+ }
+
+ public bool HasContextActions
+ {
+ get { return _contextActions != null && _contextActions.Count > 0 && IsEnabled; }
+ }
+
+ public double Height
+ {
+ get { return _height; }
+ set
+ {
+ if (_height == value)
+ return;
+
+ OnPropertyChanging("Height");
+ OnPropertyChanging("RenderHeight");
+ _height = value;
+ OnPropertyChanged("Height");
+ OnPropertyChanged("RenderHeight");
+ }
+ }
+
+ public bool IsEnabled
+ {
+ get { return (bool)GetValue(IsEnabledProperty); }
+ set { SetValue(IsEnabledProperty, value); }
+ }
+
+ public double RenderHeight
+ {
+ get
+ {
+ var table = RealParent as TableView;
+ if (table != null)
+ return table.HasUnevenRows && Height > 0 ? Height : table.RowHeight;
+
+ var list = RealParent as ListView;
+ if (list != null)
+ return list.HasUnevenRows && Height > 0 ? Height : list.RowHeight;
+
+ return 40;
+ }
+ }
+
+ public event EventHandler Appearing;
+
+ public event EventHandler Disappearing;
+
+ public void ForceUpdateSize()
+ {
+ if (_nextCallToForceUpdateSizeQueued)
+ return;
+
+ if ((Parent as ListView)?.HasUnevenRows == true)
+ {
+ _nextCallToForceUpdateSizeQueued = true;
+ OnForceUpdateSizeRequested();
+ }
+ }
+
+ public event EventHandler Tapped;
+
+ protected internal virtual void OnTapped()
+ {
+ if (Tapped != null)
+ Tapped(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnAppearing()
+ {
+ EventHandler handler = Appearing;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ if (HasContextActions)
+ {
+ for (var i = 0; i < _contextActions.Count; i++)
+ SetInheritedBindingContext(_contextActions[i], BindingContext);
+ }
+ }
+
+ protected virtual void OnDisappearing()
+ {
+ EventHandler handler = Disappearing;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ protected override void OnParentSet()
+ {
+ if (RealParent != null)
+ {
+ RealParent.PropertyChanged += OnParentPropertyChanged;
+ RealParent.PropertyChanging += OnParentPropertyChanging;
+ }
+
+ base.OnParentSet();
+ }
+
+ protected override void OnPropertyChanging(string propertyName = null)
+ {
+ if (propertyName == "Parent")
+ {
+ if (RealParent != null)
+ {
+ RealParent.PropertyChanged -= OnParentPropertyChanged;
+ RealParent.PropertyChanging -= OnParentPropertyChanging;
+ }
+ }
+
+ base.OnPropertyChanging(propertyName);
+ }
+
+ internal event EventHandler ForceUpdateSizeRequested;
+
+ internal void SendAppearing()
+ {
+ OnAppearing();
+
+ var container = RealParent as IListViewController;
+ if (container != null)
+ container.SendCellAppearing(this);
+ }
+
+ internal void SendDisappearing()
+ {
+ OnDisappearing();
+
+ var container = RealParent as IListViewController;
+ if (container != null)
+ container.SendCellDisappearing(this);
+ }
+
+ void OnContextActionsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ for (var i = 0; i < _contextActions.Count; i++)
+ SetInheritedBindingContext(_contextActions[i], BindingContext);
+
+ OnPropertyChanged("HasContextActions");
+ }
+
+ async void OnForceUpdateSizeRequested()
+ {
+ // don't run more than once per 16 milliseconds
+ await Task.Delay(TimeSpan.FromMilliseconds(16));
+ EventHandler handler = ForceUpdateSizeRequested;
+ if (handler != null)
+ handler(this, null);
+
+ _nextCallToForceUpdateSizeQueued = false;
+ }
+
+ static void OnIsEnabledPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ (bindable as Cell).OnPropertyChanged("HasContextActions");
+ }
+
+ void OnParentPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ // Technically we might be raising this even if it didn't change, but I'm taking the bet that
+ // its uncommon enough that we don't want to take the penalty of N GetValue calls to verify.
+ if (e.PropertyName == "RowHeight")
+ OnPropertyChanged("RenderHeight");
+ }
+
+ void OnParentPropertyChanging(object sender, PropertyChangingEventArgs e)
+ {
+ if (e.PropertyName == "RowHeight")
+ OnPropertyChanging("RenderHeight");
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/EntryCell.cs b/Xamarin.Forms.Core/Cells/EntryCell.cs
new file mode 100644
index 00000000..d74e365e
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/EntryCell.cs
@@ -0,0 +1,80 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class EntryCell : Cell
+ {
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(EntryCell), null, BindingMode.TwoWay);
+
+ public static readonly BindableProperty LabelProperty = BindableProperty.Create("Label", typeof(string), typeof(EntryCell), null);
+
+ public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(EntryCell), null);
+
+ public static readonly BindableProperty LabelColorProperty = BindableProperty.Create("LabelColor", typeof(Color), typeof(EntryCell), Color.Default);
+
+ public static readonly BindableProperty KeyboardProperty = BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(EntryCell), Keyboard.Default);
+
+ public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(EntryCell), TextAlignment.Start,
+ propertyChanged: OnHorizontalTextAlignmentPropertyChanged);
+
+ [Obsolete("XAlignProperty is obsolete. Please use HorizontalTextAlignmentProperty instead.")] public static readonly BindableProperty XAlignProperty = HorizontalTextAlignmentProperty;
+
+ public TextAlignment HorizontalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
+ set { SetValue(HorizontalTextAlignmentProperty, value); }
+ }
+
+ public Keyboard Keyboard
+ {
+ get { return (Keyboard)GetValue(KeyboardProperty); }
+ set { SetValue(KeyboardProperty, value); }
+ }
+
+ public string Label
+ {
+ get { return (string)GetValue(LabelProperty); }
+ set { SetValue(LabelProperty, value); }
+ }
+
+ public Color LabelColor
+ {
+ get { return (Color)GetValue(LabelColorProperty); }
+ set { SetValue(LabelColorProperty, value); }
+ }
+
+ public string Placeholder
+ {
+ get { return (string)GetValue(PlaceholderProperty); }
+ set { SetValue(PlaceholderProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ [Obsolete("XAlign is obsolete. Please use HorizontalTextAlignment instead.")]
+ public TextAlignment XAlign
+ {
+ get { return (TextAlignment)GetValue(XAlignProperty); }
+ set { SetValue(XAlignProperty, value); }
+ }
+
+ public event EventHandler Completed;
+
+ internal void SendCompleted()
+ {
+ EventHandler handler = Completed;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ static void OnHorizontalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (EntryCell)bindable;
+ label.OnPropertyChanged(nameof(XAlign));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/INativeElementView.cs b/Xamarin.Forms.Core/Cells/INativeElementView.cs
new file mode 100644
index 00000000..2d015282
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/INativeElementView.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ public interface INativeElementView
+ {
+ Element Element { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/ImageCell.cs b/Xamarin.Forms.Core/Cells/ImageCell.cs
new file mode 100644
index 00000000..6d5ba714
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/ImageCell.cs
@@ -0,0 +1,56 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ImageCell : TextCell
+ {
+ public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create("ImageSource", typeof(ImageSource), typeof(ImageCell), null,
+ propertyChanging: (bindable, oldvalue, newvalue) => ((ImageCell)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((ImageCell)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue));
+
+ public ImageCell()
+ {
+ Disappearing += (sender, e) =>
+ {
+ if (ImageSource == null)
+ return;
+ ImageSource.Cancel();
+ };
+ }
+
+ [TypeConverter(typeof(ImageSourceConverter))]
+ public ImageSource ImageSource
+ {
+ get { return (ImageSource)GetValue(ImageSourceProperty); }
+ set { SetValue(ImageSourceProperty, value); }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ if (ImageSource != null)
+ SetInheritedBindingContext(ImageSource, BindingContext);
+
+ base.OnBindingContextChanged();
+ }
+
+ void OnSourceChanged(object sender, EventArgs eventArgs)
+ {
+ OnPropertyChanged(ImageSourceProperty.PropertyName);
+ }
+
+ void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
+ {
+ if (newvalue != null)
+ {
+ newvalue.SourceChanged += OnSourceChanged;
+ SetInheritedBindingContext(newvalue, BindingContext);
+ }
+ }
+
+ void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
+ {
+ if (oldvalue != null)
+ oldvalue.SourceChanged -= OnSourceChanged;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/SwitchCell.cs b/Xamarin.Forms.Core/Cells/SwitchCell.cs
new file mode 100644
index 00000000..adab7f45
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/SwitchCell.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class SwitchCell : Cell
+ {
+ public static readonly BindableProperty OnProperty = BindableProperty.Create("On", typeof(bool), typeof(SwitchCell), false, propertyChanged: (obj, oldValue, newValue) =>
+ {
+ var switchCell = (SwitchCell)obj;
+ EventHandler<ToggledEventArgs> handler = switchCell.OnChanged;
+ if (handler != null)
+ handler(obj, new ToggledEventArgs((bool)newValue));
+ }, defaultBindingMode: BindingMode.TwoWay);
+
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(SwitchCell), default(string));
+
+ public bool On
+ {
+ get { return (bool)GetValue(OnProperty); }
+ set { SetValue(OnProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public event EventHandler<ToggledEventArgs> OnChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/TextCell.cs b/Xamarin.Forms.Core/Cells/TextCell.cs
new file mode 100644
index 00000000..01da6447
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/TextCell.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Windows.Input;
+
+namespace Xamarin.Forms
+{
+ public class TextCell : Cell
+ {
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(TextCell), default(ICommand),
+ propertyChanging: (bindable, oldvalue, newvalue) =>
+ {
+ var textCell = (TextCell)bindable;
+ var oldcommand = (ICommand)oldvalue;
+ if (oldcommand != null)
+ oldcommand.CanExecuteChanged -= textCell.OnCommandCanExecuteChanged;
+ }, propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ var textCell = (TextCell)bindable;
+ var newcommand = (ICommand)newvalue;
+ if (newcommand != null)
+ {
+ textCell.IsEnabled = newcommand.CanExecute(textCell.CommandParameter);
+ newcommand.CanExecuteChanged += textCell.OnCommandCanExecuteChanged;
+ }
+ });
+
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(TextCell), default(object),
+ propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ var textCell = (TextCell)bindable;
+ if (textCell.Command != null)
+ {
+ textCell.IsEnabled = textCell.Command.CanExecute(newvalue);
+ }
+ });
+
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(TextCell), default(string));
+
+ public static readonly BindableProperty DetailProperty = BindableProperty.Create("Detail", typeof(string), typeof(TextCell), default(string));
+
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(TextCell), Color.Default);
+
+ public static readonly BindableProperty DetailColorProperty = BindableProperty.Create("DetailColor", typeof(Color), typeof(TextCell), Color.Default);
+
+ public ICommand Command
+ {
+ get { return (ICommand)GetValue(CommandProperty); }
+ set { SetValue(CommandProperty, value); }
+ }
+
+ public object CommandParameter
+ {
+ get { return GetValue(CommandParameterProperty); }
+ set { SetValue(CommandParameterProperty, value); }
+ }
+
+ public string Detail
+ {
+ get { return (string)GetValue(DetailProperty); }
+ set { SetValue(DetailProperty, value); }
+ }
+
+ public Color DetailColor
+ {
+ get { return (Color)GetValue(DetailColorProperty); }
+ set { SetValue(DetailColorProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ protected internal override void OnTapped()
+ {
+ base.OnTapped();
+
+ ICommand cmd = Command;
+ if (cmd != null)
+ cmd.Execute(CommandParameter);
+ }
+
+ void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs)
+ {
+ IsEnabled = Command.CanExecute(CommandParameter);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Cells/ViewCell.cs b/Xamarin.Forms.Core/Cells/ViewCell.cs
new file mode 100644
index 00000000..334822f6
--- /dev/null
+++ b/Xamarin.Forms.Core/Cells/ViewCell.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("View")]
+ public class ViewCell : Cell
+ {
+ ReadOnlyCollection<Element> _logicalChildren;
+
+ View _view;
+
+ public View View
+ {
+ get { return _view; }
+ set
+ {
+ if (_view == value)
+ return;
+
+ OnPropertyChanging();
+
+ if (_view != null)
+ {
+ OnChildRemoved(_view);
+ _view.ComputedConstraint = LayoutConstraint.None;
+ }
+
+ _view = value;
+
+ if (_view != null)
+ {
+ _view.ComputedConstraint = LayoutConstraint.Fixed;
+ OnChildAdded(_view);
+ _logicalChildren = new ReadOnlyCollection<Element>(new List<Element>(new[] { View }));
+ }
+ else
+ {
+ _logicalChildren = null;
+ }
+
+ OnPropertyChanged();
+ }
+ }
+
+ internal override ReadOnlyCollection<Element> LogicalChildren => _logicalChildren ?? base.LogicalChildren;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ChatKeyboard.cs b/Xamarin.Forms.Core/ChatKeyboard.cs
new file mode 100644
index 00000000..26a403a0
--- /dev/null
+++ b/Xamarin.Forms.Core/ChatKeyboard.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal sealed class ChatKeyboard : Keyboard
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs b/Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs
new file mode 100644
index 00000000..3c5e946a
--- /dev/null
+++ b/Xamarin.Forms.Core/ChildCollectionChangedEventArgs.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Specialized;
+
+namespace Xamarin.Forms
+{
+ internal class ChildCollectionChangedEventArgs : EventArgs
+ {
+ public ChildCollectionChangedEventArgs(NotifyCollectionChangedEventArgs args)
+ {
+ Args = args;
+ }
+
+ public NotifyCollectionChangedEventArgs Args { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/CollectionSynchronizationCallback.cs b/Xamarin.Forms.Core/CollectionSynchronizationCallback.cs
new file mode 100644
index 00000000..e186e016
--- /dev/null
+++ b/Xamarin.Forms.Core/CollectionSynchronizationCallback.cs
@@ -0,0 +1,7 @@
+using System;
+using System.Collections;
+
+namespace Xamarin.Forms
+{
+ public delegate void CollectionSynchronizationCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess);
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/CollectionSynchronizationContext.cs b/Xamarin.Forms.Core/CollectionSynchronizationContext.cs
new file mode 100644
index 00000000..a0144260
--- /dev/null
+++ b/Xamarin.Forms.Core/CollectionSynchronizationContext.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal sealed class CollectionSynchronizationContext
+ {
+ internal CollectionSynchronizationContext(object context, CollectionSynchronizationCallback callback)
+ {
+ ContextReference = new WeakReference(context);
+ Callback = callback;
+ }
+
+ internal CollectionSynchronizationCallback Callback { get; private set; }
+
+ internal object Context
+ {
+ get { return ContextReference != null ? ContextReference.Target : null; }
+ }
+
+ internal WeakReference ContextReference { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Color.cs b/Xamarin.Forms.Core/Color.cs
new file mode 100644
index 00000000..e9f2987d
--- /dev/null
+++ b/Xamarin.Forms.Core/Color.cs
@@ -0,0 +1,375 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("R={R}, G={G}, B={B}, A={A}, Hue={Hue}, Saturation={Saturation}, Luminosity={Luminosity}")]
+ [TypeConverter(typeof(ColorTypeConverter))]
+ public struct Color
+ {
+ readonly Mode _mode;
+
+ enum Mode
+ {
+ Default,
+ Rgb,
+ Hsl
+ }
+
+ public static Color Default
+ {
+ get { return new Color(-1d, -1d, -1d, -1d, Mode.Default); }
+ }
+
+ internal bool IsDefault
+ {
+ get { return _mode == Mode.Default; }
+ }
+
+ public static Color Accent { get; internal set; }
+
+ readonly float _a;
+
+ public double A
+ {
+ get { return _a; }
+ }
+
+ readonly float _r;
+
+ public double R
+ {
+ get { return _r; }
+ }
+
+ readonly float _g;
+
+ public double G
+ {
+ get { return _g; }
+ }
+
+ readonly float _b;
+
+ public double B
+ {
+ get { return _b; }
+ }
+
+ readonly float _hue;
+
+ public double Hue
+ {
+ get { return _hue; }
+ }
+
+ readonly float _saturation;
+
+ public double Saturation
+ {
+ get { return _saturation; }
+ }
+
+ readonly float _luminosity;
+
+ public double Luminosity
+ {
+ get { return _luminosity; }
+ }
+
+ public Color(double r, double g, double b, double a) : this(r, g, b, a, Mode.Rgb)
+ {
+ }
+
+ Color(double w, double x, double y, double z, Mode mode)
+ {
+ _mode = mode;
+ switch (mode)
+ {
+ default:
+ case Mode.Default:
+ _r = _g = _b = _a = -1;
+ _hue = _saturation = _luminosity = -1;
+ break;
+ case Mode.Rgb:
+ _r = (float)w.Clamp(0, 1);
+ _g = (float)x.Clamp(0, 1);
+ _b = (float)y.Clamp(0, 1);
+ _a = (float)z.Clamp(0, 1);
+ ConvertToHsl(_r, _g, _b, mode, out _hue, out _saturation, out _luminosity);
+ break;
+ case Mode.Hsl:
+ _hue = (float)w.Clamp(0, 1);
+ _saturation = (float)x.Clamp(0, 1);
+ _luminosity = (float)y.Clamp(0, 1);
+ _a = (float)z.Clamp(0, 1);
+ ConvertToRgb(_hue, _saturation, _luminosity, mode, out _r, out _g, out _b);
+ break;
+ }
+ }
+
+ public Color(double r, double g, double b) : this(r, g, b, 1)
+ {
+ }
+
+ public Color(double value) : this(value, value, value, 1)
+ {
+ }
+
+ public Color MultiplyAlpha(double alpha)
+ {
+ switch (_mode)
+ {
+ default:
+ case Mode.Default:
+ throw new InvalidOperationException("Invalid on Color.Default");
+ case Mode.Rgb:
+ return new Color(_r, _g, _b, _a * alpha, Mode.Rgb);
+ case Mode.Hsl:
+ return new Color(_hue, _saturation, _luminosity, _a * alpha, Mode.Hsl);
+ }
+ }
+
+ public Color AddLuminosity(double delta)
+ {
+ if (_mode == Mode.Default)
+ throw new InvalidOperationException("Invalid on Color.Default");
+
+ return new Color(_hue, _saturation, _luminosity + delta, _a, Mode.Hsl);
+ }
+
+ public Color WithHue(double hue)
+ {
+ if (_mode == Mode.Default)
+ throw new InvalidOperationException("Invalid on Color.Default");
+ return new Color(hue, _saturation, _luminosity, _a, Mode.Hsl);
+ }
+
+ public Color WithSaturation(double saturation)
+ {
+ if (_mode == Mode.Default)
+ throw new InvalidOperationException("Invalid on Color.Default");
+ return new Color(_hue, saturation, _luminosity, _a, Mode.Hsl);
+ }
+
+ public Color WithLuminosity(double luminosity)
+ {
+ if (_mode == Mode.Default)
+ throw new InvalidOperationException("Invalid on Color.Default");
+ return new Color(_hue, _saturation, luminosity, _a, Mode.Hsl);
+ }
+
+ static void ConvertToRgb(float hue, float saturation, float luminosity, Mode mode, out float r, out float g, out float b)
+ {
+ if (mode != Mode.Hsl)
+ throw new InvalidOperationException();
+
+ if (luminosity == 0)
+ {
+ r = g = b = 0;
+ return;
+ }
+
+ if (saturation == 0)
+ {
+ r = g = b = luminosity;
+ return;
+ }
+ float temp2 = luminosity <= 0.5f ? luminosity * (1.0f + saturation) : luminosity + saturation - luminosity * saturation;
+ float temp1 = 2.0f * luminosity - temp2;
+
+ var t3 = new[] { hue + 1.0f / 3.0f, hue, hue - 1.0f / 3.0f };
+ var clr = new float[] { 0, 0, 0 };
+ for (var i = 0; i < 3; i++)
+ {
+ if (t3[i] < 0)
+ t3[i] += 1.0f;
+ if (t3[i] > 1)
+ t3[i] -= 1.0f;
+ if (6.0 * t3[i] < 1.0)
+ clr[i] = temp1 + (temp2 - temp1) * t3[i] * 6.0f;
+ else if (2.0 * t3[i] < 1.0)
+ clr[i] = temp2;
+ else if (3.0 * t3[i] < 2.0)
+ clr[i] = temp1 + (temp2 - temp1) * (2.0f / 3.0f - t3[i]) * 6.0f;
+ else
+ clr[i] = temp1;
+ }
+
+ r = clr[0];
+ g = clr[1];
+ b = clr[2];
+ }
+
+ static void ConvertToHsl(float r, float g, float b, Mode mode, out float h, out float s, out float l)
+ {
+ float v = Math.Max(r, g);
+ v = Math.Max(v, b);
+
+ float m = Math.Min(r, g);
+ m = Math.Min(m, b);
+
+ l = (m + v) / 2.0f;
+ if (l <= 0.0)
+ {
+ h = s = l = 0;
+ return;
+ }
+ float vm = v - m;
+ s = vm;
+
+ if (s > 0.0)
+ {
+ s /= l <= 0.5f ? v + m : 2.0f - v - m;
+ }
+ else
+ {
+ h = 0;
+ s = 0;
+ return;
+ }
+
+ float r2 = (v - r) / vm;
+ float g2 = (v - g) / vm;
+ float b2 = (v - b) / vm;
+
+ if (r == v)
+ {
+ h = g == m ? 5.0f + b2 : 1.0f - g2;
+ }
+ else if (g == v)
+ {
+ h = b == m ? 1.0f + r2 : 3.0f - b2;
+ }
+ else
+ {
+ h = r == m ? 3.0f + g2 : 5.0f - r2;
+ }
+ h /= 6.0f;
+ }
+
+ public static bool operator ==(Color color1, Color color2)
+ {
+ return EqualsInner(color1, color2);
+ }
+
+ public static bool operator !=(Color color1, Color color2)
+ {
+ return !EqualsInner(color1, color2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashcode = _r.GetHashCode();
+ hashcode = (hashcode * 397) ^ _g.GetHashCode();
+ hashcode = (hashcode * 397) ^ _b.GetHashCode();
+ hashcode = (hashcode * 397) ^ _a.GetHashCode();
+ return hashcode;
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Color)
+ {
+ return EqualsInner(this, (Color)obj);
+ }
+ return base.Equals(obj);
+ }
+
+ static bool EqualsInner(Color color1, Color color2)
+ {
+ if (color1._mode == Mode.Default && color2._mode == Mode.Default)
+ return true;
+ if (color1._mode == Mode.Default || color2._mode == Mode.Default)
+ return false;
+ if (color1._mode == Mode.Hsl && color2._mode == Mode.Hsl)
+ return color1._hue == color2._hue && color1._saturation == color2._saturation && color1._luminosity == color2._luminosity && color1._a == color2._a;
+ return color1._r == color2._r && color1._g == color2._g && color1._b == color2._b && color1._a == color2._a;
+ }
+
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[Color: A={0}, R={1}, G={2}, B={3}, Hue={4}, Saturation={5}, Luminosity={6}]", A, R, G, B, Hue, Saturation, Luminosity);
+ }
+
+ public static Color FromHex(string hex)
+ {
+ hex = hex.Replace("#", "");
+ switch (hex.Length)
+ {
+ case 3: //#rgb => ffrrggbb
+ hex = string.Format("ff{0}{1}{2}{3}{4}{5}", hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]);
+ break;
+ case 4: //#argb => aarrggbb
+ hex = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}", hex[0], hex[0], hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]);
+ break;
+ case 6: //#rrggbb => ffrrggbb
+ hex = string.Format("ff{0}", hex);
+ break;
+ }
+ return FromUint(Convert.ToUInt32(hex.Replace("#", ""), 16));
+ }
+
+ public static Color FromUint(uint argb)
+ {
+ return FromRgba((byte)((argb & 0x00ff0000) >> 0x10), (byte)((argb & 0x0000ff00) >> 0x8), (byte)(argb & 0x000000ff), (byte)((argb & 0xff000000) >> 0x18));
+ }
+
+ public static Color FromRgba(int r, int g, int b, int a)
+ {
+ double red = (double)r / 255;
+ double green = (double)g / 255;
+ double blue = (double)b / 255;
+ double alpha = (double)a / 255;
+ return new Color(red, green, blue, alpha, Mode.Rgb);
+ }
+
+ public static Color FromRgb(int r, int g, int b)
+ {
+ return FromRgba(r, g, b, 255);
+ }
+
+ public static Color FromRgba(double r, double g, double b, double a)
+ {
+ return new Color(r, g, b, a);
+ }
+
+ public static Color FromRgb(double r, double g, double b)
+ {
+ return new Color(r, g, b, 1d, Mode.Rgb);
+ }
+
+ public static Color FromHsla(double h, double s, double l, double a = 1d)
+ {
+ return new Color(h, s, l, a, Mode.Hsl);
+ }
+
+ #region Color Definitions
+
+ public static readonly Color Transparent = FromRgba(0, 0, 0, 0);
+ public static readonly Color Aqua = FromRgb(0, 255, 255);
+ public static readonly Color Black = FromRgb(0, 0, 0);
+ public static readonly Color Blue = FromRgb(0, 0, 255);
+ public static readonly Color Fuchsia = FromRgb(255, 0, 255);
+ [Obsolete("Fuschia is obsolete as of version 1.3, please use the correct spelling of Fuchsia")] public static readonly Color Fuschia = FromRgb(255, 0, 255);
+ public static readonly Color Gray = FromRgb(128, 128, 128);
+ public static readonly Color Green = FromRgb(0, 128, 0);
+ public static readonly Color Lime = FromRgb(0, 255, 0);
+ public static readonly Color Maroon = FromRgb(128, 0, 0);
+ public static readonly Color Navy = FromRgb(0, 0, 128);
+ public static readonly Color Olive = FromRgb(128, 128, 0);
+ public static readonly Color Purple = FromRgb(128, 0, 128);
+ public static readonly Color Pink = FromRgb(255, 102, 255);
+ public static readonly Color Red = FromRgb(255, 0, 0);
+ public static readonly Color Silver = FromRgb(192, 192, 192);
+ public static readonly Color Teal = FromRgb(0, 128, 128);
+ public static readonly Color White = FromRgb(255, 255, 255);
+ public static readonly Color Yellow = FromRgb(255, 255, 0);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ColorTypeConverter.cs b/Xamarin.Forms.Core/ColorTypeConverter.cs
new file mode 100644
index 00000000..547adf3b
--- /dev/null
+++ b/Xamarin.Forms.Core/ColorTypeConverter.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ public class ColorTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ if (value.Trim().StartsWith("#", StringComparison.Ordinal))
+ return Color.FromHex(value);
+ string[] parts = value.Split('.');
+ if (parts.Length == 1 || (parts.Length == 2 && parts[0] == "Color"))
+ {
+ string color = parts[parts.Length - 1];
+ switch (color)
+ {
+ case "Default":
+ return Color.Default;
+ case "Transparent":
+ return Color.Transparent;
+ case "Aqua":
+ return Color.Aqua;
+ case "Black":
+ return Color.Black;
+ case "Blue":
+ return Color.Blue;
+ case "Fuchsia":
+ return Color.Fuchsia;
+ case "Gray":
+ return Color.Gray;
+ case "Green":
+ return Color.Green;
+ case "Lime":
+ return Color.Lime;
+ case "Maroon":
+ return Color.Maroon;
+ case "Navy":
+ return Color.Navy;
+ case "Olive":
+ return Color.Olive;
+ case "Purple":
+ return Color.Purple;
+ case "Pink":
+ return Color.Pink;
+ case "Red":
+ return Color.Red;
+ case "Silver":
+ return Color.Silver;
+ case "Teal":
+ return Color.Teal;
+ case "White":
+ return Color.White;
+ case "Yellow":
+ return Color.Yellow;
+ }
+ FieldInfo field = typeof(Color).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == color);
+ if (field != null)
+ return (Color)field.GetValue(null);
+ PropertyInfo property = typeof(Color).GetProperties().FirstOrDefault(pi => pi.Name == color && pi.CanRead && pi.GetMethod.IsStatic);
+ if (property != null)
+ return (Color)property.GetValue(null, null);
+ }
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Color)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ColumnDefinition.cs b/Xamarin.Forms.Core/ColumnDefinition.cs
new file mode 100644
index 00000000..995f6c73
--- /dev/null
+++ b/Xamarin.Forms.Core/ColumnDefinition.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class ColumnDefinition : BindableObject, IDefinition
+ {
+ public static readonly BindableProperty WidthProperty = BindableProperty.Create("Width", typeof(GridLength), typeof(ColumnDefinition), new GridLength(1, GridUnitType.Star),
+ propertyChanged: (bindable, oldValue, newValue) => ((ColumnDefinition)bindable).OnSizeChanged());
+
+ public ColumnDefinition()
+ {
+ MinimumWidth = -1;
+ }
+
+ public GridLength Width
+ {
+ get { return (GridLength)GetValue(WidthProperty); }
+ set { SetValue(WidthProperty, value); }
+ }
+
+ internal double ActualWidth { get; set; }
+
+ internal double MinimumWidth { get; set; }
+
+ public event EventHandler SizeChanged;
+
+ void OnSizeChanged()
+ {
+ EventHandler eh = SizeChanged;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ColumnDefinitionCollection.cs b/Xamarin.Forms.Core/ColumnDefinitionCollection.cs
new file mode 100644
index 00000000..0ee0358b
--- /dev/null
+++ b/Xamarin.Forms.Core/ColumnDefinitionCollection.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ public sealed class ColumnDefinitionCollection : DefinitionCollection<ColumnDefinition>
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Command.cs b/Xamarin.Forms.Core/Command.cs
new file mode 100644
index 00000000..73ae1b08
--- /dev/null
+++ b/Xamarin.Forms.Core/Command.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Windows.Input;
+
+namespace Xamarin.Forms
+{
+ public sealed class Command<T> : Command
+ {
+ public Command(Action<T> execute) : base(o => execute((T)o))
+ {
+ if (execute == null)
+ throw new ArgumentNullException("execute");
+ }
+
+ public Command(Action<T> execute, Func<T, bool> canExecute) : base(o => execute((T)o), o => canExecute((T)o))
+ {
+ if (execute == null)
+ throw new ArgumentNullException("execute");
+ if (canExecute == null)
+ throw new ArgumentNullException("canExecute");
+ }
+ }
+
+ public class Command : ICommand
+ {
+ readonly Func<object, bool> _canExecute;
+ readonly Action<object> _execute;
+
+ public Command(Action<object> execute)
+ {
+ if (execute == null)
+ throw new ArgumentNullException("execute");
+
+ _execute = execute;
+ }
+
+ public Command(Action execute) : this(o => execute())
+ {
+ if (execute == null)
+ throw new ArgumentNullException("execute");
+ }
+
+ public Command(Action<object> execute, Func<object, bool> canExecute) : this(execute)
+ {
+ if (canExecute == null)
+ throw new ArgumentNullException("canExecute");
+
+ _canExecute = canExecute;
+ }
+
+ public Command(Action execute, Func<bool> canExecute) : this(o => execute(), o => canExecute())
+ {
+ if (execute == null)
+ throw new ArgumentNullException("execute");
+ if (canExecute == null)
+ throw new ArgumentNullException("canExecute");
+ }
+
+ public bool CanExecute(object parameter)
+ {
+ if (_canExecute != null)
+ return _canExecute(parameter);
+
+ return true;
+ }
+
+ public event EventHandler CanExecuteChanged;
+
+ public void Execute(object parameter)
+ {
+ _execute(parameter);
+ }
+
+ public void ChangeCanExecute()
+ {
+ EventHandler changed = CanExecuteChanged;
+ if (changed != null)
+ changed(this, EventArgs.Empty);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ConcurrentDictionary.cs b/Xamarin.Forms.Core/ConcurrentDictionary.cs
new file mode 100644
index 00000000..a229c6fe
--- /dev/null
+++ b/Xamarin.Forms.Core/ConcurrentDictionary.cs
@@ -0,0 +1,426 @@
+// ConcurrentDictionary.cs
+//
+// Copyright (c) 2009 Jérémie "Garuma" Laval
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms
+{
+ internal class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable
+ {
+ readonly IEqualityComparer<TKey> _comparer;
+
+ SplitOrderedList<TKey, KeyValuePair<TKey, TValue>> _internalDictionary;
+
+ public ConcurrentDictionary() : this(EqualityComparer<TKey>.Default)
+ {
+ }
+
+ public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, EqualityComparer<TKey>.Default)
+ {
+ }
+
+ public ConcurrentDictionary(IEqualityComparer<TKey> comparer)
+ {
+ _comparer = comparer;
+ _internalDictionary = new SplitOrderedList<TKey, KeyValuePair<TKey, TValue>>(comparer);
+ }
+
+ public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) : this(comparer)
+ {
+ foreach (KeyValuePair<TKey, TValue> pair in collection)
+ Add(pair.Key, pair.Value);
+ }
+
+ // Parameters unused
+ public ConcurrentDictionary(int concurrencyLevel, int capacity) : this(EqualityComparer<TKey>.Default)
+ {
+ }
+
+ public ConcurrentDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) : this(collection, comparer)
+ {
+ }
+
+ // Parameters unused
+ public ConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer) : this(comparer)
+ {
+ }
+
+ public bool IsEmpty
+ {
+ get { return Count == 0; }
+ }
+
+ void ICollection.CopyTo(Array array, int startIndex)
+ {
+ var arr = array as KeyValuePair<TKey, TValue>[];
+ if (arr == null)
+ return;
+
+ CopyTo(arr, startIndex, Count);
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get { return true; }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get { return this; }
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> pair)
+ {
+ Add(pair.Key, pair.Value);
+ }
+
+ public void Clear()
+ {
+ // Pronk
+ _internalDictionary = new SplitOrderedList<TKey, KeyValuePair<TKey, TValue>>(_comparer);
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> pair)
+ {
+ return ContainsKey(pair.Key);
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int startIndex)
+ {
+ CopyTo(array, startIndex);
+ }
+
+ public int Count
+ {
+ get { return _internalDictionary.Count; }
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> pair)
+ {
+ return Remove(pair.Key);
+ }
+
+ void IDictionary.Add(object key, object value)
+ {
+ if (!(key is TKey) || !(value is TValue))
+ throw new ArgumentException("key or value aren't of correct type");
+
+ Add((TKey)key, (TValue)value);
+ }
+
+ bool IDictionary.Contains(object key)
+ {
+ if (!(key is TKey))
+ return false;
+
+ return ContainsKey((TKey)key);
+ }
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ {
+ return new ConcurrentDictionaryEnumerator(GetEnumeratorInternal());
+ }
+
+ bool IDictionary.IsFixedSize
+ {
+ get { return false; }
+ }
+
+ bool IDictionary.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ object IDictionary.this[object key]
+ {
+ get
+ {
+ if (!(key is TKey))
+ throw new ArgumentException("key isn't of correct type", "key");
+
+ return this[(TKey)key];
+ }
+ set
+ {
+ if (!(key is TKey) || !(value is TValue))
+ throw new ArgumentException("key or value aren't of correct type");
+
+ this[(TKey)key] = (TValue)value;
+ }
+ }
+
+ ICollection IDictionary.Keys
+ {
+ get { return (ICollection)Keys; }
+ }
+
+ void IDictionary.Remove(object key)
+ {
+ if (!(key is TKey))
+ return;
+
+ Remove((TKey)key);
+ }
+
+ ICollection IDictionary.Values
+ {
+ get { return (ICollection)Values; }
+ }
+
+ void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
+ {
+ Add(key, value);
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ CheckKey(key);
+ KeyValuePair<TKey, TValue> dummy;
+ return _internalDictionary.Find(Hash(key), key, out dummy);
+ }
+
+ public TValue this[TKey key]
+ {
+ get { return GetValue(key); }
+ set { AddOrUpdate(key, value, value); }
+ }
+
+ public ICollection<TKey> Keys
+ {
+ get { return GetPart(kvp => kvp.Key); }
+ }
+
+ bool IDictionary<TKey, TValue>.Remove(TKey key)
+ {
+ return Remove(key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ CheckKey(key);
+ KeyValuePair<TKey, TValue> pair;
+ bool result = _internalDictionary.Find(Hash(key), key, out pair);
+ value = pair.Value;
+
+ return result;
+ }
+
+ public ICollection<TValue> Values
+ {
+ get { return GetPart(kvp => kvp.Value); }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumeratorInternal();
+ }
+
+ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+ {
+ return GetEnumeratorInternal();
+ }
+
+ public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
+ {
+ CheckKey(key);
+ if (addValueFactory == null)
+ throw new ArgumentNullException("addValueFactory");
+ if (updateValueFactory == null)
+ throw new ArgumentNullException("updateValueFactory");
+ return _internalDictionary.InsertOrUpdate(Hash(key), key, () => Make(key, addValueFactory(key)), e => Make(key, updateValueFactory(key, e.Value))).Value;
+ }
+
+ public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
+ {
+ return AddOrUpdate(key, _ => addValue, updateValueFactory);
+ }
+
+ public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
+ {
+ CheckKey(key);
+ return _internalDictionary.InsertOrGet(Hash(key), key, Make(key, default(TValue)), () => Make(key, valueFactory(key))).Value;
+ }
+
+ public TValue GetOrAdd(TKey key, TValue value)
+ {
+ CheckKey(key);
+ return _internalDictionary.InsertOrGet(Hash(key), key, Make(key, value), null).Value;
+ }
+
+ public KeyValuePair<TKey, TValue>[] ToArray()
+ {
+ // This is most certainly not optimum but there is
+ // not a lot of possibilities
+
+ return new List<KeyValuePair<TKey, TValue>>(this).ToArray();
+ }
+
+ public bool TryAdd(TKey key, TValue value)
+ {
+ CheckKey(key);
+ return _internalDictionary.Insert(Hash(key), key, Make(key, value));
+ }
+
+ public bool TryRemove(TKey key, out TValue value)
+ {
+ CheckKey(key);
+ KeyValuePair<TKey, TValue> data;
+ bool result = _internalDictionary.Delete(Hash(key), key, out data);
+ value = data.Value;
+ return result;
+ }
+
+ public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
+ {
+ CheckKey(key);
+ return _internalDictionary.CompareExchange(Hash(key), key, Make(key, newValue), e => e.Value.Equals(comparisonValue));
+ }
+
+ void Add(TKey key, TValue value)
+ {
+ while (!TryAdd(key, value))
+ ;
+ }
+
+ TValue AddOrUpdate(TKey key, TValue addValue, TValue updateValue)
+ {
+ CheckKey(key);
+ return _internalDictionary.InsertOrUpdate(Hash(key), key, Make(key, addValue), Make(key, updateValue)).Value;
+ }
+
+ void CheckKey(TKey key)
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+ }
+
+ void CopyTo(KeyValuePair<TKey, TValue>[] array, int startIndex)
+ {
+ CopyTo(array, startIndex, Count);
+ }
+
+ void CopyTo(KeyValuePair<TKey, TValue>[] array, int startIndex, int num)
+ {
+ foreach (KeyValuePair<TKey, TValue> kvp in this)
+ {
+ array[startIndex++] = kvp;
+
+ if (--num <= 0)
+ return;
+ }
+ }
+
+ IEnumerator<KeyValuePair<TKey, TValue>> GetEnumeratorInternal()
+ {
+ return _internalDictionary.GetEnumerator();
+ }
+
+ ICollection<T> GetPart<T>(Func<KeyValuePair<TKey, TValue>, T> extractor)
+ {
+ var temp = new List<T>();
+
+ foreach (KeyValuePair<TKey, TValue> kvp in this)
+ temp.Add(extractor(kvp));
+
+ return new ReadOnlyCollection<T>(temp);
+ }
+
+ TValue GetValue(TKey key)
+ {
+ TValue temp;
+ if (!TryGetValue(key, out temp))
+ throw new KeyNotFoundException(key.ToString());
+ return temp;
+ }
+
+ uint Hash(TKey key)
+ {
+ return (uint)_comparer.GetHashCode(key);
+ }
+
+ static KeyValuePair<T, V> Make<T, V>(T key, V value)
+ {
+ return new KeyValuePair<T, V>(key, value);
+ }
+
+ bool Remove(TKey key)
+ {
+ TValue dummy;
+
+ return TryRemove(key, out dummy);
+ }
+
+ class ConcurrentDictionaryEnumerator : IDictionaryEnumerator
+ {
+ readonly IEnumerator<KeyValuePair<TKey, TValue>> _internalEnum;
+
+ public ConcurrentDictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> internalEnum)
+ {
+ _internalEnum = internalEnum;
+ }
+
+ public DictionaryEntry Entry
+ {
+ get
+ {
+ KeyValuePair<TKey, TValue> current = _internalEnum.Current;
+ return new DictionaryEntry(current.Key, current.Value);
+ }
+ }
+
+ public object Key
+ {
+ get { return _internalEnum.Current.Key; }
+ }
+
+ public object Value
+ {
+ get { return _internalEnum.Current.Value; }
+ }
+
+ public object Current
+ {
+ get { return Entry; }
+ }
+
+ public bool MoveNext()
+ {
+ return _internalEnum.MoveNext();
+ }
+
+ public void Reset()
+ {
+ _internalEnum.Reset();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Constraint.cs b/Xamarin.Forms.Core/Constraint.cs
new file mode 100644
index 00000000..bd219f0a
--- /dev/null
+++ b/Xamarin.Forms.Core/Constraint.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(ConstraintTypeConverter))]
+ public sealed class Constraint
+ {
+ Func<RelativeLayout, double> _measureFunc;
+
+ Constraint()
+ {
+ }
+
+ internal IEnumerable<View> RelativeTo { get; set; }
+
+ public static Constraint Constant(double size)
+ {
+ var result = new Constraint { _measureFunc = parent => size };
+
+ return result;
+ }
+
+ public static Constraint FromExpression(Expression<Func<double>> expression)
+ {
+ Func<double> compiled = expression.Compile();
+ var result = new Constraint
+ {
+ _measureFunc = layout => compiled(),
+ RelativeTo = ExpressionSearch.Default.FindObjects<View>(expression).ToArray() // make sure we have our own copy
+ };
+
+ return result;
+ }
+
+ public static Constraint RelativeToParent(Func<RelativeLayout, double> measure)
+ {
+ var result = new Constraint { _measureFunc = measure };
+
+ return result;
+ }
+
+ public static Constraint RelativeToView(View view, Func<RelativeLayout, View, double> measure)
+ {
+ var result = new Constraint { _measureFunc = layout => measure(layout, view), RelativeTo = new[] { view } };
+
+ return result;
+ }
+
+ internal double Compute(RelativeLayout parent)
+ {
+ return _measureFunc(parent);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ConstraintExpression.cs b/Xamarin.Forms.Core/ConstraintExpression.cs
new file mode 100644
index 00000000..b2ca4b8f
--- /dev/null
+++ b/Xamarin.Forms.Core/ConstraintExpression.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ public class ConstraintExpression : IMarkupExtension
+ {
+ public ConstraintExpression()
+ {
+ Factor = 1.0;
+ }
+
+ public double Constant { get; set; }
+
+ public string ElementName { get; set; }
+
+ public double Factor { get; set; }
+
+ public string Property { get; set; }
+
+ public ConstraintType Type { get; set; }
+
+ public object ProvideValue(IServiceProvider serviceProvider)
+ {
+ MethodInfo minfo;
+ switch (Type)
+ {
+ default:
+ case ConstraintType.RelativeToParent:
+ if (string.IsNullOrEmpty(Property))
+ return null;
+ minfo = typeof(View).GetProperties().First(pi => pi.Name == Property && pi.CanRead && pi.GetMethod.IsPublic).GetMethod;
+ return Constraint.RelativeToParent(p => (double)minfo.Invoke(p, new object[] { }) * Factor + Constant);
+ case ConstraintType.Constant:
+ return Constraint.Constant(Constant);
+ case ConstraintType.RelativeToView:
+ if (string.IsNullOrEmpty(Property))
+ return null;
+ if (string.IsNullOrEmpty(ElementName))
+ return null;
+ minfo = typeof(View).GetProperties().First(pi => pi.Name == Property && pi.CanRead && pi.GetMethod.IsPublic).GetMethod;
+ var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
+ if (valueProvider == null || !(valueProvider.TargetObject is INameScope))
+ return null;
+ var view = ((INameScope)valueProvider.TargetObject).FindByName<View>(ElementName);
+ return Constraint.RelativeToView(view, delegate(RelativeLayout p, View v) { return (double)minfo.Invoke(v, new object[] { }) * Factor + Constant; });
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ConstraintType.cs b/Xamarin.Forms.Core/ConstraintType.cs
new file mode 100644
index 00000000..5ee8bc9a
--- /dev/null
+++ b/Xamarin.Forms.Core/ConstraintType.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public enum ConstraintType
+ {
+ RelativeToParent,
+ RelativeToView,
+ Constant
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ConstraintTypeConverter.cs b/Xamarin.Forms.Core/ConstraintTypeConverter.cs
new file mode 100644
index 00000000..8cc45229
--- /dev/null
+++ b/Xamarin.Forms.Core/ConstraintTypeConverter.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class ConstraintTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ double size;
+ if (value != null && double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out size))
+ return Constraint.Constant(size);
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Color)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ContentPage.cs b/Xamarin.Forms.Core/ContentPage.cs
new file mode 100644
index 00000000..01957c39
--- /dev/null
+++ b/Xamarin.Forms.Core/ContentPage.cs
@@ -0,0 +1,26 @@
+namespace Xamarin.Forms
+{
+ [ContentProperty("Content")]
+ public class ContentPage : TemplatedPage
+ {
+ public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(ContentPage), null, propertyChanged: TemplateUtilities.OnContentChanged);
+
+ public View Content
+ {
+ get { return (View)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ View content = Content;
+ ControlTemplate controlTemplate = ControlTemplate;
+ if (content != null && controlTemplate != null)
+ {
+ SetInheritedBindingContext(content, BindingContext);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ContentPresenter.cs b/Xamarin.Forms.Core/ContentPresenter.cs
new file mode 100644
index 00000000..a99a048b
--- /dev/null
+++ b/Xamarin.Forms.Core/ContentPresenter.cs
@@ -0,0 +1,91 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ContentPresenter : Layout
+ {
+ public static BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(View), typeof(ContentPresenter), null, propertyChanged: OnContentChanged);
+
+ public ContentPresenter()
+ {
+ SetBinding(ContentProperty, new TemplateBinding("Content"));
+ }
+
+ public View Content
+ {
+ get { return (View)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ Element element = LogicalChildren[i];
+ var child = element as View;
+ if (child != null)
+ LayoutChildIntoBoundingRegion(child, new Rectangle(x, y, width, height));
+ }
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ double widthRequest = WidthRequest;
+ double heightRequest = HeightRequest;
+ var childRequest = new SizeRequest();
+ if ((widthRequest == -1 || heightRequest == -1) && Content != null)
+ {
+ childRequest = Content.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
+ }
+
+ return new SizeRequest
+ {
+ Request = new Size { Width = widthRequest != -1 ? widthRequest : childRequest.Request.Width, Height = heightRequest != -1 ? heightRequest : childRequest.Request.Height },
+ Minimum = childRequest.Minimum
+ };
+ }
+
+ internal virtual void Clear()
+ {
+ Content = null;
+ }
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ bool isFixedHorizontally = (Constraint & LayoutConstraint.HorizontallyFixed) != 0;
+ bool isFixedVertically = (Constraint & LayoutConstraint.VerticallyFixed) != 0;
+
+ var result = LayoutConstraint.None;
+ if (isFixedVertically && view.VerticalOptions.Alignment == LayoutAlignment.Fill)
+ result |= LayoutConstraint.VerticallyFixed;
+ if (isFixedHorizontally && view.HorizontalOptions.Alignment == LayoutAlignment.Fill)
+ result |= LayoutConstraint.HorizontallyFixed;
+ view.ComputedConstraint = result;
+ }
+
+ internal override void SetChildInheritedBindingContext(Element child, object context)
+ {
+ // We never want to use the standard inheritance mechanism, we will get this set by our parent
+ }
+
+ static async void OnContentChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var self = (ContentPresenter)bindable;
+
+ var oldView = (View)oldValue;
+ var newView = (View)newValue;
+ if (oldView != null)
+ {
+ self.InternalChildren.Remove(oldView);
+ oldView.ParentOverride = null;
+ }
+
+ if (newView != null)
+ {
+ self.InternalChildren.Add(newView);
+ newView.ParentOverride = await TemplateUtilities.FindTemplatedParentAsync((Element)bindable);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ContentPropertyAttribute.cs b/Xamarin.Forms.Core/ContentPropertyAttribute.cs
new file mode 100644
index 00000000..7aa60744
--- /dev/null
+++ b/Xamarin.Forms.Core/ContentPropertyAttribute.cs
@@ -0,0 +1,26 @@
+//
+// ContentPropertyAttribute.cs
+//
+// Author:
+// Stephane Delcroix <stephane@delcroix.org>
+//
+// Copyright (c) 2013 S. Delcroix
+//
+
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class ContentPropertyAttribute : Attribute
+ {
+ internal static string[] ContentPropertyTypes = { "Xamarin.Forms.ContentPropertyAttribute", "System.Windows.Markup.ContentPropertyAttribute" };
+
+ public ContentPropertyAttribute(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ContentView.cs b/Xamarin.Forms.Core/ContentView.cs
new file mode 100644
index 00000000..a30688f9
--- /dev/null
+++ b/Xamarin.Forms.Core/ContentView.cs
@@ -0,0 +1,26 @@
+namespace Xamarin.Forms
+{
+ [ContentProperty("Content")]
+ public class ContentView : TemplatedView
+ {
+ public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(ContentView), null, propertyChanged: TemplateUtilities.OnContentChanged);
+
+ public View Content
+ {
+ get { return (View)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ View content = Content;
+ ControlTemplate controlTemplate = ControlTemplate;
+ if (content != null && controlTemplate != null)
+ {
+ SetInheritedBindingContext(content, BindingContext);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ControlTemplate.cs b/Xamarin.Forms.Core/ControlTemplate.cs
new file mode 100644
index 00000000..1e198e97
--- /dev/null
+++ b/Xamarin.Forms.Core/ControlTemplate.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ControlTemplate : ElementTemplate
+ {
+ public ControlTemplate()
+ {
+ }
+
+ public ControlTemplate(Type type) : base(type)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/CustomKeyboard.cs b/Xamarin.Forms.Core/CustomKeyboard.cs
new file mode 100644
index 00000000..422198cc
--- /dev/null
+++ b/Xamarin.Forms.Core/CustomKeyboard.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms
+{
+ internal sealed class CustomKeyboard : Keyboard
+ {
+ internal CustomKeyboard(KeyboardFlags flags)
+ {
+ Flags = flags;
+ }
+
+ internal KeyboardFlags Flags { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DataTemplate.cs b/Xamarin.Forms.Core/DataTemplate.cs
new file mode 100644
index 00000000..676718a0
--- /dev/null
+++ b/Xamarin.Forms.Core/DataTemplate.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public class DataTemplate : ElementTemplate
+ {
+ public DataTemplate()
+ {
+ }
+
+ public DataTemplate(Type type) : base(type)
+ {
+ }
+
+ public DataTemplate(Func<object> loadTemplate) : base(loadTemplate)
+ {
+ }
+
+ public IDictionary<BindableProperty, BindingBase> Bindings { get; } = new Dictionary<BindableProperty, BindingBase>();
+
+ public IDictionary<BindableProperty, object> Values { get; } = new Dictionary<BindableProperty, object>();
+
+ public void SetBinding(BindableProperty property, BindingBase binding)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+ if (binding == null)
+ throw new ArgumentNullException("binding");
+
+ Values.Remove(property);
+ Bindings[property] = binding;
+ }
+
+ public void SetValue(BindableProperty property, object value)
+ {
+ if (property == null)
+ throw new ArgumentNullException("property");
+
+ Bindings.Remove(property);
+ Values[property] = value;
+ }
+
+ internal override void SetupContent(object item)
+ {
+ ApplyBindings(item);
+ ApplyValues(item);
+ }
+
+ void ApplyBindings(object item)
+ {
+ if (Bindings == null)
+ return;
+
+ var bindable = item as BindableObject;
+ if (bindable == null)
+ return;
+
+ foreach (KeyValuePair<BindableProperty, BindingBase> kvp in Bindings)
+ {
+ if (Values.ContainsKey(kvp.Key))
+ throw new InvalidOperationException("Binding and Value found for " + kvp.Key.PropertyName);
+
+ bindable.SetBinding(kvp.Key, kvp.Value.Clone());
+ }
+ }
+
+ void ApplyValues(object item)
+ {
+ if (Values == null)
+ return;
+
+ var bindable = item as BindableObject;
+ if (bindable == null)
+ return;
+ foreach (KeyValuePair<BindableProperty, object> kvp in Values)
+ bindable.SetValue(kvp.Key, kvp.Value);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DataTemplateExtensions.cs b/Xamarin.Forms.Core/DataTemplateExtensions.cs
new file mode 100644
index 00000000..ffa0ffd0
--- /dev/null
+++ b/Xamarin.Forms.Core/DataTemplateExtensions.cs
@@ -0,0 +1,15 @@
+namespace Xamarin.Forms
+{
+ internal static class DataTemplateExtensions
+ {
+ public static object CreateContent(this DataTemplate self, object item, BindableObject container)
+ {
+ var selector = self as DataTemplateSelector;
+ if (selector != null)
+ {
+ self = selector.SelectTemplate(item, container);
+ }
+ return self.CreateContent();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DataTemplateSelector.cs b/Xamarin.Forms.Core/DataTemplateSelector.cs
new file mode 100644
index 00000000..8ffa4781
--- /dev/null
+++ b/Xamarin.Forms.Core/DataTemplateSelector.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class DataTemplateSelector : DataTemplate
+ {
+ public DataTemplate SelectTemplate(object item, BindableObject container)
+ {
+ DataTemplate result = OnSelectTemplate(item, container);
+ if (result is DataTemplateSelector)
+ throw new NotSupportedException("DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector");
+ return result;
+ }
+
+ protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DateChangedEventArgs.cs b/Xamarin.Forms.Core/DateChangedEventArgs.cs
new file mode 100644
index 00000000..8fbc803d
--- /dev/null
+++ b/Xamarin.Forms.Core/DateChangedEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class DateChangedEventArgs : EventArgs
+ {
+ public DateChangedEventArgs(DateTime oldDate, DateTime newDate)
+ {
+ OldDate = oldDate;
+ NewDate = newDate;
+ }
+
+ public DateTime NewDate { get; private set; }
+
+ public DateTime OldDate { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DatePicker.cs b/Xamarin.Forms.Core/DatePicker.cs
new file mode 100644
index 00000000..15f1c198
--- /dev/null
+++ b/Xamarin.Forms.Core/DatePicker.cs
@@ -0,0 +1,99 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_DatePickerRenderer))]
+ public class DatePicker : View
+ {
+ public static readonly BindableProperty FormatProperty = BindableProperty.Create("Format", typeof(string), typeof(DatePicker), "d");
+
+ public static readonly BindableProperty DateProperty = BindableProperty.Create("Date", typeof(DateTime), typeof(DatePicker), DateTime.Today, BindingMode.TwoWay, coerceValue: CoerceDate,
+ propertyChanged: DatePropertyChanged);
+
+ public static readonly BindableProperty MinimumDateProperty = BindableProperty.Create("MinimumDate", typeof(DateTime), typeof(DatePicker), new DateTime(1900, 1, 1),
+ validateValue: ValidateMinimumDate, coerceValue: CoerceMinimumDate);
+
+ public static readonly BindableProperty MaximumDateProperty = BindableProperty.Create("MaximumDate", typeof(DateTime), typeof(DatePicker), new DateTime(2100, 12, 31),
+ validateValue: ValidateMaximumDate, coerceValue: CoerceMaximumDate);
+
+ public DateTime Date
+ {
+ get { return (DateTime)GetValue(DateProperty); }
+ set { SetValue(DateProperty, value); }
+ }
+
+ public string Format
+ {
+ get { return (string)GetValue(FormatProperty); }
+ set { SetValue(FormatProperty, value); }
+ }
+
+ public DateTime MaximumDate
+ {
+ get { return (DateTime)GetValue(MaximumDateProperty); }
+ set { SetValue(MaximumDateProperty, value); }
+ }
+
+ public DateTime MinimumDate
+ {
+ get { return (DateTime)GetValue(MinimumDateProperty); }
+ set { SetValue(MinimumDateProperty, value); }
+ }
+
+ public event EventHandler<DateChangedEventArgs> DateSelected;
+
+ static object CoerceDate(BindableObject bindable, object value)
+ {
+ var picker = (DatePicker)bindable;
+ DateTime dateValue = ((DateTime)value).Date;
+
+ if (dateValue > picker.MaximumDate)
+ dateValue = picker.MaximumDate;
+
+ if (dateValue < picker.MinimumDate)
+ dateValue = picker.MinimumDate;
+
+ return dateValue;
+ }
+
+ static object CoerceMaximumDate(BindableObject bindable, object value)
+ {
+ DateTime dateValue = ((DateTime)value).Date;
+ var picker = (DatePicker)bindable;
+ if (picker.Date > dateValue)
+ picker.Date = dateValue;
+
+ return dateValue;
+ }
+
+ static object CoerceMinimumDate(BindableObject bindable, object value)
+ {
+ DateTime dateValue = ((DateTime)value).Date;
+ var picker = (DatePicker)bindable;
+ if (picker.Date < dateValue)
+ picker.Date = dateValue;
+
+ return dateValue;
+ }
+
+ static void DatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var datePicker = (DatePicker)bindable;
+ EventHandler<DateChangedEventArgs> selected = datePicker.DateSelected;
+
+ if (selected != null)
+ selected(datePicker, new DateChangedEventArgs((DateTime)oldValue, (DateTime)newValue));
+ }
+
+ static bool ValidateMaximumDate(BindableObject bindable, object value)
+ {
+ return (DateTime)value >= ((DatePicker)bindable).MinimumDate;
+ }
+
+ static bool ValidateMinimumDate(BindableObject bindable, object value)
+ {
+ return (DateTime)value <= ((DatePicker)bindable).MaximumDate;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DefinitionCollection.cs b/Xamarin.Forms.Core/DefinitionCollection.cs
new file mode 100644
index 00000000..bf0e4d06
--- /dev/null
+++ b/Xamarin.Forms.Core/DefinitionCollection.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public class DefinitionCollection<T> : IList<T>, ICollection<T> where T : IDefinition
+ {
+ readonly List<T> _internalList = new List<T>();
+
+ internal DefinitionCollection()
+ {
+ }
+
+ public void Add(T item)
+ {
+ _internalList.Add(item);
+ item.SizeChanged += OnItemSizeChanged;
+ OnItemSizeChanged(this, EventArgs.Empty);
+ }
+
+ public void Clear()
+ {
+ foreach (T item in _internalList)
+ item.SizeChanged -= OnItemSizeChanged;
+ _internalList.Clear();
+ OnItemSizeChanged(this, EventArgs.Empty);
+ }
+
+ public bool Contains(T item)
+ {
+ return _internalList.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ _internalList.CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return _internalList.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return false; }
+ }
+
+ public bool Remove(T item)
+ {
+ item.SizeChanged -= OnItemSizeChanged;
+ bool success = _internalList.Remove(item);
+ if (success)
+ OnItemSizeChanged(this, EventArgs.Empty);
+ return success;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _internalList.GetEnumerator();
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return _internalList.GetEnumerator();
+ }
+
+ public int IndexOf(T item)
+ {
+ return _internalList.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ _internalList.Insert(index, item);
+ item.SizeChanged += OnItemSizeChanged;
+ OnItemSizeChanged(this, EventArgs.Empty);
+ }
+
+ public T this[int index]
+ {
+ get { return _internalList[index]; }
+ set
+ {
+ _internalList[index] = value;
+ value.SizeChanged += OnItemSizeChanged;
+ OnItemSizeChanged(this, EventArgs.Empty);
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ T item = _internalList[index];
+ _internalList.RemoveAt(index);
+ item.SizeChanged -= OnItemSizeChanged;
+ OnItemSizeChanged(this, EventArgs.Empty);
+ }
+
+ public event EventHandler ItemSizeChanged;
+
+ void OnItemSizeChanged(object sender, EventArgs e)
+ {
+ EventHandler eh = ItemSizeChanged;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DelegateLogListener.cs b/Xamarin.Forms.Core/DelegateLogListener.cs
new file mode 100644
index 00000000..20a0ab76
--- /dev/null
+++ b/Xamarin.Forms.Core/DelegateLogListener.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class DelegateLogListener : LogListener
+ {
+ readonly Action<string, string> _log;
+
+ public DelegateLogListener(Action<string, string> log)
+ {
+ if (log == null)
+ throw new ArgumentNullException("log");
+
+ _log = log;
+ }
+
+ public override void Warning(string category, string message)
+ {
+ _log(category, message);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DependencyAttribute.cs b/Xamarin.Forms.Core/DependencyAttribute.cs
new file mode 100644
index 00000000..e0ea22a1
--- /dev/null
+++ b/Xamarin.Forms.Core/DependencyAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public class DependencyAttribute : Attribute
+ {
+ public DependencyAttribute(Type implementorType)
+ {
+ Implementor = implementorType;
+ }
+
+ internal Type Implementor { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DependencyFetchTarget.cs b/Xamarin.Forms.Core/DependencyFetchTarget.cs
new file mode 100644
index 00000000..2433c4f4
--- /dev/null
+++ b/Xamarin.Forms.Core/DependencyFetchTarget.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ public enum DependencyFetchTarget
+ {
+ GlobalInstance,
+ NewInstance
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DependencyService.cs b/Xamarin.Forms.Core/DependencyService.cs
new file mode 100644
index 00000000..d3c43998
--- /dev/null
+++ b/Xamarin.Forms.Core/DependencyService.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ public static class DependencyService
+ {
+ static bool s_initialized;
+
+ static readonly List<Type> DependencyTypes = new List<Type>();
+ static readonly Dictionary<Type, DependencyData> DependencyImplementations = new Dictionary<Type, DependencyData>();
+
+ public static T Get<T>(DependencyFetchTarget fetchTarget = DependencyFetchTarget.GlobalInstance) where T : class
+ {
+ if (!s_initialized)
+ Initialize();
+
+ Type targetType = typeof(T);
+
+ if (!DependencyImplementations.ContainsKey(targetType))
+ {
+ Type implementor = FindImplementor(targetType);
+ DependencyImplementations[targetType] = implementor != null ? new DependencyData { ImplementorType = implementor } : null;
+ }
+
+ DependencyData dependencyImplementation = DependencyImplementations[targetType];
+ if (dependencyImplementation == null)
+ return null;
+
+ if (fetchTarget == DependencyFetchTarget.GlobalInstance)
+ {
+ if (dependencyImplementation.GlobalInstance == null)
+ {
+ dependencyImplementation.GlobalInstance = Activator.CreateInstance(dependencyImplementation.ImplementorType);
+ }
+ return (T)dependencyImplementation.GlobalInstance;
+ }
+ return (T)Activator.CreateInstance(dependencyImplementation.ImplementorType);
+ }
+
+ public static void Register<T>() where T : class
+ {
+ Type type = typeof(T);
+ if (!DependencyTypes.Contains(type))
+ DependencyTypes.Add(type);
+ }
+
+ public static void Register<T, TImpl>() where T : class where TImpl : class, T
+ {
+ Type targetType = typeof(T);
+ Type implementorType = typeof(TImpl);
+ if (!DependencyTypes.Contains(targetType))
+ DependencyTypes.Add(targetType);
+
+ DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType };
+ }
+
+ static Type FindImplementor(Type target)
+ {
+ return DependencyTypes.FirstOrDefault(t => target.IsAssignableFrom(t));
+ }
+
+ static void Initialize()
+ {
+ Assembly[] assemblies = Device.GetAssemblies();
+ if (Registrar.ExtraAssemblies != null)
+ {
+ assemblies = assemblies.Union(Registrar.ExtraAssemblies).ToArray();
+ }
+
+ Type targetAttrType = typeof(DependencyAttribute);
+
+ // Don't use LINQ for performance reasons
+ // Naive implementation can easily take over a second to run
+ foreach (Assembly assembly in assemblies)
+ {
+ Attribute[] attributes = assembly.GetCustomAttributes(targetAttrType).ToArray();
+ if (attributes.Length == 0)
+ continue;
+
+ foreach (DependencyAttribute attribute in attributes)
+ {
+ if (!DependencyTypes.Contains(attribute.Implementor))
+ {
+ DependencyTypes.Add(attribute.Implementor);
+ }
+ }
+ }
+
+ s_initialized = true;
+ }
+
+ class DependencyData
+ {
+ public object GlobalInstance { get; set; }
+
+ public Type ImplementorType { get; set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Device.cs b/Xamarin.Forms.Core/Device.cs
new file mode 100644
index 00000000..db0a2747
--- /dev/null
+++ b/Xamarin.Forms.Core/Device.cs
@@ -0,0 +1,159 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ public static class Device
+ {
+ internal static DeviceInfo info;
+
+ static IPlatformServices s_platformServices;
+
+ public static TargetIdiom Idiom { get; internal set; }
+
+ public static TargetPlatform OS { get; internal set; }
+
+ internal static DeviceInfo Info
+ {
+ get
+ {
+ if (info == null)
+ throw new InvalidOperationException("You MUST call Xamarin.Forms.Init(); prior to using it.");
+ return info;
+ }
+ set { info = value; }
+ }
+
+ internal static bool IsInvokeRequired
+ {
+ get { return PlatformServices.IsInvokeRequired; }
+ }
+
+ internal static IPlatformServices PlatformServices
+ {
+ get
+ {
+ if (s_platformServices == null)
+ throw new InvalidOperationException("You MUST call Xamarin.Forms.Init(); prior to using it.");
+ return s_platformServices;
+ }
+ set { s_platformServices = value; }
+ }
+
+ public static void BeginInvokeOnMainThread(Action action)
+ {
+ PlatformServices.BeginInvokeOnMainThread(action);
+ }
+
+ public static double GetNamedSize(NamedSize size, Element targetElement)
+ {
+ return GetNamedSize(size, targetElement.GetType());
+ }
+
+ public static double GetNamedSize(NamedSize size, Type targetElementType)
+ {
+ return GetNamedSize(size, targetElementType, false);
+ }
+
+ public static void OnPlatform(Action iOS = null, Action Android = null, Action WinPhone = null, Action Default = null)
+ {
+ switch (OS)
+ {
+ case TargetPlatform.iOS:
+ if (iOS != null)
+ iOS();
+ else if (Default != null)
+ Default();
+ break;
+ case TargetPlatform.Android:
+ if (Android != null)
+ Android();
+ else if (Default != null)
+ Default();
+ break;
+ case TargetPlatform.Windows:
+ case TargetPlatform.WinPhone:
+ if (WinPhone != null)
+ WinPhone();
+ else if (Default != null)
+ Default();
+ break;
+ case TargetPlatform.Other:
+ if (Default != null)
+ Default();
+ break;
+ }
+ }
+
+ public static T OnPlatform<T>(T iOS, T Android, T WinPhone)
+ {
+ switch (OS)
+ {
+ case TargetPlatform.iOS:
+ return iOS;
+ case TargetPlatform.Android:
+ return Android;
+ case TargetPlatform.Windows:
+ case TargetPlatform.WinPhone:
+ return WinPhone;
+ }
+
+ return iOS;
+ }
+
+ public static void OpenUri(Uri uri)
+ {
+ PlatformServices.OpenUriAction(uri);
+ }
+
+ public static void StartTimer(TimeSpan interval, Func<bool> callback)
+ {
+ PlatformServices.StartTimer(interval, callback);
+ }
+
+ internal static Assembly[] GetAssemblies()
+ {
+ return PlatformServices.GetAssemblies();
+ }
+
+ internal static double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes)
+ {
+ return PlatformServices.GetNamedSize(size, targetElementType, useOldSizes);
+ }
+
+ internal static Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ return PlatformServices.GetStreamAsync(uri, cancellationToken);
+ }
+
+ public static class Styles
+ {
+ public static readonly string TitleStyleKey = "TitleStyle";
+
+ public static readonly string SubtitleStyleKey = "SubtitleStyle";
+
+ public static readonly string BodyStyleKey = "BodyStyle";
+
+ public static readonly string ListItemTextStyleKey = "ListItemTextStyle";
+
+ public static readonly string ListItemDetailTextStyleKey = "ListItemDetailTextStyle";
+
+ public static readonly string CaptionStyleKey = "CaptionStyle";
+
+ public static readonly Style TitleStyle = new Style(typeof(Label)) { BaseResourceKey = TitleStyleKey };
+
+ public static readonly Style SubtitleStyle = new Style(typeof(Label)) { BaseResourceKey = SubtitleStyleKey };
+
+ public static readonly Style BodyStyle = new Style(typeof(Label)) { BaseResourceKey = BodyStyleKey };
+
+ public static readonly Style ListItemTextStyle = new Style(typeof(Label)) { BaseResourceKey = ListItemTextStyleKey };
+
+ public static readonly Style ListItemDetailTextStyle = new Style(typeof(Label)) { BaseResourceKey = ListItemDetailTextStyleKey };
+
+ public static readonly Style CaptionStyle = new Style(typeof(Label)) { BaseResourceKey = CaptionStyleKey };
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DeviceInfo.cs b/Xamarin.Forms.Core/DeviceInfo.cs
new file mode 100644
index 00000000..dc83075a
--- /dev/null
+++ b/Xamarin.Forms.Core/DeviceInfo.cs
@@ -0,0 +1,51 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ internal abstract class DeviceInfo : INotifyPropertyChanged, IDisposable
+ {
+ DeviceOrientation _currentOrientation;
+ bool _disposed;
+
+ public DeviceOrientation CurrentOrientation
+ {
+ get { return _currentOrientation; }
+ internal set
+ {
+ if (Equals(_currentOrientation, value))
+ return;
+ _currentOrientation = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public abstract Size PixelScreenSize { get; }
+
+ public abstract Size ScaledScreenSize { get; }
+
+ public abstract double ScalingFactor { get; }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+ }
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DeviceOrientation.cs b/Xamarin.Forms.Core/DeviceOrientation.cs
new file mode 100644
index 00000000..53a03f2d
--- /dev/null
+++ b/Xamarin.Forms.Core/DeviceOrientation.cs
@@ -0,0 +1,13 @@
+namespace Xamarin.Forms
+{
+ internal enum DeviceOrientation
+ {
+ Portrait,
+ Landscape,
+ PortraitUp,
+ PortraitDown,
+ LandscapeLeft,
+ LandscapeRight,
+ Other
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/DeviceOrientationExtensions.cs b/Xamarin.Forms.Core/DeviceOrientationExtensions.cs
new file mode 100644
index 00000000..8dbaaa82
--- /dev/null
+++ b/Xamarin.Forms.Core/DeviceOrientationExtensions.cs
@@ -0,0 +1,15 @@
+namespace Xamarin.Forms
+{
+ internal static class DeviceOrientationExtensions
+ {
+ public static bool IsLandscape(this DeviceOrientation orientation)
+ {
+ return orientation == DeviceOrientation.Landscape || orientation == DeviceOrientation.LandscapeLeft || orientation == DeviceOrientation.LandscapeRight;
+ }
+
+ public static bool IsPortrait(this DeviceOrientation orientation)
+ {
+ return orientation == DeviceOrientation.Portrait || orientation == DeviceOrientation.PortraitDown || orientation == DeviceOrientation.PortraitUp;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Easing.cs b/Xamarin.Forms.Core/Easing.cs
new file mode 100644
index 00000000..8d64e255
--- /dev/null
+++ b/Xamarin.Forms.Core/Easing.cs
@@ -0,0 +1,98 @@
+//
+// Tweener.cs
+//
+// Author:
+// Jason Smith <jason.smith@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Xamarin.Forms
+{
+ public class Easing
+ {
+ public static readonly Easing Linear = new Easing(x => x);
+
+ public static readonly Easing SinOut = new Easing(x => Math.Sin(x * Math.PI * 0.5f));
+ public static readonly Easing SinIn = new Easing(x => 1.0f - Math.Cos(x * Math.PI * 0.5f));
+ public static readonly Easing SinInOut = new Easing(x => -Math.Cos(Math.PI * x) / 2.0f + 0.5f);
+
+ public static readonly Easing CubicIn = new Easing(x => x * x * x);
+ public static readonly Easing CubicOut = new Easing(x => Math.Pow(x - 1.0f, 3.0f) + 1.0f);
+
+ public static readonly Easing CubicInOut = new Easing(x => x < 0.5f ? Math.Pow(x * 2.0f, 3.0f) / 2.0f : (Math.Pow((x - 1) * 2.0f, 3.0f) + 2.0f) / 2.0f);
+
+ public static readonly Easing BounceOut;
+ public static readonly Easing BounceIn;
+
+ public static readonly Easing SpringIn = new Easing(x => x * x * ((1.70158f + 1) * x - 1.70158f));
+ public static readonly Easing SpringOut = new Easing(x => (x - 1) * (x - 1) * ((1.70158f + 1) * (x - 1) + 1.70158f) + 1);
+
+ readonly Func<double, double> _easingFunc;
+
+ static Easing()
+ {
+ BounceOut = new Easing(p =>
+ {
+ if (p < 1 / 2.75f)
+ {
+ return 7.5625f * p * p;
+ }
+ if (p < 2 / 2.75f)
+ {
+ p -= 1.5f / 2.75f;
+
+ return 7.5625f * p * p + .75f;
+ }
+ if (p < 2.5f / 2.75f)
+ {
+ p -= 2.25f / 2.75f;
+
+ return 7.5625f * p * p + .9375f;
+ }
+ p -= 2.625f / 2.75f;
+
+ return 7.5625f * p * p + .984375f;
+ });
+
+ BounceIn = new Easing(p => 1.0f - BounceOut.Ease(1 - p));
+ }
+
+ public Easing(Func<double, double> easingFunc)
+ {
+ if (easingFunc == null)
+ throw new ArgumentNullException("easingFunc");
+
+ _easingFunc = easingFunc;
+ }
+
+ public double Ease(double v)
+ {
+ return _easingFunc(v);
+ }
+
+ public static implicit operator Easing(Func<double, double> func)
+ {
+ return new Easing(func);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Editor.cs b/Xamarin.Forms.Core/Editor.cs
new file mode 100644
index 00000000..949c0865
--- /dev/null
+++ b/Xamarin.Forms.Core/Editor.cs
@@ -0,0 +1,67 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_EditorRenderer))]
+ public class Editor : InputView, IFontElement
+ {
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Editor), null, BindingMode.TwoWay, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var editor = (Editor)bindable;
+ if (editor.TextChanged != null)
+ editor.TextChanged(editor, new TextChangedEventArgs((string)oldValue, (string)newValue));
+ });
+
+ public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Editor), default(string));
+
+ public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Editor), -1.0,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Editor)bindable));
+
+ public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Editor), FontAttributes.None);
+
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Editor), Color.Default);
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ public event EventHandler Completed;
+
+ public event EventHandler<TextChangedEventArgs> TextChanged;
+
+ internal void SendCompleted()
+ {
+ EventHandler handler = Completed;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Effect.cs b/Xamarin.Forms.Core/Effect.cs
new file mode 100644
index 00000000..9e269118
--- /dev/null
+++ b/Xamarin.Forms.Core/Effect.cs
@@ -0,0 +1,70 @@
+using System;
+using System.ComponentModel;
+
+namespace Xamarin.Forms
+{
+ public abstract class Effect
+ {
+ internal Effect()
+ {
+ }
+
+ public Element Element { get; internal set; }
+
+ public bool IsAttached { get; private set; }
+
+ public string ResolveId { get; internal set; }
+
+ #region Statics
+
+ public static Effect Resolve(string name)
+ {
+ Type effectType;
+ Effect result = null;
+ if (Registrar.Effects.TryGetValue(name, out effectType))
+ {
+ result = (Effect)Activator.CreateInstance(effectType);
+ }
+
+ if (result == null)
+ result = new NullEffect();
+ result.ResolveId = name;
+ return result;
+ }
+
+ #endregion
+
+ // Received after Control/Container/Element made valid
+ protected abstract void OnAttached();
+
+ // Received after Control/Container made invalid
+ protected abstract void OnDetached();
+
+ internal virtual void ClearEffect()
+ {
+ if (IsAttached)
+ SendDetached();
+ Element = null;
+ }
+
+ internal virtual void SendAttached()
+ {
+ if (IsAttached)
+ return;
+ OnAttached();
+ IsAttached = true;
+ }
+
+ internal virtual void SendDetached()
+ {
+ if (!IsAttached)
+ return;
+ OnDetached();
+ IsAttached = false;
+ }
+
+ internal virtual void SendOnElementPropertyChanged(PropertyChangedEventArgs args)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Element.cs b/Xamarin.Forms.Core/Element.cs
new file mode 100644
index 00000000..a85bb3fb
--- /dev/null
+++ b/Xamarin.Forms.Core/Element.cs
@@ -0,0 +1,584 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Xml;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+ public abstract class Element : BindableObject, IElement, INameScope, IElementController
+ {
+ internal static readonly ReadOnlyCollection<Element> EmptyChildren = new ReadOnlyCollection<Element>(new Element[0]);
+
+ public static readonly BindableProperty ClassIdProperty = BindableProperty.Create("ClassId", typeof(string), typeof(View), null);
+
+ string _automationId;
+
+ List<Action<object, ResourcesChangedEventArgs>> _changeHandlers;
+
+ List<KeyValuePair<string, BindableProperty>> _dynamicResources;
+
+ IEffectControlProvider _effectControlProvider;
+
+ TrackableCollection<Effect> _effects;
+
+ Guid? _id;
+
+ Element _parentOverride;
+
+ IPlatform _platform;
+
+ string _styleId;
+
+ public string AutomationId
+ {
+ get { return _automationId; }
+ set
+ {
+ if (_automationId != null)
+ throw new InvalidOperationException("AutomationId may only be set one time");
+ _automationId = value;
+ }
+ }
+
+ public string ClassId
+ {
+ get { return (string)GetValue(ClassIdProperty); }
+ set { SetValue(ClassIdProperty, value); }
+ }
+
+ public IList<Effect> Effects
+ {
+ get
+ {
+ if (_effects == null)
+ {
+ _effects = new TrackableCollection<Effect>();
+ _effects.CollectionChanged += EffectsOnCollectionChanged;
+ _effects.Clearing += EffectsOnClearing;
+ }
+ return _effects;
+ }
+ }
+
+ public Guid Id
+ {
+ get
+ {
+ if (!_id.HasValue)
+ _id = Guid.NewGuid();
+ return _id.Value;
+ }
+ }
+
+ [Obsolete("Use Parent")]
+ public VisualElement ParentView
+ {
+ get
+ {
+ Element parent = Parent;
+ while (parent != null)
+ {
+ var parentView = parent as VisualElement;
+ if (parentView != null)
+ return parentView;
+ parent = parent.RealParent;
+ }
+ return null;
+ }
+ }
+
+ public string StyleId
+ {
+ get { return _styleId; }
+ set
+ {
+ if (_styleId == value)
+ return;
+
+ OnPropertyChanging();
+ _styleId = value;
+ OnPropertyChanged();
+ }
+ }
+
+ internal virtual ReadOnlyCollection<Element> LogicalChildren
+ {
+ get { return EmptyChildren; }
+ }
+
+ internal bool Owned { get; set; }
+
+ internal Element ParentOverride
+ {
+ get { return _parentOverride; }
+ set
+ {
+ if (_parentOverride == value)
+ return;
+
+ bool emitChange = Parent != value;
+
+ if (emitChange)
+ OnPropertyChanging(nameof(Parent));
+
+ _parentOverride = value;
+
+ if (emitChange)
+ OnPropertyChanged(nameof(Parent));
+ }
+ }
+
+ internal IPlatform Platform
+ {
+ get
+ {
+ if (_platform == null && RealParent != null)
+ return RealParent.Platform;
+ return _platform;
+ }
+ set
+ {
+ if (_platform == value)
+ return;
+ _platform = value;
+ if (PlatformSet != null)
+ PlatformSet(this, EventArgs.Empty);
+ foreach (Element descendant in Descendants())
+ {
+ descendant._platform = _platform;
+ if (descendant.PlatformSet != null)
+ descendant.PlatformSet(this, EventArgs.Empty);
+ }
+ }
+ }
+
+ // you're not my real dad
+ internal Element RealParent { get; private set; }
+
+ List<KeyValuePair<string, BindableProperty>> DynamicResources
+ {
+ get { return _dynamicResources ?? (_dynamicResources = new List<KeyValuePair<string, BindableProperty>>(4)); }
+ }
+
+ void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
+ {
+ _changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(2);
+ _changeHandlers.Add(onchanged);
+ }
+
+ public Element Parent
+ {
+ get { return _parentOverride ?? RealParent; }
+ set
+ {
+ if (RealParent == value)
+ return;
+
+ OnPropertyChanging();
+
+ if (RealParent != null)
+ ((IElement)RealParent).RemoveResourcesChangedListener(OnParentResourcesChanged);
+ RealParent = value;
+ if (RealParent != null)
+ {
+ OnParentResourcesChanged(RealParent.GetMergedResources());
+ ((IElement)RealParent).AddResourcesChangedListener(OnParentResourcesChanged);
+ }
+
+ object context = value != null ? value.BindingContext : null;
+ if (value != null)
+ {
+ value.SetChildInheritedBindingContext(this, context);
+ }
+ else
+ {
+ SetInheritedBindingContext(this, null);
+ }
+
+ OnParentSet();
+
+ if (RealParent != null)
+ {
+ IPlatform platform = RealParent.Platform;
+ if (platform != null)
+ Platform = platform;
+ }
+
+ OnPropertyChanged();
+ }
+ }
+
+ void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
+ {
+ if (_changeHandlers == null)
+ return;
+ _changeHandlers.Remove(onchanged);
+ }
+
+ IEffectControlProvider IElementController.EffectControlProvider
+ {
+ get { return _effectControlProvider; }
+ set
+ {
+ if (_effectControlProvider == value)
+ return;
+ if (_effectControlProvider != null && _effects != null)
+ {
+ foreach (Effect effect in _effects)
+ effect?.SendDetached();
+ }
+ _effectControlProvider = value;
+ if (_effectControlProvider != null && _effects != null)
+ {
+ foreach (Effect effect in _effects)
+ {
+ if (effect != null)
+ AttachEffect(effect);
+ }
+ }
+ }
+ }
+
+ void IElementController.SetValueFromRenderer(BindableProperty property, object value)
+ {
+ SetValueCore(property, value);
+ }
+
+ void IElementController.SetValueFromRenderer(BindablePropertyKey property, object value)
+ {
+ SetValueCore(property, value);
+ }
+
+ object INameScope.FindByName(string name)
+ {
+ INameScope namescope = GetNameScope();
+ if (namescope == null)
+ throw new InvalidOperationException("this element is not in a namescope");
+ return namescope.FindByName(name);
+ }
+
+ void INameScope.RegisterName(string name, object scopedElement)
+ {
+ INameScope namescope = GetNameScope();
+ if (namescope == null)
+ throw new InvalidOperationException("this element is not in a namescope");
+ namescope.RegisterName(name, scopedElement);
+ }
+
+ void INameScope.RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo)
+ {
+ INameScope namescope = GetNameScope();
+ if (namescope == null)
+ throw new InvalidOperationException("this element is not in a namescope");
+ namescope.RegisterName(name, scopedElement, xmlLineInfo);
+ }
+
+ void INameScope.UnregisterName(string name)
+ {
+ INameScope namescope = GetNameScope();
+ if (namescope == null)
+ throw new InvalidOperationException("this element is not in a namescope");
+ namescope.UnregisterName(name);
+ }
+
+ public event EventHandler<ElementEventArgs> ChildAdded;
+
+ public event EventHandler<ElementEventArgs> ChildRemoved;
+
+ public event EventHandler<ElementEventArgs> DescendantAdded;
+
+ public event EventHandler<ElementEventArgs> DescendantRemoved;
+
+ public new void RemoveDynamicResource(BindableProperty property)
+ {
+ base.RemoveDynamicResource(property);
+ }
+
+ public new void SetDynamicResource(BindableProperty property, string key)
+ {
+ base.SetDynamicResource(property, key);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ var gotBindingContext = false;
+ object bc = null;
+
+ for (var index = 0; index < LogicalChildren.Count; index++)
+ {
+ Element child = LogicalChildren[index];
+
+ if (!gotBindingContext)
+ {
+ bc = BindingContext;
+ gotBindingContext = true;
+ }
+
+ SetChildInheritedBindingContext(child, bc);
+ }
+
+ base.OnBindingContextChanged();
+ }
+
+ protected virtual void OnChildAdded(Element child)
+ {
+ child.Parent = this;
+ if (Platform != null)
+ child.Platform = Platform;
+
+ child.ApplyBindings();
+
+ if (ChildAdded != null)
+ ChildAdded(this, new ElementEventArgs(child));
+
+ OnDescendantAdded(child);
+ foreach (Element element in child.Descendants())
+ OnDescendantAdded(element);
+ }
+
+ protected virtual void OnChildRemoved(Element child)
+ {
+ child.Parent = null;
+
+ if (ChildRemoved != null)
+ ChildRemoved(child, new ElementEventArgs(child));
+
+ OnDescendantRemoved(child);
+ foreach (Element element in child.Descendants())
+ OnDescendantRemoved(element);
+ }
+
+ protected virtual void OnParentSet()
+ {
+ ParentSet?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (_effects == null || _effects.Count == 0)
+ return;
+
+ var args = new PropertyChangedEventArgs(propertyName);
+ foreach (Effect effect in _effects)
+ {
+ effect?.SendOnElementPropertyChanged(args);
+ }
+ }
+
+ internal IEnumerable<Element> Descendants()
+ {
+ var queue = new Queue<Element>(16);
+ queue.Enqueue(this);
+
+ while (queue.Count > 0)
+ {
+ ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren;
+ for (var i = 0; i < children.Count; i++)
+ {
+ Element child = children[i];
+ yield return child;
+ queue.Enqueue(child);
+ }
+ }
+ }
+
+ internal void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ OnParentResourcesChanged(e.Values);
+ }
+
+ internal virtual void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
+ {
+ OnResourcesChanged(values);
+ }
+
+ internal override void OnRemoveDynamicResource(BindableProperty property)
+ {
+ DynamicResources.RemoveAll(kvp => kvp.Value == property);
+ if (DynamicResources.Count == 0)
+ _dynamicResources = null;
+ base.OnRemoveDynamicResource(property);
+ }
+
+ internal void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ OnResourcesChanged(e.Values);
+ }
+
+ internal void OnResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
+ {
+ if (values == null)
+ return;
+ if (_changeHandlers != null)
+ foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers)
+ handler(this, new ResourcesChangedEventArgs(values));
+ if (_dynamicResources == null)
+ return;
+ foreach (KeyValuePair<string, object> value in values)
+ {
+ List<BindableProperty> changedResources = null;
+ foreach (KeyValuePair<string, BindableProperty> dynR in DynamicResources)
+ {
+ if (dynR.Key != value.Key)
+ continue;
+ changedResources = changedResources ?? new List<BindableProperty>();
+ changedResources.Add(dynR.Value);
+ }
+ if (changedResources == null)
+ continue;
+ foreach (BindableProperty changedResource in changedResources)
+ OnResourceChanged(changedResource, value.Value);
+ }
+ }
+
+ internal override void OnSetDynamicResource(BindableProperty property, string key)
+ {
+ base.OnSetDynamicResource(property, key);
+ DynamicResources.Add(new KeyValuePair<string, BindableProperty>(key, property));
+ object value;
+ if (this.TryGetResource(key, out value))
+ OnResourceChanged(property, value);
+ }
+
+ internal event EventHandler ParentSet;
+
+ internal event EventHandler PlatformSet;
+
+ internal virtual void SetChildInheritedBindingContext(Element child, object context)
+ {
+ SetInheritedBindingContext(child, context);
+ }
+
+ internal IEnumerable<Element> VisibleDescendants()
+ {
+ var queue = new Queue<Element>(16);
+ queue.Enqueue(this);
+
+ while (queue.Count > 0)
+ {
+ ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren;
+ for (var i = 0; i < children.Count; i++)
+ {
+ var child = children[i] as VisualElement;
+ if (child == null || !child.IsVisible)
+ continue;
+ yield return child;
+ queue.Enqueue(child);
+ }
+ }
+ }
+
+ void AttachEffect(Effect effect)
+ {
+ if (_effectControlProvider == null)
+ return;
+ if (effect.IsAttached)
+ throw new InvalidOperationException("Cannot attach Effect to multiple sources");
+
+ Effect effectToRegister = effect;
+ if (effect is RoutingEffect)
+ effectToRegister = ((RoutingEffect)effect).Inner;
+ _effectControlProvider.RegisterEffect(effectToRegister);
+ effectToRegister.Element = this;
+ effect.SendAttached();
+ }
+
+ void EffectsOnClearing(object sender, EventArgs eventArgs)
+ {
+ foreach (Effect effect in _effects)
+ {
+ effect.ClearEffect();
+ }
+ }
+
+ void EffectsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ foreach (Effect effect in e.NewItems)
+ {
+ AttachEffect(effect);
+ }
+ break;
+ case NotifyCollectionChangedAction.Move:
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ foreach (Effect effect in e.OldItems)
+ {
+ effect.ClearEffect();
+ }
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ foreach (Effect effect in e.NewItems)
+ {
+ AttachEffect(effect);
+ }
+ foreach (Effect effect in e.OldItems)
+ {
+ effect.ClearEffect();
+ }
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ if (e.NewItems != null)
+ {
+ foreach (Effect effect in e.NewItems)
+ {
+ AttachEffect(effect);
+ }
+ }
+ if (e.OldItems != null)
+ {
+ foreach (Effect effect in e.OldItems)
+ {
+ effect.ClearEffect();
+ }
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ INameScope GetNameScope()
+ {
+ INameScope namescope = NameScope.GetNameScope(this);
+ Element p = RealParent;
+ while (namescope == null && p != null)
+ {
+ namescope = NameScope.GetNameScope(p);
+ p = p.RealParent;
+ }
+ return namescope;
+ }
+
+ void OnDescendantAdded(Element child)
+ {
+ if (DescendantAdded != null)
+ DescendantAdded(this, new ElementEventArgs(child));
+
+ if (RealParent != null)
+ RealParent.OnDescendantAdded(child);
+ }
+
+ void OnDescendantRemoved(Element child)
+ {
+ if (DescendantRemoved != null)
+ DescendantRemoved(this, new ElementEventArgs(child));
+
+ if (RealParent != null)
+ RealParent.OnDescendantRemoved(child);
+ }
+
+ void OnResourceChanged(BindableProperty property, object value)
+ {
+ SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearTwoWayBindings);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ElementCollection.cs b/Xamarin.Forms.Core/ElementCollection.cs
new file mode 100644
index 00000000..37520ab6
--- /dev/null
+++ b/Xamarin.Forms.Core/ElementCollection.cs
@@ -0,0 +1,11 @@
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms
+{
+ internal class ElementCollection<T> : ObservableWrapper<Element, T> where T : Element
+ {
+ public ElementCollection(ObservableCollection<Element> list) : base(list)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ElementEventArgs.cs b/Xamarin.Forms.Core/ElementEventArgs.cs
new file mode 100644
index 00000000..34af4e0e
--- /dev/null
+++ b/Xamarin.Forms.Core/ElementEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ElementEventArgs : EventArgs
+ {
+ public ElementEventArgs(Element element)
+ {
+ if (element == null)
+ throw new ArgumentNullException("element");
+
+ Element = element;
+ }
+
+ public Element Element { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ElementTemplate.cs b/Xamarin.Forms.Core/ElementTemplate.cs
new file mode 100644
index 00000000..016dee7e
--- /dev/null
+++ b/Xamarin.Forms.Core/ElementTemplate.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+#pragma warning disable 612
+ public class ElementTemplate : IElement, IDataTemplate
+#pragma warning restore 612
+ {
+ List<Action<object, ResourcesChangedEventArgs>> _changeHandlers;
+ Element _parent;
+
+ internal ElementTemplate()
+ {
+ }
+
+ internal ElementTemplate(Type type) : this()
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+
+ LoadTemplate = () => Activator.CreateInstance(type);
+ }
+
+ internal ElementTemplate(Func<object> loadTemplate) : this()
+ {
+ if (loadTemplate == null)
+ throw new ArgumentNullException("loadTemplate");
+
+ LoadTemplate = loadTemplate;
+ }
+
+ Func<object> LoadTemplate { get; set; }
+#pragma warning disable 0612
+ Func<object> IDataTemplate.LoadTemplate
+ {
+#pragma warning restore 0612
+ get { return LoadTemplate; }
+ set { LoadTemplate = value; }
+ }
+
+ void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
+ {
+ _changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(1);
+ _changeHandlers.Add(onchanged);
+ }
+
+ Element IElement.Parent
+ {
+ get { return _parent; }
+ set
+ {
+ if (_parent == value)
+ return;
+ if (_parent != null)
+ ((IElement)_parent).RemoveResourcesChangedListener(OnResourcesChanged);
+ _parent = value;
+ if (_parent != null)
+ ((IElement)_parent).AddResourcesChangedListener(OnResourcesChanged);
+ }
+ }
+
+ void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
+ {
+ if (_changeHandlers == null)
+ return;
+ _changeHandlers.Remove(onchanged);
+ }
+
+ public object CreateContent()
+ {
+ if (LoadTemplate == null)
+ throw new InvalidOperationException("LoadTemplate should not be null");
+ if (this is DataTemplateSelector)
+ throw new InvalidOperationException("Cannot call CreateContent directly on a DataTemplateSelector");
+
+ object item = LoadTemplate();
+ SetupContent(item);
+
+ return item;
+ }
+
+ internal virtual void SetupContent(object item)
+ {
+ }
+
+ void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ if (_changeHandlers == null)
+ return;
+ foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers)
+ handler(this, e);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/EmailKeyboard.cs b/Xamarin.Forms.Core/EmailKeyboard.cs
new file mode 100644
index 00000000..1911fd3c
--- /dev/null
+++ b/Xamarin.Forms.Core/EmailKeyboard.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal sealed class EmailKeyboard : Keyboard
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Entry.cs b/Xamarin.Forms.Core/Entry.cs
new file mode 100644
index 00000000..ef10e963
--- /dev/null
+++ b/Xamarin.Forms.Core/Entry.cs
@@ -0,0 +1,99 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_EntryRenderer))]
+ public class Entry : InputView, IFontElement
+ {
+ public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(Entry), default(string));
+
+ public static readonly BindableProperty IsPasswordProperty = BindableProperty.Create("IsPassword", typeof(bool), typeof(Entry), default(bool));
+
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Entry), null, BindingMode.TwoWay, propertyChanged: OnTextChanged);
+
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Entry), Color.Default);
+
+ public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(Entry), TextAlignment.Start);
+
+ public static readonly BindableProperty PlaceholderColorProperty = BindableProperty.Create("PlaceholderColor", typeof(Color), typeof(Entry), Color.Default);
+
+ public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Entry), default(string));
+
+ public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Entry), -1.0,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Entry)bindable));
+
+ public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Entry), FontAttributes.None);
+
+ public TextAlignment HorizontalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
+ set { SetValue(HorizontalTextAlignmentProperty, value); }
+ }
+
+ public bool IsPassword
+ {
+ get { return (bool)GetValue(IsPasswordProperty); }
+ set { SetValue(IsPasswordProperty, value); }
+ }
+
+ public string Placeholder
+ {
+ get { return (string)GetValue(PlaceholderProperty); }
+ set { SetValue(PlaceholderProperty, value); }
+ }
+
+ public Color PlaceholderColor
+ {
+ get { return (Color)GetValue(PlaceholderColorProperty); }
+ set { SetValue(PlaceholderColorProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ public event EventHandler Completed;
+
+ public event EventHandler<TextChangedEventArgs> TextChanged;
+
+ internal void SendCompleted()
+ {
+ Completed?.Invoke(this, EventArgs.Empty);
+ }
+
+ static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var entry = (Entry)bindable;
+
+ entry.TextChanged?.Invoke(entry, new TextChangedEventArgs((string)oldValue, (string)newValue));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/EnumerableExtensions.cs b/Xamarin.Forms.Core/EnumerableExtensions.cs
new file mode 100644
index 00000000..066e7e91
--- /dev/null
+++ b/Xamarin.Forms.Core/EnumerableExtensions.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal static class EnumerableExtensions
+ {
+ public static IEnumerable<T> GetGesturesFor<T>(this IEnumerable<IGestureRecognizer> gestures, Func<T, bool> predicate = null) where T : GestureRecognizer
+ {
+ if (gestures == null)
+ yield break;
+
+ if (predicate == null)
+ predicate = x => true;
+
+ foreach (IGestureRecognizer item in gestures)
+ {
+ var gesture = item as T;
+ if (gesture != null && predicate(gesture))
+ {
+ yield return gesture;
+ }
+ }
+ }
+
+ internal static IEnumerable<T> Append<T>(this IEnumerable<T> enumerable, T item)
+ {
+ foreach (T x in enumerable)
+ yield return x;
+
+ yield return item;
+ }
+
+ internal static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
+ {
+ foreach (T item in enumeration)
+ {
+ action(item);
+ }
+ }
+
+ internal static int IndexOf<T>(this IEnumerable<T> enumerable, T item)
+ {
+ if (enumerable == null)
+ throw new ArgumentNullException("enumerable");
+
+ var i = 0;
+ foreach (T element in enumerable)
+ {
+ if (Equals(element, item))
+ return i;
+
+ i++;
+ }
+
+ return -1;
+ }
+
+ internal static int IndexOf<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
+ {
+ var i = 0;
+ foreach (T element in enumerable)
+ {
+ if (predicate(element))
+ return i;
+
+ i++;
+ }
+
+ return -1;
+ }
+
+ internal static IEnumerable<T> Prepend<T>(this IEnumerable<T> enumerable, T item)
+ {
+ yield return item;
+
+ foreach (T x in enumerable)
+ yield return x;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/EventArg.cs b/Xamarin.Forms.Core/EventArg.cs
new file mode 100644
index 00000000..9b9ea0a1
--- /dev/null
+++ b/Xamarin.Forms.Core/EventArg.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class EventArg<T> : EventArgs
+ {
+ // Property variable
+
+ // Constructor
+ public EventArg(T data)
+ {
+ Data = data;
+ }
+
+ // Property for EventArgs argument
+ public T Data { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ExportEffectAttribute.cs b/Xamarin.Forms.Core/ExportEffectAttribute.cs
new file mode 100644
index 00000000..35869570
--- /dev/null
+++ b/Xamarin.Forms.Core/ExportEffectAttribute.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public class ExportEffectAttribute : Attribute
+ {
+ public ExportEffectAttribute(Type effectType, string uniqueName)
+ {
+ if (uniqueName.Contains("."))
+ throw new ArgumentException("uniqueName must not contain a .");
+ Type = effectType;
+ Id = uniqueName;
+ }
+
+ internal string Id { get; private set; }
+
+ internal Type Type { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ExpressionSearch.cs b/Xamarin.Forms.Core/ExpressionSearch.cs
new file mode 100644
index 00000000..fbbe80a8
--- /dev/null
+++ b/Xamarin.Forms.Core/ExpressionSearch.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ internal abstract class ExpressionSearch
+ {
+ internal static IExpressionSearch Default { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FileAccess.cs b/Xamarin.Forms.Core/FileAccess.cs
new file mode 100644
index 00000000..3636b733
--- /dev/null
+++ b/Xamarin.Forms.Core/FileAccess.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ internal enum FileAccess
+ {
+ Read = 0x00000001,
+ Write = 0x00000002,
+ ReadWrite = Read | Write
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FileImageSource.cs b/Xamarin.Forms.Core/FileImageSource.cs
new file mode 100644
index 00000000..9e1d1e73
--- /dev/null
+++ b/Xamarin.Forms.Core/FileImageSource.cs
@@ -0,0 +1,38 @@
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(FileImageSourceConverter))]
+ public sealed class FileImageSource : ImageSource
+ {
+ public static readonly BindableProperty FileProperty = BindableProperty.Create("File", typeof(string), typeof(FileImageSource), default(string));
+
+ public string File
+ {
+ get { return (string)GetValue(FileProperty); }
+ set { SetValue(FileProperty, value); }
+ }
+
+ public override Task<bool> Cancel()
+ {
+ return Task.FromResult(false);
+ }
+
+ public static implicit operator FileImageSource(string file)
+ {
+ return (FileImageSource)FromFile(file);
+ }
+
+ public static implicit operator string(FileImageSource file)
+ {
+ return file != null ? file.File : null;
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ if (propertyName == FileProperty.PropertyName)
+ OnSourceChanged();
+ base.OnPropertyChanged(propertyName);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FileImageSourceConverter.cs b/Xamarin.Forms.Core/FileImageSourceConverter.cs
new file mode 100644
index 00000000..25a0e22e
--- /dev/null
+++ b/Xamarin.Forms.Core/FileImageSourceConverter.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class FileImageSourceConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ return (FileImageSource)ImageSource.FromFile(value);
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(FileImageSource)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FileMode.cs b/Xamarin.Forms.Core/FileMode.cs
new file mode 100644
index 00000000..31369872
--- /dev/null
+++ b/Xamarin.Forms.Core/FileMode.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms
+{
+ internal enum FileMode
+ {
+ CreateNew = 1,
+ Create = 2,
+ Open = 3,
+ OpenOrCreate = 4,
+ Truncate = 5,
+ Append = 6
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FileShare.cs b/Xamarin.Forms.Core/FileShare.cs
new file mode 100644
index 00000000..bf9fc717
--- /dev/null
+++ b/Xamarin.Forms.Core/FileShare.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ internal enum FileShare
+ {
+ None = 0,
+ Read = 1,
+ Write = 2,
+ ReadWrite = 3,
+ Delete = 4,
+ Inheritable = 16
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FocusEventArgs.cs b/Xamarin.Forms.Core/FocusEventArgs.cs
new file mode 100644
index 00000000..304fd823
--- /dev/null
+++ b/Xamarin.Forms.Core/FocusEventArgs.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class FocusEventArgs : EventArgs
+ {
+ public FocusEventArgs(VisualElement visualElement, bool isFocused)
+ {
+ if (visualElement == null)
+ throw new ArgumentNullException("visualElement");
+
+ VisualElement = visualElement;
+ IsFocused = isFocused;
+ }
+
+ public bool IsFocused { get; private set; }
+
+ public VisualElement VisualElement { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Font.cs b/Xamarin.Forms.Core/Font.cs
new file mode 100644
index 00000000..bb6da4f4
--- /dev/null
+++ b/Xamarin.Forms.Core/Font.cs
@@ -0,0 +1,145 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(FontTypeConverter))]
+ public struct Font
+ {
+ public string FontFamily { get; private set; }
+
+ public double FontSize { get; private set; }
+
+ public NamedSize NamedSize { get; private set; }
+
+ public FontAttributes FontAttributes { get; private set; }
+
+ public bool IsDefault
+ {
+ get { return FontFamily == null && FontSize == 0 && NamedSize == NamedSize.Default && FontAttributes == FontAttributes.None; }
+ }
+
+ public bool UseNamedSize
+ {
+ get { return FontSize <= 0; }
+ }
+
+ public static Font Default
+ {
+ get { return default(Font); }
+ }
+
+ public Font WithSize(double size)
+ {
+ return new Font { FontFamily = FontFamily, FontSize = size, NamedSize = 0, FontAttributes = FontAttributes };
+ }
+
+ public Font WithSize(NamedSize size)
+ {
+ if (size <= 0)
+ throw new ArgumentOutOfRangeException("size");
+
+ return new Font { FontFamily = FontFamily, FontSize = 0, NamedSize = size, FontAttributes = FontAttributes };
+ }
+
+ public Font WithAttributes(FontAttributes fontAttributes)
+ {
+ return new Font { FontFamily = FontFamily, FontSize = FontSize, NamedSize = NamedSize, FontAttributes = fontAttributes };
+ }
+
+ public static Font OfSize(string name, double size)
+ {
+ var result = new Font { FontFamily = name, FontSize = size };
+ return result;
+ }
+
+ public static Font OfSize(string name, NamedSize size)
+ {
+ var result = new Font { FontFamily = name, NamedSize = size };
+ return result;
+ }
+
+ public static Font SystemFontOfSize(double size)
+ {
+ var result = new Font { FontSize = size };
+ return result;
+ }
+
+ public static Font SystemFontOfSize(NamedSize size)
+ {
+ var result = new Font { NamedSize = size };
+ return result;
+ }
+
+ public static Font SystemFontOfSize(double size, FontAttributes attributes)
+ {
+ var result = new Font { FontSize = size, FontAttributes = attributes };
+ return result;
+ }
+
+ public static Font SystemFontOfSize(NamedSize size, FontAttributes attributes)
+ {
+ var result = new Font { NamedSize = size, FontAttributes = attributes };
+ return result;
+ }
+
+ [Obsolete("BoldSystemFontOfSize is obsolete, please use SystemFontOfSize (double, FontAttributes)")]
+ public static Font BoldSystemFontOfSize(double size)
+ {
+ var result = new Font { FontSize = size, FontAttributes = FontAttributes.Bold };
+ return result;
+ }
+
+ [Obsolete("BoldSystemFontOfSize is obsolete, please use SystemFontOfSize (NamedSize, FontAttributes)")]
+ public static Font BoldSystemFontOfSize(NamedSize size)
+ {
+ var result = new Font { NamedSize = size, FontAttributes = FontAttributes.Bold };
+ return result;
+ }
+
+ bool Equals(Font other)
+ {
+ return string.Equals(FontFamily, other.FontFamily) && FontSize.Equals(other.FontSize) && NamedSize == other.NamedSize && FontAttributes == other.FontAttributes;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+ if (obj.GetType() != GetType())
+ {
+ return false;
+ }
+ return Equals((Font)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = FontFamily != null ? FontFamily.GetHashCode() : 0;
+ hashCode = (hashCode * 397) ^ FontSize.GetHashCode();
+ hashCode = (hashCode * 397) ^ NamedSize.GetHashCode();
+ hashCode = (hashCode * 397) ^ FontAttributes.GetHashCode();
+
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(Font left, Font right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Font left, Font right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("FontFamily: {0}, FontSize: {1}, NamedSize: {2}, FontAttributes: {3}", FontFamily, FontSize, NamedSize, FontAttributes);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FontAttributes.cs b/Xamarin.Forms.Core/FontAttributes.cs
new file mode 100644
index 00000000..872629c5
--- /dev/null
+++ b/Xamarin.Forms.Core/FontAttributes.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ public enum FontAttributes
+ {
+ None = 0,
+ Bold = 1 << 0,
+ Italic = 1 << 1
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FontSizeConverter.cs b/Xamarin.Forms.Core/FontSizeConverter.cs
new file mode 100644
index 00000000..0d0a8865
--- /dev/null
+++ b/Xamarin.Forms.Core/FontSizeConverter.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ public class FontSizeConverter : TypeConverter, IExtendedTypeConverter
+ {
+ [Obsolete("use ConvertFromInvariantString (string, IServiceProvider)")]
+ object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
+ {
+ return ((IExtendedTypeConverter)this).ConvertFromInvariantString(value as string, serviceProvider);
+ }
+
+ object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
+ {
+ if (value != null)
+ {
+ double size;
+ if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out size))
+ return size;
+ NamedSize namedSize;
+ if (Enum.TryParse(value, out namedSize))
+ {
+ Type type;
+ var valueTargetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
+ type = valueTargetProvider != null ? valueTargetProvider.TargetObject.GetType() : typeof(Label);
+ return Device.GetNamedSize(namedSize, type, false);
+ }
+ }
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(double)));
+ }
+
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ double size;
+ if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out size))
+ return size;
+ NamedSize namedSize;
+ if (Enum.TryParse(value, out namedSize))
+ return Device.GetNamedSize(namedSize, typeof(Label), false);
+ }
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(double)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FontTypeConverter.cs b/Xamarin.Forms.Core/FontTypeConverter.cs
new file mode 100644
index 00000000..464cf4ab
--- /dev/null
+++ b/Xamarin.Forms.Core/FontTypeConverter.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ public sealed class FontTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ // string should be formatted as "[name],[attributes],[size]" there may be multiple attributes, e.g. "Georgia, Bold, Italic, 42"
+ if (value != null)
+ {
+ // trim because mono implements Enum.Parse incorrectly and fails to trim correctly.
+ List<string> parts = value.Split(',').Select(s => s.Trim()).ToList();
+
+ string name = null;
+ var bold = false;
+ var italic = false;
+ double size = -1;
+ NamedSize namedSize = 0;
+
+ // check if last is a size
+ string last = parts.Last();
+
+ double trySize;
+ NamedSize tryNamedSize;
+ if (double.TryParse(last, NumberStyles.Number, CultureInfo.InvariantCulture, out trySize))
+ {
+ size = trySize;
+ parts.RemoveAt(parts.Count - 1);
+ }
+ else if (Enum.TryParse(last, out tryNamedSize))
+ {
+ namedSize = tryNamedSize;
+ parts.RemoveAt(parts.Count - 1);
+ }
+
+ // check if first is a name
+ foreach (string part in parts)
+ {
+ FontAttributes tryAttibute;
+ if (Enum.TryParse(part, out tryAttibute))
+ {
+ // they did not provide a font name
+ if (tryAttibute == FontAttributes.Bold)
+ bold = true;
+ else if (tryAttibute == FontAttributes.Italic)
+ italic = true;
+ }
+ else
+ {
+ // they may have provided a font name
+ if (name != null)
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Font)));
+
+ name = part;
+ }
+ }
+
+ FontAttributes attributes = 0;
+ if (bold)
+ attributes = attributes | FontAttributes.Bold;
+ if (italic)
+ attributes = attributes | FontAttributes.Italic;
+ if (size == -1 && namedSize == 0)
+ namedSize = NamedSize.Medium;
+
+ if (name != null)
+ {
+ if (size == -1)
+ {
+ return Font.OfSize(name, namedSize).WithAttributes(attributes);
+ }
+ return Font.OfSize(name, size).WithAttributes(attributes);
+ }
+ if (size == -1)
+ {
+ return Font.SystemFontOfSize(namedSize, attributes);
+ }
+ return Font.SystemFontOfSize(size, attributes);
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Font)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/FormattedString.cs b/Xamarin.Forms.Core/FormattedString.cs
new file mode 100644
index 00000000..14839ab3
--- /dev/null
+++ b/Xamarin.Forms.Core/FormattedString.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Spans")]
+ public class FormattedString : INotifyPropertyChanged
+ {
+ readonly SpanCollection _spans = new SpanCollection();
+
+ public FormattedString()
+ {
+ _spans.CollectionChanged += OnCollectionChanged;
+ }
+
+ public IList<Span> Spans
+ {
+ get { return _spans; }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public static explicit operator string(FormattedString formatted)
+ {
+ return formatted.ToString();
+ }
+
+ public static implicit operator FormattedString(string text)
+ {
+ return new FormattedString { Spans = { new Span { Text = text } } };
+ }
+
+ public override string ToString()
+ {
+ return string.Concat(Spans.Select(span => span.Text));
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.OldItems != null)
+ {
+ foreach (object item in e.OldItems)
+ {
+ var bo = item as Span;
+ if (bo != null)
+ bo.PropertyChanged -= OnItemPropertyChanged;
+ }
+ }
+
+ if (e.NewItems != null)
+ {
+ foreach (object item in e.NewItems)
+ {
+ var bo = item as Span;
+ if (bo != null)
+ bo.PropertyChanged += OnItemPropertyChanged;
+ }
+ }
+
+ OnPropertyChanged("Spans");
+ }
+
+ void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ OnPropertyChanged("Spans");
+ }
+
+ void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ class SpanCollection : ObservableCollection<Span>
+ {
+ protected override void InsertItem(int index, Span item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+
+ base.InsertItem(index, item);
+ }
+
+ protected override void SetItem(int index, Span item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+
+ base.SetItem(index, item);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Frame.cs b/Xamarin.Forms.Core/Frame.cs
new file mode 100644
index 00000000..01f409e4
--- /dev/null
+++ b/Xamarin.Forms.Core/Frame.cs
@@ -0,0 +1,30 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Content")]
+ [RenderWith(typeof(_FrameRenderer))]
+ public class Frame : ContentView
+ {
+ public static readonly BindableProperty OutlineColorProperty = BindableProperty.Create("OutlineColor", typeof(Color), typeof(Frame), Color.Default);
+
+ public static readonly BindableProperty HasShadowProperty = BindableProperty.Create("HasShadow", typeof(bool), typeof(Frame), true);
+
+ public Frame()
+ {
+ Padding = new Size(20, 20);
+ }
+
+ public bool HasShadow
+ {
+ get { return (bool)GetValue(HasShadowProperty); }
+ set { SetValue(HasShadowProperty, value); }
+ }
+
+ public Color OutlineColor
+ {
+ get { return (Color)GetValue(OutlineColorProperty); }
+ set { SetValue(OutlineColorProperty, value); }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GestureRecognizer.cs b/Xamarin.Forms.Core/GestureRecognizer.cs
new file mode 100644
index 00000000..f68c996d
--- /dev/null
+++ b/Xamarin.Forms.Core/GestureRecognizer.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public class GestureRecognizer : Element, IGestureRecognizer
+ {
+ internal GestureRecognizer()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GestureState.cs b/Xamarin.Forms.Core/GestureState.cs
new file mode 100644
index 00000000..a6c110f2
--- /dev/null
+++ b/Xamarin.Forms.Core/GestureState.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms
+{
+ public enum GestureState
+ {
+ Began,
+ Update,
+ Ended,
+ Failed,
+ Cancelled,
+ Possible
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GestureStatus.cs b/Xamarin.Forms.Core/GestureStatus.cs
new file mode 100644
index 00000000..22f24460
--- /dev/null
+++ b/Xamarin.Forms.Core/GestureStatus.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum GestureStatus
+ {
+ Started = 0,
+ Running = 1,
+ Completed = 2,
+ Canceled = 3
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Grid.cs b/Xamarin.Forms.Core/Grid.cs
new file mode 100644
index 00000000..442146c2
--- /dev/null
+++ b/Xamarin.Forms.Core/Grid.cs
@@ -0,0 +1,359 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ public partial class Grid : Layout<View>
+ {
+ public static readonly BindableProperty RowProperty = BindableProperty.CreateAttached("Row", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0);
+
+ public static readonly BindableProperty RowSpanProperty = BindableProperty.CreateAttached("RowSpan", typeof(int), typeof(Grid), 1, validateValue: (bindable, value) => (int)value >= 1);
+
+ public static readonly BindableProperty ColumnProperty = BindableProperty.CreateAttached("Column", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0);
+
+ public static readonly BindableProperty ColumnSpanProperty = BindableProperty.CreateAttached("ColumnSpan", typeof(int), typeof(Grid), 1, validateValue: (bindable, value) => (int)value >= 1);
+
+ public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create("RowSpacing", typeof(double), typeof(Grid), 6d,
+ propertyChanged: (bindable, oldValue, newValue) => ((Grid)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged));
+
+ public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create("ColumnSpacing", typeof(double), typeof(Grid), 6d,
+ propertyChanged: (bindable, oldValue, newValue) => ((Grid)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged));
+
+ public static readonly BindableProperty ColumnDefinitionsProperty = BindableProperty.Create("ColumnDefinitions", typeof(ColumnDefinitionCollection), typeof(Grid), null,
+ validateValue: (bindable, value) => value != null, propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ if (oldvalue != null)
+ ((ColumnDefinitionCollection)oldvalue).ItemSizeChanged -= ((Grid)bindable).OnDefinitionChanged;
+ if (newvalue != null)
+ ((ColumnDefinitionCollection)newvalue).ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged;
+ }, defaultValueCreator: bindable =>
+ {
+ var colDef = new ColumnDefinitionCollection();
+ colDef.ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged;
+ return colDef;
+ });
+
+ public static readonly BindableProperty RowDefinitionsProperty = BindableProperty.Create("RowDefinitions", typeof(RowDefinitionCollection), typeof(Grid), null,
+ validateValue: (bindable, value) => value != null, propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ if (oldvalue != null)
+ ((RowDefinitionCollection)oldvalue).ItemSizeChanged -= ((Grid)bindable).OnDefinitionChanged;
+ if (newvalue != null)
+ ((RowDefinitionCollection)newvalue).ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged;
+ }, defaultValueCreator: bindable =>
+ {
+ var rowDef = new RowDefinitionCollection();
+ rowDef.ItemSizeChanged += ((Grid)bindable).OnDefinitionChanged;
+ return rowDef;
+ });
+
+ readonly GridElementCollection _children;
+
+ public Grid()
+ {
+ _children = new GridElementCollection(InternalChildren, this) { Parent = this };
+ }
+
+ public new IGridList<View> Children
+ {
+ get { return _children; }
+ }
+
+ public ColumnDefinitionCollection ColumnDefinitions
+ {
+ get { return (ColumnDefinitionCollection)GetValue(ColumnDefinitionsProperty); }
+ set { SetValue(ColumnDefinitionsProperty, value); }
+ }
+
+ public double ColumnSpacing
+ {
+ get { return (double)GetValue(ColumnSpacingProperty); }
+ set { SetValue(ColumnSpacingProperty, value); }
+ }
+
+ public RowDefinitionCollection RowDefinitions
+ {
+ get { return (RowDefinitionCollection)GetValue(RowDefinitionsProperty); }
+ set { SetValue(RowDefinitionsProperty, value); }
+ }
+
+ public double RowSpacing
+ {
+ get { return (double)GetValue(RowSpacingProperty); }
+ set { SetValue(RowSpacingProperty, value); }
+ }
+
+ public static int GetColumn(BindableObject bindable)
+ {
+ return (int)bindable.GetValue(ColumnProperty);
+ }
+
+ public static int GetColumnSpan(BindableObject bindable)
+ {
+ return (int)bindable.GetValue(ColumnSpanProperty);
+ }
+
+ public static int GetRow(BindableObject bindable)
+ {
+ return (int)bindable.GetValue(RowProperty);
+ }
+
+ public static int GetRowSpan(BindableObject bindable)
+ {
+ return (int)bindable.GetValue(RowSpanProperty);
+ }
+
+ public static void SetColumn(BindableObject bindable, int value)
+ {
+ bindable.SetValue(ColumnProperty, value);
+ }
+
+ public static void SetColumnSpan(BindableObject bindable, int value)
+ {
+ bindable.SetValue(ColumnSpanProperty, value);
+ }
+
+ public static void SetRow(BindableObject bindable, int value)
+ {
+ bindable.SetValue(RowProperty, value);
+ }
+
+ public static void SetRowSpan(BindableObject bindable, int value)
+ {
+ bindable.SetValue(RowSpanProperty, value);
+ }
+
+ protected override void OnAdded(View view)
+ {
+ base.OnAdded(view);
+ view.PropertyChanged += OnItemPropertyChanged;
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ UpdateInheritedBindingContexts();
+ base.OnBindingContextChanged();
+ }
+
+ protected override void OnRemoved(View view)
+ {
+ base.OnRemoved(view);
+ view.PropertyChanged -= OnItemPropertyChanged;
+ }
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ LayoutOptions vOptions = view.VerticalOptions;
+ LayoutOptions hOptions = view.HorizontalOptions;
+
+ var result = LayoutConstraint.None;
+
+ if (_rows == null || _columns == null)
+ EnsureRowsColumnsInitialized();
+
+ if (vOptions.Alignment == LayoutAlignment.Fill)
+ {
+ int row = GetRow(view);
+ int rowSpan = GetRowSpan(view);
+ List<RowDefinition> rowDefinitions = _rows;
+
+ var canFix = true;
+
+ for (int i = row; i < row + rowSpan && i < rowDefinitions.Count; i++)
+ {
+ GridLength height = rowDefinitions[i].Height;
+ if (height.IsAuto)
+ {
+ canFix = false;
+ break;
+ }
+ if ((Constraint & LayoutConstraint.VerticallyFixed) == 0 && height.IsStar)
+ {
+ canFix = false;
+ break;
+ }
+ }
+
+ if (canFix)
+ result |= LayoutConstraint.VerticallyFixed;
+ }
+
+ if (hOptions.Alignment == LayoutAlignment.Fill)
+ {
+ int col = GetColumn(view);
+ int colSpan = GetColumnSpan(view);
+ List<ColumnDefinition> columnDefinitions = _columns;
+
+ var canFix = true;
+
+ for (int i = col; i < col + colSpan && i < columnDefinitions.Count; i++)
+ {
+ GridLength width = columnDefinitions[i].Width;
+ if (width.IsAuto)
+ {
+ canFix = false;
+ break;
+ }
+ if ((Constraint & LayoutConstraint.HorizontallyFixed) == 0 && width.IsStar)
+ {
+ canFix = false;
+ break;
+ }
+ }
+
+ if (canFix)
+ result |= LayoutConstraint.HorizontallyFixed;
+ }
+
+ view.ComputedConstraint = result;
+ }
+
+ internal override void InvalidateMeasure(InvalidationTrigger trigger)
+ {
+ base.InvalidateMeasure(trigger);
+ _columns = null;
+ _rows = null;
+ }
+
+ void OnDefinitionChanged(object sender, EventArgs args)
+ {
+ ComputeConstrainsForChildren();
+ UpdateInheritedBindingContexts();
+ InvalidateLayout();
+ }
+
+ void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == ColumnProperty.PropertyName || e.PropertyName == ColumnSpanProperty.PropertyName || e.PropertyName == RowProperty.PropertyName ||
+ e.PropertyName == RowSpanProperty.PropertyName)
+ {
+ var child = sender as View;
+ if (child != null)
+ {
+ ComputeConstraintForView(child);
+ }
+
+ InvalidateLayout();
+ }
+ }
+
+ void UpdateInheritedBindingContexts()
+ {
+ object bindingContext = BindingContext;
+ RowDefinitionCollection rowDefs = RowDefinitions;
+ if (rowDefs != null)
+ {
+ for (var i = 0; i < rowDefs.Count; i++)
+ {
+ RowDefinition rowdef = rowDefs[i];
+ SetInheritedBindingContext(rowdef, bindingContext);
+ }
+ }
+
+ ColumnDefinitionCollection colDefs = ColumnDefinitions;
+ if (colDefs != null)
+ {
+ for (var i = 0; i < colDefs.Count; i++)
+ {
+ ColumnDefinition coldef = colDefs[i];
+ SetInheritedBindingContext(coldef, bindingContext);
+ }
+ }
+ }
+
+ public interface IGridList<T> : IList<T> where T : View
+ {
+ void Add(View view, int left, int top);
+ void Add(View view, int left, int right, int top, int bottom);
+ void AddHorizontal(IEnumerable<View> views);
+ void AddHorizontal(View view);
+ void AddVertical(IEnumerable<View> views);
+ void AddVertical(View view);
+ }
+
+ class GridElementCollection : ElementCollection<View>, IGridList<View>
+ {
+ public GridElementCollection(ObservableCollection<Element> inner, Grid parent) : base(inner)
+ {
+ Parent = parent;
+ }
+
+ internal Grid Parent { get; set; }
+
+ public void Add(View view, int left, int top)
+ {
+ if (left < 0)
+ throw new ArgumentOutOfRangeException("left");
+ if (top < 0)
+ throw new ArgumentOutOfRangeException("top");
+ Add(view, left, left + 1, top, top + 1);
+ }
+
+ public void Add(View view, int left, int right, int top, int bottom)
+ {
+ if (left < 0)
+ throw new ArgumentOutOfRangeException("left");
+ if (top < 0)
+ throw new ArgumentOutOfRangeException("top");
+ if (left >= right)
+ throw new ArgumentOutOfRangeException("right");
+ if (top >= bottom)
+ throw new ArgumentOutOfRangeException("bottom");
+ if (view == null)
+ throw new ArgumentNullException("view");
+
+ SetRow(view, top);
+ SetRowSpan(view, bottom - top);
+ SetColumn(view, left);
+ SetColumnSpan(view, right - left);
+
+ Add(view);
+ }
+
+ public void AddHorizontal(IEnumerable<View> views)
+ {
+ if (views == null)
+ throw new ArgumentNullException("views");
+
+ views.ForEach(AddHorizontal);
+ }
+
+ public void AddHorizontal(View view)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+
+ int lastRow = this.Any() ? this.Max(w => GetRow(w) + GetRowSpan(w) - 1) : -1;
+ lastRow = Math.Max(lastRow, Parent.RowDefinitions.Count - 1);
+ int lastCol = this.Any() ? this.Max(w => GetColumn(w) + GetColumnSpan(w) - 1) : -1;
+ lastCol = Math.Max(lastCol, Parent.ColumnDefinitions.Count - 1);
+
+ Add(view, lastCol + 1, lastCol + 2, 0, Math.Max(1, lastRow));
+ }
+
+ public void AddVertical(IEnumerable<View> views)
+ {
+ if (views == null)
+ throw new ArgumentNullException("views");
+
+ views.ForEach(AddVertical);
+ }
+
+ public void AddVertical(View view)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+
+ int lastRow = this.Any() ? this.Max(w => GetRow(w) + GetRowSpan(w) - 1) : -1;
+ lastRow = Math.Max(lastRow, Parent.RowDefinitions.Count - 1);
+ int lastCol = this.Any() ? this.Max(w => GetColumn(w) + GetColumnSpan(w) - 1) : -1;
+ lastCol = Math.Max(lastCol, Parent.ColumnDefinitions.Count - 1);
+
+ Add(view, 0, Math.Max(1, lastCol), lastRow + 1, lastRow + 2);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GridCalc.cs b/Xamarin.Forms.Core/GridCalc.cs
new file mode 100644
index 00000000..778e188e
--- /dev/null
+++ b/Xamarin.Forms.Core/GridCalc.cs
@@ -0,0 +1,698 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ public partial class Grid
+ {
+ List<ColumnDefinition> _columns;
+ List<RowDefinition> _rows;
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ if (!InternalChildren.Any())
+ return;
+
+ MeasureGrid(width, height);
+
+ // Make copies so if InvalidateMeasure is called during layout we dont crash when these get nulled
+ List<ColumnDefinition> columnsCopy = _columns;
+ List<RowDefinition> rowsCopy = _rows;
+
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ var child = (View)InternalChildren[index];
+ if (!child.IsVisible)
+ continue;
+ int r = GetRow(child);
+ int c = GetColumn(child);
+ int rs = GetRowSpan(child);
+ int cs = GetColumnSpan(child);
+
+ double posx = x + c * ColumnSpacing;
+ for (var i = 0; i < c; i++)
+ posx += columnsCopy[i].ActualWidth;
+ double posy = y + r * RowSpacing;
+ for (var i = 0; i < r; i++)
+ posy += rowsCopy[i].ActualHeight;
+
+ double w = columnsCopy[c].ActualWidth;
+ for (var i = 1; i < cs; i++)
+ w += ColumnSpacing + columnsCopy[c + i].ActualWidth;
+ double h = rowsCopy[r].ActualHeight;
+ for (var i = 1; i < rs; i++)
+ h += RowSpacing + rowsCopy[r + i].ActualHeight;
+
+ // in the future we can might maybe optimize by passing the already calculated size request
+ LayoutChildIntoBoundingRegion(child, new Rectangle(posx, posy, w, h));
+ }
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ if (!InternalChildren.Any())
+ return new SizeRequest(new Size(0, 0));
+
+ MeasureGrid(widthConstraint, heightConstraint, true);
+
+ double columnWidthSum = 0;
+ double nonStarColumnWidthSum = 0;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition c = _columns[index];
+ columnWidthSum += c.ActualWidth;
+ if (!c.Width.IsStar)
+ nonStarColumnWidthSum += c.ActualWidth;
+ }
+ double rowHeightSum = 0;
+ double nonStarRowHeightSum = 0;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition r = _rows[index];
+ rowHeightSum += r.ActualHeight;
+ if (!r.Height.IsStar)
+ nonStarRowHeightSum += r.ActualHeight;
+ }
+
+ var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing);
+ var minimum = new Size(nonStarColumnWidthSum + (_columns.Count - 1) * ColumnSpacing, nonStarRowHeightSum + (_rows.Count - 1) * RowSpacing);
+
+ var result = new SizeRequest(request, minimum);
+ return result;
+ }
+
+ void AssignAbsoluteCells()
+ {
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (row.Height.IsAbsolute)
+ row.ActualHeight = row.Height.Value;
+ }
+
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (col.Width.IsAbsolute)
+ col.ActualWidth = col.Width.Value;
+ }
+ }
+
+ void CalculateAutoCells(double width, double height)
+ {
+ // this require multiple passes. First process the 1-span, then 2, 3, ...
+ // And this needs to be run twice, just in case a lower-span column can be determined by a larger span
+ for (var iteration = 0; iteration < 2; iteration++)
+ {
+ for (var rowspan = 1; rowspan <= _rows.Count; rowspan++)
+ {
+ for (var i = 0; i < _rows.Count; i++)
+ {
+ RowDefinition row = _rows[i];
+ if (!row.Height.IsAuto)
+ continue;
+ if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3
+ continue;
+
+ double actualHeight = row.ActualHeight;
+ double minimumHeight = row.MinimumHeight;
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ var child = (View)InternalChildren[index];
+ if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1)
+ continue;
+ double assignedWidth = GetAssignedColumnWidth(child);
+ double assignedHeight = GetAssignedRowHeight(child);
+ double widthRequest = assignedWidth + GetUnassignedWidth(width);
+ double heightRequest = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height);
+
+ SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins);
+ actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1));
+ minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1));
+ }
+ if (actualHeight >= 0)
+ row.ActualHeight = actualHeight;
+ if (minimumHeight >= 0)
+ row.MinimumHeight = minimumHeight;
+ }
+ }
+
+ for (var colspan = 1; colspan <= _columns.Count; colspan++)
+ {
+ for (var i = 0; i < _columns.Count; i++)
+ {
+ ColumnDefinition col = _columns[i];
+ if (!col.Width.IsAuto)
+ continue;
+ if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip
+ continue;
+
+ double actualWidth = col.ActualWidth;
+ double minimumWidth = col.MinimumWidth;
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ var child = (View)InternalChildren[index];
+ if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1)
+ continue;
+ double assignedWidth = GetAssignedColumnWidth(child);
+ double assignedHeight = GetAssignedRowHeight(child);
+ double widthRequest = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width);
+ double heightRequest = assignedHeight + GetUnassignedHeight(height);
+
+ SizeRequest sizeRequest = child.Measure(widthRequest, heightRequest, MeasureFlags.IncludeMargins);
+ actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
+ minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
+ }
+ if (actualWidth >= 0)
+ col.ActualWidth = actualWidth;
+ if (minimumWidth >= 0)
+ col.MinimumWidth = actualWidth;
+ }
+ }
+ }
+ }
+
+ void CalculateStarCells(double width, double height, double totalStarsWidth, double totalStarsHeight)
+ {
+ double starColWidth = GetUnassignedWidth(width) / totalStarsWidth;
+ double starRowHeight = GetUnassignedHeight(height) / totalStarsHeight;
+
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (col.Width.IsStar)
+ col.ActualWidth = col.Width.Value * starColWidth;
+ }
+
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (row.Height.IsStar)
+ row.ActualHeight = row.Height.Value * starRowHeight;
+ }
+ }
+
+ void ContractColumnsIfNeeded(double width, Func<ColumnDefinition, bool> predicate)
+ {
+ double columnWidthSum = 0;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition c = _columns[index];
+ columnWidthSum += c.ActualWidth;
+ }
+
+ double rowHeightSum = 0;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition r = _rows[index];
+ rowHeightSum += r.ActualHeight;
+ }
+
+ var request = new Size(columnWidthSum + (_columns.Count - 1) * ColumnSpacing, rowHeightSum + (_rows.Count - 1) * RowSpacing);
+ if (request.Width > width)
+ {
+ double contractionSpace = 0;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition c = _columns[index];
+ if (predicate(c))
+ contractionSpace += c.ActualWidth - c.MinimumWidth;
+ }
+ if (contractionSpace > 0)
+ {
+ // contract as much as we can but no more
+ double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - width, 0));
+ double contractFactor = contractionNeeded / contractionSpace;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (!predicate(col))
+ continue;
+ double availableSpace = col.ActualWidth - col.MinimumWidth;
+ double contraction = availableSpace * contractFactor;
+ col.ActualWidth -= contraction;
+ contractionNeeded -= contraction;
+ }
+ }
+ }
+ }
+
+ void ContractRowsIfNeeded(double height, Func<RowDefinition, bool> predicate)
+ {
+ double columnSum = 0;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition c = _columns[index];
+ columnSum += Math.Max(0, c.ActualWidth);
+ }
+ double rowSum = 0;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition r = _rows[index];
+ rowSum += Math.Max(0, r.ActualHeight);
+ }
+
+ var request = new Size(columnSum + (_columns.Count - 1) * ColumnSpacing, rowSum + (_rows.Count - 1) * RowSpacing);
+ if (request.Height <= height)
+ return;
+ double contractionSpace = 0;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition r = _rows[index];
+ if (predicate(r))
+ contractionSpace += r.ActualHeight - r.MinimumHeight;
+ }
+ if (!(contractionSpace > 0))
+ return;
+ // contract as much as we can but no more
+ double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - height, 0));
+ double contractFactor = contractionNeeded / contractionSpace;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (!predicate(row))
+ continue;
+ double availableSpace = row.ActualHeight - row.MinimumHeight;
+ double contraction = availableSpace * contractFactor;
+ row.ActualHeight -= contraction;
+ contractionNeeded -= contraction;
+ }
+ }
+
+ void EnsureRowsColumnsInitialized()
+ {
+ _columns = ColumnDefinitions == null ? new List<ColumnDefinition>() : ColumnDefinitions.ToList();
+ _rows = RowDefinitions == null ? new List<RowDefinition>() : RowDefinitions.ToList();
+
+ int lastRow = -1;
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ Element w = InternalChildren[index];
+ lastRow = Math.Max(lastRow, GetRow(w) + GetRowSpan(w) - 1);
+ }
+ lastRow = Math.Max(lastRow, RowDefinitions.Count - 1);
+
+ int lastCol = -1;
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ Element w = InternalChildren[index];
+ lastCol = Math.Max(lastCol, GetColumn(w) + GetColumnSpan(w) - 1);
+ }
+ lastCol = Math.Max(lastCol, ColumnDefinitions.Count - 1);
+
+ while (_columns.Count <= lastCol)
+ _columns.Add(new ColumnDefinition());
+ while (_rows.Count <= lastRow)
+ _rows.Add(new RowDefinition());
+
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ col.ActualWidth = -1;
+ }
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ row.ActualHeight = -1;
+ }
+ }
+
+ void ExpandLastAutoColumnIfNeeded(double width, bool expandToRequest)
+ {
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ Element element = InternalChildren[index];
+ var child = (View)element;
+ if (!child.IsVisible)
+ continue;
+
+ ColumnDefinition col = GetLastAutoColumn(child);
+ if (col == null)
+ continue;
+
+ double assignedWidth = GetAssignedColumnWidth(child);
+ double w = double.IsPositiveInfinity(width) ? double.PositiveInfinity : assignedWidth + GetUnassignedWidth(width);
+ SizeRequest sizeRequest = child.Measure(w, GetAssignedRowHeight(child), MeasureFlags.IncludeMargins);
+ double requiredWidth = expandToRequest ? sizeRequest.Request.Width : sizeRequest.Minimum.Width;
+ double deltaWidth = requiredWidth - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing;
+ if (deltaWidth > 0)
+ {
+ col.ActualWidth += deltaWidth;
+ }
+ }
+ }
+
+ void ExpandLastAutoRowIfNeeded(double height, bool expandToRequest)
+ {
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ Element element = InternalChildren[index];
+ var child = (View)element;
+ if (!child.IsVisible)
+ continue;
+
+ RowDefinition row = GetLastAutoRow(child);
+ if (row == null)
+ continue;
+
+ double assignedHeight = GetAssignedRowHeight(child);
+ double h = double.IsPositiveInfinity(height) ? double.PositiveInfinity : assignedHeight + GetUnassignedHeight(height);
+ SizeRequest sizeRequest = child.Measure(GetAssignedColumnWidth(child), h, MeasureFlags.IncludeMargins);
+ double requiredHeight = expandToRequest ? sizeRequest.Request.Height : sizeRequest.Minimum.Height;
+ double deltaHeight = requiredHeight - assignedHeight - (GetRowSpan(child) - 1) * RowSpacing;
+ if (deltaHeight > 0)
+ {
+ row.ActualHeight += deltaHeight;
+ }
+ }
+ }
+
+ void MeasureAndContractStarredColumns(double width, double height, double totalStarsWidth)
+ {
+ double starColWidth;
+ starColWidth = MeasuredStarredColumns();
+
+ if (!double.IsPositiveInfinity(width) && double.IsPositiveInfinity(height))
+ {
+ // re-zero columns so GetUnassignedWidth returns correctly
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (col.Width.IsStar)
+ col.ActualWidth = 0;
+ }
+
+ starColWidth = Math.Max(starColWidth, GetUnassignedWidth(width) / totalStarsWidth);
+ }
+
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (col.Width.IsStar)
+ col.ActualWidth = col.Width.Value * starColWidth;
+ }
+
+ ContractColumnsIfNeeded(width, c => c.Width.IsStar);
+ }
+
+ void MeasureAndContractStarredRows(double width, double height, double totalStarsHeight)
+ {
+ double starRowHeight;
+ starRowHeight = MeasureStarredRows();
+
+ if (!double.IsPositiveInfinity(height) && double.IsPositiveInfinity(width))
+ {
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (row.Height.IsStar)
+ row.ActualHeight = 0;
+ }
+
+ starRowHeight = Math.Max(starRowHeight, GetUnassignedHeight(height) / totalStarsHeight);
+ }
+
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (row.Height.IsStar)
+ row.ActualHeight = row.Height.Value * starRowHeight;
+ }
+
+ ContractRowsIfNeeded(height, r => r.Height.IsStar);
+ }
+
+ double MeasuredStarredColumns()
+ {
+ double starColWidth;
+ for (var iteration = 0; iteration < 2; iteration++)
+ {
+ for (var colspan = 1; colspan <= _columns.Count; colspan++)
+ {
+ for (var i = 0; i < _columns.Count; i++)
+ {
+ ColumnDefinition col = _columns[i];
+ if (!col.Width.IsStar)
+ continue;
+ if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip
+ continue;
+
+ double actualWidth = col.ActualWidth;
+ double minimumWidth = col.MinimumWidth;
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ var child = (View)InternalChildren[index];
+ if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1)
+ continue;
+ double assignedWidth = GetAssignedColumnWidth(child);
+
+ SizeRequest sizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
+ minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
+ }
+ if (actualWidth >= 0)
+ col.ActualWidth = actualWidth;
+
+ if (minimumWidth >= 0)
+ col.MinimumWidth = minimumWidth;
+ }
+ }
+ }
+
+ //Measure the stars
+ starColWidth = 1;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (!col.Width.IsStar)
+ continue;
+ starColWidth = Math.Max(starColWidth, col.ActualWidth / col.Width.Value);
+ }
+
+ return starColWidth;
+ }
+
+ void MeasureGrid(double width, double height, bool requestSize = false)
+ {
+ EnsureRowsColumnsInitialized();
+
+ AssignAbsoluteCells();
+
+ CalculateAutoCells(width, height);
+
+ if (!requestSize)
+ {
+ ContractColumnsIfNeeded(width, c => c.Width.IsAuto);
+ ContractRowsIfNeeded(height, r => r.Height.IsAuto);
+ }
+
+ double totalStarsHeight = 0;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (row.Height.IsStar)
+ totalStarsHeight += row.Height.Value;
+ }
+
+ double totalStarsWidth = 0;
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (col.Width.IsStar)
+ totalStarsWidth += col.Width.Value;
+ }
+
+ if (requestSize)
+ {
+ MeasureAndContractStarredColumns(width, height, totalStarsWidth);
+ MeasureAndContractStarredRows(width, height, totalStarsHeight);
+ }
+ else
+ {
+ CalculateStarCells(width, height, totalStarsWidth, totalStarsHeight);
+ }
+
+ ZeroUnassignedCells();
+
+ ExpandLastAutoRowIfNeeded(height, requestSize);
+ ExpandLastAutoColumnIfNeeded(width, requestSize);
+ }
+
+ double MeasureStarredRows()
+ {
+ double starRowHeight;
+ for (var iteration = 0; iteration < 2; iteration++)
+ {
+ for (var rowspan = 1; rowspan <= _rows.Count; rowspan++)
+ {
+ for (var i = 0; i < _rows.Count; i++)
+ {
+ RowDefinition row = _rows[i];
+ if (!row.Height.IsStar)
+ continue;
+ if (row.ActualHeight >= 0) // if Actual is already set (by a smaller span), skip till pass 3
+ continue;
+
+ double actualHeight = row.ActualHeight;
+ double minimumHeight = row.MinimumHeight;
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ var child = (View)InternalChildren[index];
+ if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1)
+ continue;
+ double assignedHeight = GetAssignedRowHeight(child);
+ double assignedWidth = GetAssignedColumnWidth(child);
+
+ SizeRequest sizeRequest = child.Measure(assignedWidth, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1));
+ minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1));
+ }
+ if (actualHeight >= 0)
+ row.ActualHeight = actualHeight;
+
+ if (minimumHeight >= 0)
+ row.MinimumHeight = minimumHeight;
+ }
+ }
+ }
+
+ // 3. Star columns:
+
+ //Measure the stars
+ starRowHeight = 1;
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (!row.Height.IsStar)
+ continue;
+ starRowHeight = Math.Max(starRowHeight, row.ActualHeight / row.Height.Value);
+ }
+
+ return starRowHeight;
+ }
+
+ void ZeroUnassignedCells()
+ {
+ for (var index = 0; index < _columns.Count; index++)
+ {
+ ColumnDefinition col = _columns[index];
+ if (col.ActualWidth < 0)
+ col.ActualWidth = 0;
+ }
+ for (var index = 0; index < _rows.Count; index++)
+ {
+ RowDefinition row = _rows[index];
+ if (row.ActualHeight < 0)
+ row.ActualHeight = 0;
+ }
+ }
+
+ #region Helpers
+
+ static bool IsInColumn(BindableObject child, int column)
+ {
+ int childColumn = GetColumn(child);
+ int span = GetColumnSpan(child);
+ return childColumn <= column && column < childColumn + span;
+ }
+
+ static bool IsInRow(BindableObject child, int row)
+ {
+ int childRow = GetRow(child);
+ int span = GetRowSpan(child);
+ return childRow <= row && row < childRow + span;
+ }
+
+ int NumberOfUnsetColumnWidth(BindableObject child)
+ {
+ var n = 0;
+ int index = GetColumn(child);
+ int span = GetColumnSpan(child);
+ for (int i = index; i < index + span; i++)
+ if (_columns[i].ActualWidth <= 0)
+ n++;
+ return n;
+ }
+
+ int NumberOfUnsetRowHeight(BindableObject child)
+ {
+ var n = 0;
+ int index = GetRow(child);
+ int span = GetRowSpan(child);
+ for (int i = index; i < index + span; i++)
+ if (_rows[i].ActualHeight <= 0)
+ n++;
+ return n;
+ }
+
+ double GetAssignedColumnWidth(BindableObject child)
+ {
+ var actual = 0d;
+ int index = GetColumn(child);
+ int span = GetColumnSpan(child);
+ for (int i = index; i < index + span; i++)
+ if (_columns[i].ActualWidth >= 0)
+ actual += _columns[i].ActualWidth;
+ return actual;
+ }
+
+ double GetAssignedRowHeight(BindableObject child)
+ {
+ var actual = 0d;
+ int index = GetRow(child);
+ int span = GetRowSpan(child);
+ for (int i = index; i < index + span; i++)
+ if (_rows[i].ActualHeight >= 0)
+ actual += _rows[i].ActualHeight;
+ return actual;
+ }
+
+ ColumnDefinition GetLastAutoColumn(BindableObject child)
+ {
+ int index = GetColumn(child);
+ int span = GetColumnSpan(child);
+ for (int i = index + span - 1; i >= index; i--)
+ if (_columns[i].Width.IsAuto)
+ return _columns[i];
+ return null;
+ }
+
+ RowDefinition GetLastAutoRow(BindableObject child)
+ {
+ int index = GetRow(child);
+ int span = GetRowSpan(child);
+ for (int i = index + span - 1; i >= index; i--)
+ if (_rows[i].Height.IsAuto)
+ return _rows[i];
+ return null;
+ }
+
+ double GetUnassignedHeight(double heightRequest)
+ {
+ double assigned = (_rows.Count - 1) * RowSpacing;
+ for (var i = 0; i < _rows.Count; i++)
+ {
+ double actual = _rows[i].ActualHeight;
+ if (actual >= 0)
+ assigned += actual;
+ }
+ return heightRequest - assigned;
+ }
+
+ double GetUnassignedWidth(double widthRequest)
+ {
+ double assigned = (_columns.Count - 1) * ColumnSpacing;
+ for (var i = 0; i < _columns.Count; i++)
+ {
+ double actual = _columns[i].ActualWidth;
+ if (actual >= 0)
+ assigned += actual;
+ }
+ return widthRequest - assigned;
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GridLength.cs b/Xamarin.Forms.Core/GridLength.cs
new file mode 100644
index 00000000..5d569d08
--- /dev/null
+++ b/Xamarin.Forms.Core/GridLength.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Diagnostics;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(GridLengthTypeConverter))]
+ [DebuggerDisplay("{Value}.{GridUnitType}")]
+ public struct GridLength
+ {
+ public static GridLength Auto
+ {
+ get { return new GridLength(1, GridUnitType.Auto); }
+ }
+
+ public double Value { get; }
+
+ public GridUnitType GridUnitType { get; }
+
+ public bool IsAbsolute
+ {
+ get { return GridUnitType == GridUnitType.Absolute; }
+ }
+
+ public bool IsAuto
+ {
+ get { return GridUnitType == GridUnitType.Auto; }
+ }
+
+ public bool IsStar
+ {
+ get { return GridUnitType == GridUnitType.Star; }
+ }
+
+ public GridLength(double value) : this(value, GridUnitType.Absolute)
+ {
+ }
+
+ public GridLength(double value, GridUnitType type)
+ {
+ if (value < 0 || double.IsNaN(value))
+ throw new ArgumentException("value is less than 0 or is not a number", "value");
+ if ((int)type < (int)GridUnitType.Absolute || (int)type > (int)GridUnitType.Auto)
+ throw new ArgumentException("type is not a valid GridUnitType", "type");
+
+ Value = value;
+ GridUnitType = type;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj != null && obj is GridLength && Equals((GridLength)obj);
+ }
+
+ bool Equals(GridLength other)
+ {
+ return GridUnitType == other.GridUnitType && Math.Abs(Value - other.Value) < double.Epsilon;
+ }
+
+ public override int GetHashCode()
+ {
+ return GridUnitType.GetHashCode() * 397 ^ Value.GetHashCode();
+ }
+
+ public static implicit operator GridLength(double absoluteValue)
+ {
+ return new GridLength(absoluteValue);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0}.{1}", Value, GridUnitType);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GridLengthTypeConverter.cs b/Xamarin.Forms.Core/GridLengthTypeConverter.cs
new file mode 100644
index 00000000..7c58b417
--- /dev/null
+++ b/Xamarin.Forms.Core/GridLengthTypeConverter.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class GridLengthTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value == null)
+ return null;
+
+ double length;
+ value = value.Trim();
+ if (string.Compare(value, "auto", StringComparison.OrdinalIgnoreCase) == 0)
+ return GridLength.Auto;
+ if (string.Compare(value, "*", StringComparison.OrdinalIgnoreCase) == 0)
+ return new GridLength(1, GridUnitType.Star);
+ if (value.EndsWith("*", StringComparison.Ordinal) && double.TryParse(value.Substring(0, value.Length - 1), NumberStyles.Number, CultureInfo.InvariantCulture, out length))
+ return new GridLength(length, GridUnitType.Star);
+ if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out length))
+ return new GridLength(length);
+
+ throw new FormatException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/GridUnitType.cs b/Xamarin.Forms.Core/GridUnitType.cs
new file mode 100644
index 00000000..372c1804
--- /dev/null
+++ b/Xamarin.Forms.Core/GridUnitType.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public enum GridUnitType
+ {
+ Absolute,
+ Star,
+ Auto
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/HandlerAttribute.cs b/Xamarin.Forms.Core/HandlerAttribute.cs
new file mode 100644
index 00000000..b9dc9ecb
--- /dev/null
+++ b/Xamarin.Forms.Core/HandlerAttribute.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public abstract class HandlerAttribute : Attribute
+ {
+ protected HandlerAttribute(Type handler, Type target)
+ {
+ TargetType = target;
+ HandlerType = handler;
+ }
+
+ internal Type HandlerType { get; private set; }
+
+ internal Type TargetType { get; private set; }
+
+ public virtual bool ShouldRegister()
+ {
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/HtmlWebViewSource.cs b/Xamarin.Forms.Core/HtmlWebViewSource.cs
new file mode 100644
index 00000000..157e5054
--- /dev/null
+++ b/Xamarin.Forms.Core/HtmlWebViewSource.cs
@@ -0,0 +1,28 @@
+namespace Xamarin.Forms
+{
+ public class HtmlWebViewSource : WebViewSource
+ {
+ public static readonly BindableProperty HtmlProperty = BindableProperty.Create("Html", typeof(string), typeof(HtmlWebViewSource), default(string),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((HtmlWebViewSource)bindable).OnSourceChanged());
+
+ public static readonly BindableProperty BaseUrlProperty = BindableProperty.Create("BaseUrl", typeof(string), typeof(HtmlWebViewSource), default(string),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((HtmlWebViewSource)bindable).OnSourceChanged());
+
+ public string BaseUrl
+ {
+ get { return (string)GetValue(BaseUrlProperty); }
+ set { SetValue(BaseUrlProperty, value); }
+ }
+
+ public string Html
+ {
+ get { return (string)GetValue(HtmlProperty); }
+ set { SetValue(HtmlProperty, value); }
+ }
+
+ internal override void Load(IWebViewRenderer renderer)
+ {
+ renderer.LoadHtml(Html, BaseUrl);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IAnimatable.cs b/Xamarin.Forms.Core/IAnimatable.cs
new file mode 100644
index 00000000..2e57c2e5
--- /dev/null
+++ b/Xamarin.Forms.Core/IAnimatable.cs
@@ -0,0 +1,34 @@
+//
+// IAnimatable.cs
+//
+// Author:
+// Jason Smith <jason.smith@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace Xamarin.Forms
+{
+ public interface IAnimatable
+ {
+ void BatchBegin();
+ void BatchCommit();
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IApplicationController.cs b/Xamarin.Forms.Core/IApplicationController.cs
new file mode 100644
index 00000000..201032f9
--- /dev/null
+++ b/Xamarin.Forms.Core/IApplicationController.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ public interface IApplicationController
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IButtonController.cs b/Xamarin.Forms.Core/IButtonController.cs
new file mode 100644
index 00000000..2fa1737f
--- /dev/null
+++ b/Xamarin.Forms.Core/IButtonController.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ internal interface IButtonController : IViewController
+ {
+ void SendClicked();
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ICarouselViewController.cs b/Xamarin.Forms.Core/ICarouselViewController.cs
new file mode 100644
index 00000000..f59bc3d3
--- /dev/null
+++ b/Xamarin.Forms.Core/ICarouselViewController.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public interface ICarouselViewController : IItemViewController
+ {
+ void SendPositionAppearing(int position);
+ void SendPositionDisappearing(int position);
+ void SendSelectedItemChanged(object item);
+ void SendSelectedPositionChanged(int position);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IControlTemplated.cs b/Xamarin.Forms.Core/IControlTemplated.cs
new file mode 100644
index 00000000..f5798c2e
--- /dev/null
+++ b/Xamarin.Forms.Core/IControlTemplated.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal interface IControlTemplated
+ {
+ ControlTemplate ControlTemplate { get; set; }
+
+ IList<Element> InternalChildren { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IDefinition.cs b/Xamarin.Forms.Core/IDefinition.cs
new file mode 100644
index 00000000..6971d611
--- /dev/null
+++ b/Xamarin.Forms.Core/IDefinition.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public interface IDefinition
+ {
+ event EventHandler SizeChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IDeserializer.cs b/Xamarin.Forms.Core/IDeserializer.cs
new file mode 100644
index 00000000..60478e1b
--- /dev/null
+++ b/Xamarin.Forms.Core/IDeserializer.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal interface IDeserializer
+ {
+ Task<IDictionary<string, object>> DeserializePropertiesAsync();
+ Task SerializePropertiesAsync(IDictionary<string, object> properties);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IEffectControlProvider.cs b/Xamarin.Forms.Core/IEffectControlProvider.cs
new file mode 100644
index 00000000..f1882f5f
--- /dev/null
+++ b/Xamarin.Forms.Core/IEffectControlProvider.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ public interface IEffectControlProvider
+ {
+ void RegisterEffect(Effect effect);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IElement.cs b/Xamarin.Forms.Core/IElement.cs
new file mode 100644
index 00000000..ee302b5f
--- /dev/null
+++ b/Xamarin.Forms.Core/IElement.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal interface IElement
+ {
+ Element Parent { get; set; }
+
+ //Use these 2 instead of an event to avoid cloning way too much multicastdelegates on mono
+ void AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged);
+ void RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IElementController.cs b/Xamarin.Forms.Core/IElementController.cs
new file mode 100644
index 00000000..4e1ab10d
--- /dev/null
+++ b/Xamarin.Forms.Core/IElementController.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public interface IElementController
+ {
+ IEffectControlProvider EffectControlProvider { get; set; }
+
+ void SetValueFromRenderer(BindableProperty property, object value);
+ void SetValueFromRenderer(BindablePropertyKey propertyKey, object value);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IExpressionSearch.cs b/Xamarin.Forms.Core/IExpressionSearch.cs
new file mode 100644
index 00000000..e5d4c26f
--- /dev/null
+++ b/Xamarin.Forms.Core/IExpressionSearch.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Xamarin.Forms
+{
+ internal interface IExpressionSearch
+ {
+ List<T> FindObjects<T>(Expression expression) where T : class;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IExtendedTypeConverter.cs b/Xamarin.Forms.Core/IExtendedTypeConverter.cs
new file mode 100644
index 00000000..e3b16b52
--- /dev/null
+++ b/Xamarin.Forms.Core/IExtendedTypeConverter.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public interface IExtendedTypeConverter
+ {
+ [Obsolete("use ConvertFromInvariantString (string, IServiceProvider)")]
+ object ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider);
+
+ object ConvertFromInvariantString(string value, IServiceProvider serviceProvider);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IFontElement.cs b/Xamarin.Forms.Core/IFontElement.cs
new file mode 100644
index 00000000..f1f3a36f
--- /dev/null
+++ b/Xamarin.Forms.Core/IFontElement.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.Forms
+{
+ internal interface IFontElement
+ {
+ FontAttributes FontAttributes { get; }
+
+ string FontFamily { get; }
+
+ double FontSize { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IGestureRecognizer.cs b/Xamarin.Forms.Core/IGestureRecognizer.cs
new file mode 100644
index 00000000..4e361c0a
--- /dev/null
+++ b/Xamarin.Forms.Core/IGestureRecognizer.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel;
+
+namespace Xamarin.Forms
+{
+ public interface IGestureRecognizer : INotifyPropertyChanged
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IIsolatedStorageFile.cs b/Xamarin.Forms.Core/IIsolatedStorageFile.cs
new file mode 100644
index 00000000..50899023
--- /dev/null
+++ b/Xamarin.Forms.Core/IIsolatedStorageFile.cs
@@ -0,0 +1,18 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal interface IIsolatedStorageFile
+ {
+ Task CreateDirectoryAsync(string path);
+ Task<bool> GetDirectoryExistsAsync(string path);
+ Task<bool> GetFileExistsAsync(string path);
+
+ Task<DateTimeOffset> GetLastWriteTimeAsync(string path);
+
+ Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access);
+ Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IItemViewController.cs b/Xamarin.Forms.Core/IItemViewController.cs
new file mode 100644
index 00000000..79f711e9
--- /dev/null
+++ b/Xamarin.Forms.Core/IItemViewController.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public interface IItemViewController
+ {
+ void BindView(View view, object item);
+ View CreateView(object itemType);
+ object GetItem(int index);
+ object GetItemType(object item);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IItemsView.cs b/Xamarin.Forms.Core/IItemsView.cs
new file mode 100644
index 00000000..7f968769
--- /dev/null
+++ b/Xamarin.Forms.Core/IItemsView.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ internal interface IItemsView<T> where T : BindableObject
+ {
+ T CreateDefault(object item);
+ void SetupContent(T content, int index);
+ void UnhookContent(T content);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ILayout.cs b/Xamarin.Forms.Core/ILayout.cs
new file mode 100644
index 00000000..781111a8
--- /dev/null
+++ b/Xamarin.Forms.Core/ILayout.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public interface ILayout
+ {
+ event EventHandler LayoutChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ILayoutController.cs b/Xamarin.Forms.Core/ILayoutController.cs
new file mode 100644
index 00000000..56bff7e0
--- /dev/null
+++ b/Xamarin.Forms.Core/ILayoutController.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public interface ILayoutController
+ {
+ IReadOnlyList<Element> Children { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IListViewController.cs b/Xamarin.Forms.Core/IListViewController.cs
new file mode 100644
index 00000000..078f13b7
--- /dev/null
+++ b/Xamarin.Forms.Core/IListViewController.cs
@@ -0,0 +1,15 @@
+namespace Xamarin.Forms
+{
+ internal interface IListViewController : IViewController
+ {
+ Element FooterElement { get; }
+
+ Element HeaderElement { get; }
+
+ bool RefreshAllowed { get; }
+
+ void SendCellAppearing(Cell cell);
+ void SendCellDisappearing(Cell cell);
+ void SendRefreshing();
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IMarkupExtension.cs b/Xamarin.Forms.Core/IMarkupExtension.cs
new file mode 100644
index 00000000..24a435f9
--- /dev/null
+++ b/Xamarin.Forms.Core/IMarkupExtension.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ public interface IMarkupExtension<out T> : IMarkupExtension
+ {
+ new T ProvideValue(IServiceProvider serviceProvider);
+ }
+
+ public interface IMarkupExtension
+ {
+ object ProvideValue(IServiceProvider serviceProvider);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/INavigation.cs b/Xamarin.Forms.Core/INavigation.cs
new file mode 100644
index 00000000..6ae5959d
--- /dev/null
+++ b/Xamarin.Forms.Core/INavigation.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ public interface INavigation
+ {
+ IReadOnlyList<Page> ModalStack { get; }
+
+ IReadOnlyList<Page> NavigationStack { get; }
+
+ void InsertPageBefore(Page page, Page before);
+ Task<Page> PopAsync();
+ Task<Page> PopAsync(bool animated);
+ Task<Page> PopModalAsync();
+ Task<Page> PopModalAsync(bool animated);
+ Task PopToRootAsync();
+ Task PopToRootAsync(bool animated);
+
+ Task PushAsync(Page page);
+
+ Task PushAsync(Page page, bool animated);
+ Task PushModalAsync(Page page);
+ Task PushModalAsync(Page page, bool animated);
+
+ void RemovePage(Page page);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IOpenGlViewController.cs b/Xamarin.Forms.Core/IOpenGlViewController.cs
new file mode 100644
index 00000000..d89c5fef
--- /dev/null
+++ b/Xamarin.Forms.Core/IOpenGlViewController.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public interface IOpenGlViewController : IViewController
+ {
+ event EventHandler DisplayRequested;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IPageContainer.cs b/Xamarin.Forms.Core/IPageContainer.cs
new file mode 100644
index 00000000..9582e5e2
--- /dev/null
+++ b/Xamarin.Forms.Core/IPageContainer.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ public interface IPageContainer<out T> where T : Page
+ {
+ T CurrentPage { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IPanGestureController.cs b/Xamarin.Forms.Core/IPanGestureController.cs
new file mode 100644
index 00000000..96283092
--- /dev/null
+++ b/Xamarin.Forms.Core/IPanGestureController.cs
@@ -0,0 +1,13 @@
+namespace Xamarin.Forms
+{
+ internal interface IPanGestureController
+ {
+ void SendPan(Element sender, double totalX, double totalY, int gestureId);
+
+ void SendPanCanceled(Element sender, int gestureId);
+
+ void SendPanCompleted(Element sender, int gestureId);
+
+ void SendPanStarted(Element sender, int gestureId);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IPinchGestureController.cs b/Xamarin.Forms.Core/IPinchGestureController.cs
new file mode 100644
index 00000000..9848fa74
--- /dev/null
+++ b/Xamarin.Forms.Core/IPinchGestureController.cs
@@ -0,0 +1,15 @@
+namespace Xamarin.Forms
+{
+ internal interface IPinchGestureController
+ {
+ bool IsPinching { get; set; }
+
+ void SendPinch(Element sender, double scale, Point currentScalePoint);
+
+ void SendPinchCanceled(Element sender);
+
+ void SendPinchEnded(Element sender);
+
+ void SendPinchStarted(Element sender, Point intialScalePoint);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IPlatform.cs b/Xamarin.Forms.Core/IPlatform.cs
new file mode 100644
index 00000000..507abcfe
--- /dev/null
+++ b/Xamarin.Forms.Core/IPlatform.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ internal interface IPlatform
+ {
+ SizeRequest GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IPlatformServices.cs b/Xamarin.Forms.Core/IPlatformServices.cs
new file mode 100644
index 00000000..e4a3ce0b
--- /dev/null
+++ b/Xamarin.Forms.Core/IPlatformServices.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal interface IPlatformServices
+ {
+ bool IsInvokeRequired { get; }
+
+ void BeginInvokeOnMainThread(Action action);
+
+ //this will go once Timer is included in Pcl profiles
+ ITimer CreateTimer(Action<object> callback);
+ ITimer CreateTimer(Action<object> callback, object state, int dueTime, int period);
+ ITimer CreateTimer(Action<object> callback, object state, long dueTime, long period);
+ ITimer CreateTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period);
+ ITimer CreateTimer(Action<object> callback, object state, uint dueTime, uint period);
+
+ Assembly[] GetAssemblies();
+
+ string GetMD5Hash(string input);
+
+ double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes);
+
+ Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken);
+
+ IIsolatedStorageFile GetUserStoreForApplication();
+
+ void OpenUriAction(Uri uri);
+
+ void StartTimer(TimeSpan interval, Func<bool> callback);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IProvideParentValues.cs b/Xamarin.Forms.Core/IProvideParentValues.cs
new file mode 100644
index 00000000..c07c606f
--- /dev/null
+++ b/Xamarin.Forms.Core/IProvideParentValues.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal interface IProvideParentValues : IProvideValueTarget
+ {
+ IEnumerable<object> ParentObjects { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IProvideValueTarget.cs b/Xamarin.Forms.Core/IProvideValueTarget.cs
new file mode 100644
index 00000000..b8324b5e
--- /dev/null
+++ b/Xamarin.Forms.Core/IProvideValueTarget.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms.Xaml
+{
+ public interface IProvideValueTarget
+ {
+ object TargetObject { get; }
+
+ object TargetProperty { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IRegisterable.cs b/Xamarin.Forms.Core/IRegisterable.cs
new file mode 100644
index 00000000..995ebbce
--- /dev/null
+++ b/Xamarin.Forms.Core/IRegisterable.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ public interface IRegisterable
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IResourceDictionary.cs b/Xamarin.Forms.Core/IResourceDictionary.cs
new file mode 100644
index 00000000..5fccb9d9
--- /dev/null
+++ b/Xamarin.Forms.Core/IResourceDictionary.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal interface IResourceDictionary : IEnumerable<KeyValuePair<string, object>>
+ {
+ bool TryGetValue(string key, out object value);
+
+ event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IResourcesProvider.cs b/Xamarin.Forms.Core/IResourcesProvider.cs
new file mode 100644
index 00000000..e06ef743
--- /dev/null
+++ b/Xamarin.Forms.Core/IResourcesProvider.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ internal interface IResourcesProvider
+ {
+ ResourceDictionary Resources { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IRootObjectProvider.cs b/Xamarin.Forms.Core/IRootObjectProvider.cs
new file mode 100644
index 00000000..883033a5
--- /dev/null
+++ b/Xamarin.Forms.Core/IRootObjectProvider.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms.Xaml
+{
+ public interface IRootObjectProvider
+ {
+ object RootObject { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IScrollViewController.cs b/Xamarin.Forms.Core/IScrollViewController.cs
new file mode 100644
index 00000000..c06ddc09
--- /dev/null
+++ b/Xamarin.Forms.Core/IScrollViewController.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public interface IScrollViewController : ILayoutController
+ {
+ Point GetScrollPositionForElement(VisualElement item, ScrollToPosition position);
+
+ event EventHandler<ScrollToRequestedEventArgs> ScrollToRequested;
+
+ void SendScrollFinished();
+
+ void SetScrolledPosition(double x, double y);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IStyle.cs b/Xamarin.Forms.Core/IStyle.cs
new file mode 100644
index 00000000..cdb998dc
--- /dev/null
+++ b/Xamarin.Forms.Core/IStyle.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal interface IStyle
+ {
+ Type TargetType { get; }
+
+ void Apply(BindableObject bindable);
+ void UnApply(BindableObject bindable);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ISystemResourcesProvider.cs b/Xamarin.Forms.Core/ISystemResourcesProvider.cs
new file mode 100644
index 00000000..9f0e5772
--- /dev/null
+++ b/Xamarin.Forms.Core/ISystemResourcesProvider.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ internal interface ISystemResourcesProvider
+ {
+ IResourceDictionary GetSystemResources();
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ITimer.cs b/Xamarin.Forms.Core/ITimer.cs
new file mode 100644
index 00000000..ba867dba
--- /dev/null
+++ b/Xamarin.Forms.Core/ITimer.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ //this will go once Timer is included in Pcl profiles
+ internal interface ITimer
+ {
+ void Change(int dueTime, int period);
+ void Change(long dueTime, long period);
+ void Change(TimeSpan dueTime, TimeSpan period);
+ void Change(uint dueTime, uint period);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IValueConverter.cs b/Xamarin.Forms.Core/IValueConverter.cs
new file mode 100644
index 00000000..8702ec3d
--- /dev/null
+++ b/Xamarin.Forms.Core/IValueConverter.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public interface IValueConverter
+ {
+ object Convert(object value, Type targetType, object parameter, CultureInfo culture);
+ object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IValueConverterProvider.cs b/Xamarin.Forms.Core/IValueConverterProvider.cs
new file mode 100644
index 00000000..1221ce74
--- /dev/null
+++ b/Xamarin.Forms.Core/IValueConverterProvider.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Reflection;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal interface IValueConverterProvider
+ {
+ object Convert(object value, Type toType, Func<MemberInfo> minfoRetriever, IServiceProvider serviceProvider);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IValueProvider.cs b/Xamarin.Forms.Core/IValueProvider.cs
new file mode 100644
index 00000000..19d3bf93
--- /dev/null
+++ b/Xamarin.Forms.Core/IValueProvider.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ public interface IValueProvider
+ {
+ object ProvideValue(IServiceProvider serviceProvider);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IViewContainer.cs b/Xamarin.Forms.Core/IViewContainer.cs
new file mode 100644
index 00000000..9b2e0570
--- /dev/null
+++ b/Xamarin.Forms.Core/IViewContainer.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public interface IViewContainer<T> where T : VisualElement
+ {
+ IList<T> Children { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IViewController.cs b/Xamarin.Forms.Core/IViewController.cs
new file mode 100644
index 00000000..1d956387
--- /dev/null
+++ b/Xamarin.Forms.Core/IViewController.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ public interface IViewController : IVisualElementController
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IVisualElementController.cs b/Xamarin.Forms.Core/IVisualElementController.cs
new file mode 100644
index 00000000..03be293a
--- /dev/null
+++ b/Xamarin.Forms.Core/IVisualElementController.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ public interface IVisualElementController : IElementController
+ {
+ void NativeSizeChanged();
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IWebViewRenderer.cs b/Xamarin.Forms.Core/IWebViewRenderer.cs
new file mode 100644
index 00000000..0b1b3db5
--- /dev/null
+++ b/Xamarin.Forms.Core/IWebViewRenderer.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ internal interface IWebViewRenderer
+ {
+ void LoadHtml(string html, string baseUrl);
+ void LoadUrl(string url);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IXamlTypeResolver.cs b/Xamarin.Forms.Core/IXamlTypeResolver.cs
new file mode 100644
index 00000000..1708e0b1
--- /dev/null
+++ b/Xamarin.Forms.Core/IXamlTypeResolver.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ public interface IXamlTypeResolver
+ {
+ Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider = null);
+ bool TryResolve(string qualifiedTypeName, out Type type);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/IXmlLineInfoProvider.cs b/Xamarin.Forms.Core/IXmlLineInfoProvider.cs
new file mode 100644
index 00000000..348f4d4d
--- /dev/null
+++ b/Xamarin.Forms.Core/IXmlLineInfoProvider.cs
@@ -0,0 +1,9 @@
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ public interface IXmlLineInfoProvider
+ {
+ IXmlLineInfo XmlLineInfo { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Image.cs b/Xamarin.Forms.Core/Image.cs
new file mode 100644
index 00000000..9e329331
--- /dev/null
+++ b/Xamarin.Forms.Core/Image.cs
@@ -0,0 +1,145 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_ImageRenderer))]
+ public class Image : View
+ {
+ public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(ImageSource), typeof(Image), default(ImageSource), propertyChanging: OnSourcePropertyChanging,
+ propertyChanged: OnSourcePropertyChanged);
+
+ public static readonly BindableProperty AspectProperty = BindableProperty.Create("Aspect", typeof(Aspect), typeof(Image), Aspect.AspectFit);
+
+ public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create("IsOpaque", typeof(bool), typeof(Image), false);
+
+ internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly("IsLoading", typeof(bool), typeof(Image), default(bool));
+
+ public static readonly BindableProperty IsLoadingProperty = IsLoadingPropertyKey.BindableProperty;
+
+ public Aspect Aspect
+ {
+ get { return (Aspect)GetValue(AspectProperty); }
+ set { SetValue(AspectProperty, value); }
+ }
+
+ public bool IsLoading
+ {
+ get { return (bool)GetValue(IsLoadingProperty); }
+ }
+
+ public bool IsOpaque
+ {
+ get { return (bool)GetValue(IsOpaqueProperty); }
+ set { SetValue(IsOpaqueProperty, value); }
+ }
+
+ [TypeConverter(typeof(ImageSourceConverter))]
+ public ImageSource Source
+ {
+ get { return (ImageSource)GetValue(SourceProperty); }
+ set { SetValue(SourceProperty, value); }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ if (Source != null)
+ SetInheritedBindingContext(Source, BindingContext);
+
+ base.OnBindingContextChanged();
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ SizeRequest desiredSize = base.OnSizeRequest(double.PositiveInfinity, double.PositiveInfinity);
+
+ double desiredAspect = desiredSize.Request.Width / desiredSize.Request.Height;
+ double constraintAspect = widthConstraint / heightConstraint;
+
+ double desiredWidth = desiredSize.Request.Width;
+ double desiredHeight = desiredSize.Request.Height;
+
+ if (desiredWidth == 0 || desiredHeight == 0)
+ return new SizeRequest(new Size(0, 0));
+
+ double width = desiredWidth;
+ double height = desiredHeight;
+ if (constraintAspect > desiredAspect)
+ {
+ // constraint area is proportionally wider than image
+ switch (Aspect)
+ {
+ case Aspect.AspectFit:
+ case Aspect.AspectFill:
+ height = Math.Min(desiredHeight, heightConstraint);
+ width = desiredWidth * (height / desiredHeight);
+ break;
+ case Aspect.Fill:
+ width = Math.Min(desiredWidth, widthConstraint);
+ height = desiredHeight * (width / desiredWidth);
+ break;
+ }
+ }
+ else if (constraintAspect < desiredAspect)
+ {
+ // constraint area is proportionally taller than image
+ switch (Aspect)
+ {
+ case Aspect.AspectFit:
+ case Aspect.AspectFill:
+ width = Math.Min(desiredWidth, widthConstraint);
+ height = desiredHeight * (width / desiredWidth);
+ break;
+ case Aspect.Fill:
+ height = Math.Min(desiredHeight, heightConstraint);
+ width = desiredWidth * (height / desiredHeight);
+ break;
+ }
+ }
+ else
+ {
+ // constraint area is same aspect as image
+ width = Math.Min(desiredWidth, widthConstraint);
+ height = desiredHeight * (width / desiredWidth);
+ }
+
+ return new SizeRequest(new Size(width, height));
+ }
+
+ void OnSourceChanged(object sender, EventArgs eventArgs)
+ {
+ OnPropertyChanged(SourceProperty.PropertyName);
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ static void OnSourcePropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ ((Image)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue);
+ }
+
+ void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
+ {
+ if (newvalue != null)
+ {
+ newvalue.SourceChanged += OnSourceChanged;
+ SetInheritedBindingContext(newvalue, BindingContext);
+ }
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ static void OnSourcePropertyChanging(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ ((Image)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue);
+ }
+
+ async void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
+ {
+ if (oldvalue == null)
+ return;
+
+ oldvalue.SourceChanged -= OnSourceChanged;
+ await oldvalue.Cancel();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ImageSource.cs b/Xamarin.Forms.Core/ImageSource.cs
new file mode 100644
index 00000000..71e75952
--- /dev/null
+++ b/Xamarin.Forms.Core/ImageSource.cs
@@ -0,0 +1,142 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(ImageSourceConverter))]
+ public abstract class ImageSource : Element
+ {
+ readonly object _synchandle = new object();
+ CancellationTokenSource _cancellationTokenSource;
+
+ TaskCompletionSource<bool> _completionSource;
+
+ internal ImageSource()
+ {
+ }
+
+ protected CancellationTokenSource CancellationTokenSource
+ {
+ get { return _cancellationTokenSource; }
+ private set
+ {
+ if (_cancellationTokenSource == value)
+ return;
+ if (_cancellationTokenSource != null)
+ _cancellationTokenSource.Cancel();
+ _cancellationTokenSource = value;
+ }
+ }
+
+ bool IsLoading
+ {
+ get { return _cancellationTokenSource != null; }
+ }
+
+ public virtual Task<bool> Cancel()
+ {
+ if (!IsLoading)
+ return Task.FromResult(false);
+
+ var tcs = new TaskCompletionSource<bool>();
+ TaskCompletionSource<bool> original = Interlocked.CompareExchange(ref _completionSource, tcs, null);
+ if (original == null)
+ {
+ _cancellationTokenSource.Cancel();
+ }
+ else
+ tcs = original;
+
+ return tcs.Task;
+ }
+
+ public static ImageSource FromFile(string file)
+ {
+ return new FileImageSource { File = file };
+ }
+
+ public static ImageSource FromResource(string resource, Type resolvingType)
+ {
+ return FromResource(resource, resolvingType.GetTypeInfo().Assembly);
+ }
+
+ public static ImageSource FromResource(string resource, Assembly sourceAssembly = null)
+ {
+ if (sourceAssembly == null)
+ {
+ MethodInfo callingAssemblyMethod = typeof(Assembly).GetTypeInfo().GetDeclaredMethod("GetCallingAssembly");
+ if (callingAssemblyMethod != null)
+ {
+ sourceAssembly = (Assembly)callingAssemblyMethod.Invoke(null, new object[0]);
+ }
+ else
+ {
+ Log.Warning("Warning", "Can not find CallingAssembly, pass resolvingType to FromResource to ensure proper resolution");
+ return null;
+ }
+ }
+
+ return FromStream(() => sourceAssembly.GetManifestResourceStream(resource));
+ }
+
+ public static ImageSource FromStream(Func<Stream> stream)
+ {
+ return new StreamImageSource { Stream = token => Task.Run(stream, token) };
+ }
+
+ public static ImageSource FromUri(Uri uri)
+ {
+ if (!uri.IsAbsoluteUri)
+ throw new ArgumentException("uri is relative");
+ return new UriImageSource { Uri = uri };
+ }
+
+ public static implicit operator ImageSource(string source)
+ {
+ Uri uri;
+ return Uri.TryCreate(source, UriKind.Absolute, out uri) && uri.Scheme != "file" ? FromUri(uri) : FromFile(source);
+ }
+
+ public static implicit operator ImageSource(Uri uri)
+ {
+ if (!uri.IsAbsoluteUri)
+ throw new ArgumentException("uri is relative");
+ return FromUri(uri);
+ }
+
+ protected void OnLoadingCompleted(bool cancelled)
+ {
+ if (!IsLoading || _completionSource == null)
+ return;
+
+ TaskCompletionSource<bool> tcs = Interlocked.Exchange(ref _completionSource, null);
+ if (tcs != null)
+ tcs.SetResult(cancelled);
+
+ lock(_synchandle)
+ {
+ CancellationTokenSource = null;
+ }
+ }
+
+ protected void OnLoadingStarted()
+ {
+ lock(_synchandle)
+ {
+ CancellationTokenSource = new CancellationTokenSource();
+ }
+ }
+
+ protected void OnSourceChanged()
+ {
+ EventHandler eh = SourceChanged;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+
+ internal event EventHandler SourceChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ImageSourceConverter.cs b/Xamarin.Forms.Core/ImageSourceConverter.cs
new file mode 100644
index 00000000..8ca829f7
--- /dev/null
+++ b/Xamarin.Forms.Core/ImageSourceConverter.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class ImageSourceConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ Uri uri;
+ return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ? ImageSource.FromUri(uri) : ImageSource.FromFile(value);
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(ImageSource)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/InputView.cs b/Xamarin.Forms.Core/InputView.cs
new file mode 100644
index 00000000..58b75c42
--- /dev/null
+++ b/Xamarin.Forms.Core/InputView.cs
@@ -0,0 +1,18 @@
+namespace Xamarin.Forms
+{
+ public class InputView : View
+ {
+ public static readonly BindableProperty KeyboardProperty = BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(InputView), Keyboard.Default,
+ coerceValue: (o, v) => (Keyboard)v ?? Keyboard.Default);
+
+ internal InputView()
+ {
+ }
+
+ public Keyboard Keyboard
+ {
+ get { return (Keyboard)GetValue(KeyboardProperty); }
+ set { SetValue(KeyboardProperty, value); }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs b/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs
new file mode 100644
index 00000000..6aff5147
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms
+{
+ internal class AttachedCollection<T> : ObservableCollection<T>, ICollection<T>, IAttachedObject where T : BindableObject, IAttachedObject
+ {
+ readonly List<WeakReference> _associatedObjects = new List<WeakReference>();
+
+ public AttachedCollection()
+ {
+ }
+
+ public AttachedCollection(IEnumerable<T> collection) : base(collection)
+ {
+ }
+
+ public AttachedCollection(IList<T> list) : base(list)
+ {
+ }
+
+ public void AttachTo(BindableObject bindable)
+ {
+ if (bindable == null)
+ throw new ArgumentNullException("bindable");
+ OnAttachedTo(bindable);
+ }
+
+ public void DetachFrom(BindableObject bindable)
+ {
+ OnDetachingFrom(bindable);
+ }
+
+ protected override void ClearItems()
+ {
+ foreach (WeakReference weakbindable in _associatedObjects)
+ {
+ foreach (T item in this)
+ {
+ var bindable = weakbindable.Target as BindableObject;
+ if (bindable == null)
+ continue;
+ item.DetachFrom(bindable);
+ }
+ }
+ base.ClearItems();
+ }
+
+ protected override void InsertItem(int index, T item)
+ {
+ base.InsertItem(index, item);
+ foreach (WeakReference weakbindable in _associatedObjects)
+ {
+ var bindable = weakbindable.Target as BindableObject;
+ if (bindable == null)
+ continue;
+ item.AttachTo(bindable);
+ }
+ }
+
+ protected virtual void OnAttachedTo(BindableObject bindable)
+ {
+ lock(_associatedObjects)
+ {
+ _associatedObjects.Add(new WeakReference(bindable));
+ }
+ foreach (T item in this)
+ item.AttachTo(bindable);
+ }
+
+ protected virtual void OnDetachingFrom(BindableObject bindable)
+ {
+ foreach (T item in this)
+ item.DetachFrom(bindable);
+ lock(_associatedObjects)
+ {
+ for (var i = 0; i < _associatedObjects.Count; i++)
+ {
+ object target = _associatedObjects[i].Target;
+
+ if (target == null || target == bindable)
+ {
+ _associatedObjects.RemoveAt(i);
+ i--;
+ }
+ }
+ }
+ }
+
+ protected override void RemoveItem(int index)
+ {
+ T item = this[index];
+ foreach (WeakReference weakbindable in _associatedObjects)
+ {
+ var bindable = weakbindable.Target as BindableObject;
+ if (bindable == null)
+ continue;
+ item.DetachFrom(bindable);
+ }
+
+ base.RemoveItem(index);
+ }
+
+ protected override void SetItem(int index, T item)
+ {
+ T old = this[index];
+ foreach (WeakReference weakbindable in _associatedObjects)
+ {
+ var bindable = weakbindable.Target as BindableObject;
+ if (bindable == null)
+ continue;
+ old.DetachFrom(bindable);
+ }
+
+ base.SetItem(index, item);
+
+ foreach (WeakReference weakbindable in _associatedObjects)
+ {
+ var bindable = weakbindable.Target as BindableObject;
+ if (bindable == null)
+ continue;
+ item.AttachTo(bindable);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/Behavior.cs b/Xamarin.Forms.Core/Interactivity/Behavior.cs
new file mode 100644
index 00000000..f8689041
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/Behavior.cs
@@ -0,0 +1,65 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class Behavior : BindableObject, IAttachedObject
+ {
+ internal Behavior(Type associatedType)
+ {
+ if (associatedType == null)
+ throw new ArgumentNullException("associatedType");
+ AssociatedType = associatedType;
+ }
+
+ protected Type AssociatedType { get; }
+
+ void IAttachedObject.AttachTo(BindableObject bindable)
+ {
+ if (bindable == null)
+ throw new ArgumentNullException("bindable");
+ if (!AssociatedType.IsInstanceOfType(bindable))
+ throw new InvalidOperationException("bindable not an instance of AssociatedType");
+ OnAttachedTo(bindable);
+ }
+
+ void IAttachedObject.DetachFrom(BindableObject bindable)
+ {
+ OnDetachingFrom(bindable);
+ }
+
+ protected virtual void OnAttachedTo(BindableObject bindable)
+ {
+ }
+
+ protected virtual void OnDetachingFrom(BindableObject bindable)
+ {
+ }
+ }
+
+ public abstract class Behavior<T> : Behavior where T : BindableObject
+ {
+ protected Behavior() : base(typeof(T))
+ {
+ }
+
+ protected override void OnAttachedTo(BindableObject bindable)
+ {
+ base.OnAttachedTo(bindable);
+ OnAttachedTo((T)bindable);
+ }
+
+ protected virtual void OnAttachedTo(T bindable)
+ {
+ }
+
+ protected override void OnDetachingFrom(BindableObject bindable)
+ {
+ OnDetachingFrom((T)bindable);
+ base.OnDetachingFrom(bindable);
+ }
+
+ protected virtual void OnDetachingFrom(T bindable)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/BindingCondition.cs b/Xamarin.Forms.Core/Interactivity/BindingCondition.cs
new file mode 100644
index 00000000..88b7cf36
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/BindingCondition.cs
@@ -0,0 +1,100 @@
+using System;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ public sealed class BindingCondition : Condition, IValueProvider
+ {
+ readonly BindableProperty _boundProperty;
+
+ BindingBase _binding;
+ object _triggerValue;
+
+ public BindingCondition()
+ {
+ _boundProperty = BindableProperty.CreateAttached("Bound", typeof(object), typeof(DataTrigger), null, propertyChanged: OnBoundPropertyChanged);
+ }
+
+ public BindingBase Binding
+ {
+ get { return _binding; }
+ set
+ {
+ if (_binding == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Binding once the Trigger has been applied.");
+ _binding = value;
+ }
+ }
+
+ public object Value
+ {
+ get { return _triggerValue; }
+ set
+ {
+ if (_triggerValue == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Value once the Trigger has been applied.");
+ _triggerValue = value;
+ }
+ }
+
+ internal IServiceProvider ServiceProvider { get; set; }
+
+ internal IValueConverterProvider ValueConverter { get; set; }
+
+ object IValueProvider.ProvideValue(IServiceProvider serviceProvider)
+ {
+ ValueConverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider;
+ ServiceProvider = serviceProvider;
+
+ return this;
+ }
+
+ internal override bool GetState(BindableObject bindable)
+ {
+ object newValue = bindable.GetValue(_boundProperty);
+ return EqualsToValue(newValue);
+ }
+
+ internal override void SetUp(BindableObject bindable)
+ {
+ if (Binding != null)
+ bindable.SetBinding(_boundProperty, Binding.Clone());
+ }
+
+ internal override void TearDown(BindableObject bindable)
+ {
+ bindable.RemoveBinding(_boundProperty);
+ bindable.ClearValue(_boundProperty);
+ }
+
+ bool EqualsToValue(object other)
+ {
+ if ((other == Value) || (other != null && other.Equals(Value)))
+ return true;
+
+ object converted = null;
+ if (ValueConverter != null)
+ converted = ValueConverter.Convert(Value, other != null ? other.GetType() : typeof(object), null, ServiceProvider);
+ else
+ return false;
+
+ return (other == converted) || (other != null && other.Equals(converted));
+ }
+
+ void OnBoundPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ bool oldState = EqualsToValue(oldValue);
+ bool newState = EqualsToValue(newValue);
+
+ if (newState == oldState)
+ return;
+
+ if (ConditionChanged != null)
+ ConditionChanged(bindable, oldState, newState);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/Condition.cs b/Xamarin.Forms.Core/Interactivity/Condition.cs
new file mode 100644
index 00000000..aad921cf
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/Condition.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class Condition
+ {
+ Action<BindableObject, bool, bool> _conditionChanged;
+
+ bool _isSealed;
+
+ internal Condition()
+ {
+ }
+
+ internal Action<BindableObject, bool, bool> ConditionChanged
+ {
+ get { return _conditionChanged; }
+ set
+ {
+ if (_conditionChanged == value)
+ return;
+ if (_conditionChanged != null)
+ throw new InvalidOperationException("The same condition instance can not be reused");
+ _conditionChanged = value;
+ }
+ }
+
+ internal bool IsSealed
+ {
+ get { return _isSealed; }
+ set
+ {
+ if (_isSealed == value)
+ return;
+ if (!value)
+ throw new InvalidOperationException("What is sealed can not be unsealed.");
+ _isSealed = value;
+ OnSealed();
+ }
+ }
+
+ internal abstract bool GetState(BindableObject bindable);
+
+ internal virtual void OnSealed()
+ {
+ }
+
+ internal abstract void SetUp(BindableObject bindable);
+ internal abstract void TearDown(BindableObject bindable);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/DataTrigger.cs b/Xamarin.Forms.Core/Interactivity/DataTrigger.cs
new file mode 100644
index 00000000..e27ec134
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/DataTrigger.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Setters")]
+ public sealed class DataTrigger : TriggerBase, IValueProvider
+ {
+ public DataTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new BindingCondition(), targetType)
+ {
+ }
+
+ public BindingBase Binding
+ {
+ get { return ((BindingCondition)Condition).Binding; }
+ set
+ {
+ if (((BindingCondition)Condition).Binding == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Binding once the Trigger has been applied.");
+ OnPropertyChanging();
+ ((BindingCondition)Condition).Binding = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public new IList<Setter> Setters
+ {
+ get { return base.Setters; }
+ }
+
+ public object Value
+ {
+ get { return ((BindingCondition)Condition).Value; }
+ set
+ {
+ if (((BindingCondition)Condition).Value == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Value once the Trigger has been applied.");
+ OnPropertyChanging();
+ ((BindingCondition)Condition).Value = value;
+ OnPropertyChanged();
+ }
+ }
+
+ object IValueProvider.ProvideValue(IServiceProvider serviceProvider)
+ {
+ var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider;
+ (Condition as BindingCondition).ValueConverter = valueconverter;
+
+ return this;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/EventTrigger.cs b/Xamarin.Forms.Core/Interactivity/EventTrigger.cs
new file mode 100644
index 00000000..52e221a0
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/EventTrigger.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Actions")]
+ public sealed class EventTrigger : TriggerBase
+ {
+ static readonly MethodInfo s_handlerinfo = typeof(EventTrigger).GetRuntimeMethods().Single(mi => mi.Name == "OnEventTriggered" && mi.IsPublic == false);
+ readonly List<BindableObject> _associatedObjects = new List<BindableObject>();
+
+ EventInfo _eventinfo;
+
+ string _eventname;
+ Delegate _handlerdelegate;
+
+ public EventTrigger() : base(typeof(BindableObject))
+ {
+ Actions = new SealedList<TriggerAction>();
+ }
+
+ public IList<TriggerAction> Actions { get; }
+
+ public string Event
+ {
+ get { return _eventname; }
+ set
+ {
+ if (_eventname == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Event cannot be changed once the Trigger has been applied");
+ OnPropertyChanging();
+ _eventname = value;
+ OnPropertyChanged();
+ }
+ }
+
+ internal override void OnAttachedTo(BindableObject bindable)
+ {
+ base.OnAttachedTo(bindable);
+ if (!string.IsNullOrEmpty(Event))
+ AttachHandlerTo(bindable);
+ _associatedObjects.Add(bindable);
+ }
+
+ internal override void OnDetachingFrom(BindableObject bindable)
+ {
+ _associatedObjects.Remove(bindable);
+ DetachHandlerFrom(bindable);
+ base.OnDetachingFrom(bindable);
+ }
+
+ internal override void OnSeal()
+ {
+ base.OnSeal();
+ ((SealedList<TriggerAction>)Actions).IsReadOnly = true;
+ }
+
+ void AttachHandlerTo(BindableObject bindable)
+ {
+ try
+ {
+ _eventinfo = bindable.GetType().GetRuntimeEvent(Event);
+ _handlerdelegate = s_handlerinfo.CreateDelegate(_eventinfo.EventHandlerType, this);
+ }
+ catch (Exception)
+ {
+ Log.Warning("EventTrigger", "Can not attach EventTrigger to {0}.{1}. Check if the handler exists and if the signature is right.", bindable.GetType(), Event);
+ }
+ if (_eventinfo != null && _handlerdelegate != null)
+ _eventinfo.AddEventHandler(bindable, _handlerdelegate);
+ }
+
+ void DetachHandlerFrom(BindableObject bindable)
+ {
+ if (_eventinfo != null && _handlerdelegate != null)
+ _eventinfo.RemoveEventHandler(bindable, _handlerdelegate);
+ }
+
+ [Preserve]
+ void OnEventTriggered(object sender, EventArgs e)
+ {
+ var bindable = (BindableObject)sender;
+ foreach (TriggerAction action in Actions)
+ action.DoInvoke(bindable);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs b/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs
new file mode 100644
index 00000000..09748873
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/IAttachedObject.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ internal interface IAttachedObject
+ {
+ void AttachTo(BindableObject bindable);
+ void DetachFrom(BindableObject bindable);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/MultiCondition.cs b/Xamarin.Forms.Core/Interactivity/MultiCondition.cs
new file mode 100644
index 00000000..23ca41c5
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/MultiCondition.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal sealed class MultiCondition : Condition
+ {
+ readonly BindableProperty _aggregatedStateProperty;
+
+ public MultiCondition()
+ {
+ _aggregatedStateProperty = BindableProperty.CreateAttached("AggregatedState", typeof(bool), typeof(DataTrigger), false, propertyChanged: OnAggregatedStatePropertyChanged);
+ Conditions = new TriggerBase.SealedList<Condition>();
+ }
+
+ public IList<Condition> Conditions { get; }
+
+ internal override bool GetState(BindableObject bindable)
+ {
+ return (bool)bindable.GetValue(_aggregatedStateProperty);
+ }
+
+ internal override void OnSealed()
+ {
+ ((TriggerBase.SealedList<Condition>)Conditions).IsReadOnly = true;
+ foreach (Condition condition in Conditions)
+ condition.ConditionChanged = OnConditionChanged;
+ }
+
+ internal override void SetUp(BindableObject bindable)
+ {
+ foreach (Condition condition in Conditions)
+ condition.SetUp(bindable);
+ }
+
+ internal override void TearDown(BindableObject bindable)
+ {
+ foreach (Condition condition in Conditions)
+ condition.TearDown(bindable);
+ }
+
+ void OnAggregatedStatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if ((bool)oldValue == (bool)newValue)
+ return;
+
+ if (ConditionChanged != null)
+ ConditionChanged(bindable, (bool)oldValue, (bool)newValue);
+ }
+
+ void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue)
+ {
+ var oldState = (bool)bindable.GetValue(_aggregatedStateProperty);
+ var newState = true;
+ foreach (Condition condition in Conditions)
+ {
+ if (!condition.GetState(bindable))
+ {
+ newState = false;
+ break;
+ }
+ }
+ if (newState != oldState)
+ bindable.SetValue(_aggregatedStateProperty, newState);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs b/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs
new file mode 100644
index 00000000..3c85467c
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/MultiTrigger.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Setters")]
+ public sealed class MultiTrigger : TriggerBase
+ {
+ public MultiTrigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new MultiCondition(), targetType)
+ {
+ }
+
+ public IList<Condition> Conditions
+ {
+ get { return ((MultiCondition)Condition).Conditions; }
+ }
+
+ public new IList<Setter> Setters
+ {
+ get { return base.Setters; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs b/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs
new file mode 100644
index 00000000..be37d48f
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/PropertyCondition.cs
@@ -0,0 +1,100 @@
+using System;
+using System.ComponentModel;
+using System.Reflection;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ public sealed class PropertyCondition : Condition, IValueProvider
+ {
+ readonly BindableProperty _stateProperty;
+
+ BindableProperty _property;
+ object _triggerValue;
+
+ public PropertyCondition()
+ {
+ _stateProperty = BindableProperty.CreateAttached("State", typeof(bool), typeof(DataTrigger), false, propertyChanged: OnStatePropertyChanged);
+ }
+
+ public BindableProperty Property
+ {
+ get { return _property; }
+ set
+ {
+ if (_property == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Property once the Trigger has been applied.");
+ _property = value;
+ }
+ }
+
+ public object Value
+ {
+ get { return _triggerValue; }
+ set
+ {
+ if (_triggerValue == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Value once the Trigger has been applied.");
+ _triggerValue = value;
+ }
+ }
+
+ object IValueProvider.ProvideValue(IServiceProvider serviceProvider)
+ {
+ var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider;
+ Func<MemberInfo> minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName);
+
+ object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider);
+ Value = value;
+ return this;
+ }
+
+ internal override bool GetState(BindableObject bindable)
+ {
+ return (bool)bindable.GetValue(_stateProperty);
+ }
+
+ internal override void SetUp(BindableObject bindable)
+ {
+ object newvalue = bindable.GetValue(Property);
+
+ bool newState = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value));
+ bindable.SetValue(_stateProperty, newState);
+ bindable.PropertyChanged += OnAttachedObjectPropertyChanged;
+ }
+
+ internal override void TearDown(BindableObject bindable)
+ {
+ bindable.ClearValue(_stateProperty);
+ bindable.PropertyChanged -= OnAttachedObjectPropertyChanged;
+ }
+
+ void OnAttachedObjectPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var bindable = (BindableObject)sender;
+ var oldState = (bool)bindable.GetValue(_stateProperty);
+
+ if (Property == null)
+ return;
+ if (e.PropertyName != Property.PropertyName)
+ return;
+ object newvalue = bindable.GetValue(Property);
+ bool newstate = (newvalue == Value) || (newvalue != null && newvalue.Equals(Value));
+ if (oldState != newstate)
+ bindable.SetValue(_stateProperty, newstate);
+ }
+
+ void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if ((bool)oldValue == (bool)newValue)
+ return;
+
+ if (ConditionChanged != null)
+ ConditionChanged(bindable, (bool)oldValue, (bool)newValue);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/Trigger.cs b/Xamarin.Forms.Core/Interactivity/Trigger.cs
new file mode 100644
index 00000000..ea3dc5ae
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/Trigger.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Setters")]
+ public sealed class Trigger : TriggerBase, IValueProvider
+ {
+ public Trigger([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType) : base(new PropertyCondition(), targetType)
+ {
+ }
+
+ public BindableProperty Property
+ {
+ get { return ((PropertyCondition)Condition).Property; }
+ set
+ {
+ if (((PropertyCondition)Condition).Property == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Property once the Trigger has been applied.");
+ OnPropertyChanging();
+ ((PropertyCondition)Condition).Property = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public new IList<Setter> Setters
+ {
+ get { return base.Setters; }
+ }
+
+ public object Value
+ {
+ get { return ((PropertyCondition)Condition).Value; }
+ set
+ {
+ if (((PropertyCondition)Condition).Value == value)
+ return;
+ if (IsSealed)
+ throw new InvalidOperationException("Can not change Value once the Trigger has been applied.");
+ OnPropertyChanging();
+ ((PropertyCondition)Condition).Value = value;
+ OnPropertyChanged();
+ }
+ }
+
+ object IValueProvider.ProvideValue(IServiceProvider serviceProvider)
+ {
+ var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider;
+ Func<MemberInfo> minforetriever = () => Property.DeclaringType.GetRuntimeProperty(Property.PropertyName);
+
+ object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider);
+ Value = value;
+ return this;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/TriggerAction.cs b/Xamarin.Forms.Core/Interactivity/TriggerAction.cs
new file mode 100644
index 00000000..bb9dc08f
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/TriggerAction.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class TriggerAction
+ {
+ internal TriggerAction(Type associatedType)
+ {
+ if (associatedType == null)
+ throw new ArgumentNullException("associatedType");
+ AssociatedType = associatedType;
+ }
+
+ protected Type AssociatedType { get; private set; }
+
+ protected abstract void Invoke(object sender);
+
+ internal virtual void DoInvoke(object sender)
+ {
+ Invoke(sender);
+ }
+ }
+
+ public abstract class TriggerAction<T> : TriggerAction where T : BindableObject
+ {
+ protected TriggerAction() : base(typeof(T))
+ {
+ }
+
+ protected override void Invoke(object sender)
+ {
+ Invoke((T)sender);
+ }
+
+ protected abstract void Invoke(T sender);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Interactivity/TriggerBase.cs b/Xamarin.Forms.Core/Interactivity/TriggerBase.cs
new file mode 100644
index 00000000..9418c7ae
--- /dev/null
+++ b/Xamarin.Forms.Core/Interactivity/TriggerBase.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public abstract class TriggerBase : BindableObject, IAttachedObject
+ {
+ bool _isSealed;
+
+ internal TriggerBase(Type targetType)
+ {
+ if (targetType == null)
+ throw new ArgumentNullException("targetType");
+ TargetType = targetType;
+
+ EnterActions = new SealedList<TriggerAction>();
+ ExitActions = new SealedList<TriggerAction>();
+ }
+
+ internal TriggerBase(Condition condition, Type targetType) : this(targetType)
+ {
+ Setters = new SealedList<Setter>();
+ Condition = condition;
+ Condition.ConditionChanged = OnConditionChanged;
+ }
+
+ public IList<TriggerAction> EnterActions { get; }
+
+ public IList<TriggerAction> ExitActions { get; }
+
+ public bool IsSealed
+ {
+ get { return _isSealed; }
+ private set
+ {
+ if (_isSealed == value)
+ return;
+ if (!value)
+ throw new InvalidOperationException("What is sealed can not be unsealed.");
+ _isSealed = value;
+ OnSeal();
+ }
+ }
+
+ public Type TargetType { get; }
+
+ internal Condition Condition { get; }
+
+ //Setters and Condition are used by Trigger, DataTrigger and MultiTrigger
+ internal IList<Setter> Setters { get; }
+
+ void IAttachedObject.AttachTo(BindableObject bindable)
+ {
+ IsSealed = true;
+
+ if (bindable == null)
+ throw new ArgumentNullException("bindable");
+ if (!TargetType.IsInstanceOfType(bindable))
+ throw new InvalidOperationException("bindable not an instance of AssociatedType");
+ OnAttachedTo(bindable);
+ }
+
+ void IAttachedObject.DetachFrom(BindableObject bindable)
+ {
+ if (bindable == null)
+ throw new ArgumentNullException("bindable");
+ OnDetachingFrom(bindable);
+ }
+
+ internal virtual void OnAttachedTo(BindableObject bindable)
+ {
+ if (Condition != null)
+ Condition.SetUp(bindable);
+ }
+
+ internal virtual void OnDetachingFrom(BindableObject bindable)
+ {
+ if (Condition != null)
+ Condition.TearDown(bindable);
+ }
+
+ internal virtual void OnSeal()
+ {
+ ((SealedList<TriggerAction>)EnterActions).IsReadOnly = true;
+ ((SealedList<TriggerAction>)ExitActions).IsReadOnly = true;
+ if (Setters != null)
+ ((SealedList<Setter>)Setters).IsReadOnly = true;
+ if (Condition != null)
+ Condition.IsSealed = true;
+ }
+
+ void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue)
+ {
+ if (newValue)
+ {
+ foreach (TriggerAction action in EnterActions)
+ action.DoInvoke(bindable);
+ foreach (Setter setter in Setters)
+ setter.Apply(bindable);
+ }
+ else
+ {
+ foreach (Setter setter in Setters)
+ setter.UnApply(bindable);
+ foreach (TriggerAction action in ExitActions)
+ action.DoInvoke(bindable);
+ }
+ }
+
+ internal class SealedList<T> : IList<T>
+ {
+ readonly IList<T> _actual;
+
+ bool _isReadOnly;
+
+ public SealedList()
+ {
+ _actual = new List<T>();
+ }
+
+ public void Add(T item)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("This list is ReadOnly");
+ _actual.Add(item);
+ }
+
+ public void Clear()
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("This list is ReadOnly");
+ _actual.Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ return _actual.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ _actual.CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return _actual.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return _isReadOnly; }
+ set
+ {
+ if (_isReadOnly == value)
+ return;
+ if (!value)
+ throw new InvalidOperationException("Can't change this back to non readonly");
+ _isReadOnly = value;
+ }
+ }
+
+ public bool Remove(T item)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("This list is ReadOnly");
+ return _actual.Remove(item);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_actual).GetEnumerator();
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return _actual.GetEnumerator();
+ }
+
+ public int IndexOf(T item)
+ {
+ return _actual.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("This list is ReadOnly");
+ _actual.Insert(index, item);
+ }
+
+ public T this[int index]
+ {
+ get { return _actual[index]; }
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("This list is ReadOnly");
+ _actual[index] = value;
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("This list is ReadOnly");
+ _actual.RemoveAt(index);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Internals/DynamicResource.cs b/Xamarin.Forms.Core/Internals/DynamicResource.cs
new file mode 100644
index 00000000..3724daec
--- /dev/null
+++ b/Xamarin.Forms.Core/Internals/DynamicResource.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms.Internals
+{
+ public class DynamicResource
+ {
+ public DynamicResource(string key)
+ {
+ Key = key;
+ }
+
+ public string Key { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Internals/IDataTemplate.cs b/Xamarin.Forms.Core/Internals/IDataTemplate.cs
new file mode 100644
index 00000000..d6947ae8
--- /dev/null
+++ b/Xamarin.Forms.Core/Internals/IDataTemplate.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Xamarin.Forms.Internals
+{
+ [Obsolete]
+ public interface IDataTemplate
+ {
+ Func<object> LoadTemplate { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs b/Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs
new file mode 100644
index 00000000..1885d8ab
--- /dev/null
+++ b/Xamarin.Forms.Core/Internals/IDynamicResourceHandler.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms.Internals
+{
+ public interface IDynamicResourceHandler
+ {
+ void SetDynamicResource(BindableProperty property, string key);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Internals/INameScope.cs b/Xamarin.Forms.Core/Internals/INameScope.cs
new file mode 100644
index 00000000..ca520605
--- /dev/null
+++ b/Xamarin.Forms.Core/Internals/INameScope.cs
@@ -0,0 +1,12 @@
+using System.Xml;
+
+namespace Xamarin.Forms.Internals
+{
+ public interface INameScope
+ {
+ object FindByName(string name);
+ void RegisterName(string name, object scopedElement);
+ void RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo);
+ void UnregisterName(string name);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Internals/NameScope.cs b/Xamarin.Forms.Core/Internals/NameScope.cs
new file mode 100644
index 00000000..b29534b6
--- /dev/null
+++ b/Xamarin.Forms.Core/Internals/NameScope.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Internals
+{
+ public class NameScope : INameScope
+ {
+ public static readonly BindableProperty NameScopeProperty = BindableProperty.CreateAttached("NameScope", typeof(INameScope), typeof(NameScope), default(INameScope));
+
+ readonly Dictionary<string, object> _names = new Dictionary<string, object>();
+
+ object INameScope.FindByName(string name)
+ {
+ if (_names.ContainsKey(name))
+ return _names[name];
+ return null;
+ }
+
+ void INameScope.RegisterName(string name, object scopedElement)
+ {
+ if (_names.ContainsKey(name))
+ throw new ArgumentException("An element with the same key already exists in NameScope", "name");
+
+ _names[name] = scopedElement;
+ }
+
+ void INameScope.RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo)
+ {
+ try
+ {
+ ((INameScope)this).RegisterName(name, scopedElement);
+ }
+ catch (ArgumentException)
+ {
+ throw new XamlParseException(string.Format("An element with the name \"{0}\" already exists in this NameScope", name), xmlLineInfo);
+ }
+ }
+
+ void INameScope.UnregisterName(string name)
+ {
+ _names.Remove(name);
+ }
+
+ public static INameScope GetNameScope(BindableObject bindable)
+ {
+ return (INameScope)bindable.GetValue(NameScopeProperty);
+ }
+
+ public static void SetNameScope(BindableObject bindable, INameScope value)
+ {
+ bindable.SetValue(NameScopeProperty, value);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/InvalidNavigationException.cs b/Xamarin.Forms.Core/InvalidNavigationException.cs
new file mode 100644
index 00000000..fce5b314
--- /dev/null
+++ b/Xamarin.Forms.Core/InvalidNavigationException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class InvalidNavigationException : Exception
+ {
+ public InvalidNavigationException(string message) : base(message)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/InvalidationEventArgs.cs b/Xamarin.Forms.Core/InvalidationEventArgs.cs
new file mode 100644
index 00000000..d88e2519
--- /dev/null
+++ b/Xamarin.Forms.Core/InvalidationEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class InvalidationEventArgs : EventArgs
+ {
+ public InvalidationEventArgs(InvalidationTrigger trigger)
+ {
+ Trigger = trigger;
+ }
+
+ public InvalidationTrigger Trigger { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/InvalidationTrigger.cs b/Xamarin.Forms.Core/InvalidationTrigger.cs
new file mode 100644
index 00000000..e7db534e
--- /dev/null
+++ b/Xamarin.Forms.Core/InvalidationTrigger.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ internal enum InvalidationTrigger
+ {
+ Undefined = 0,
+ MeasureChanged = 1 << 0,
+ HorizontalOptionsChanged = 1 << 1,
+ VerticalOptionsChanged = 1 << 2,
+ SizeRequestChanged = 1 << 3,
+ RendererReady = 1 << 4,
+ MarginChanged = 1 << 5
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ItemTappedEventArgs.cs b/Xamarin.Forms.Core/ItemTappedEventArgs.cs
new file mode 100644
index 00000000..9fde19bd
--- /dev/null
+++ b/Xamarin.Forms.Core/ItemTappedEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ItemTappedEventArgs : EventArgs
+ {
+ public ItemTappedEventArgs(object group, object item)
+ {
+ Group = group;
+ Item = item;
+ }
+
+ public object Group { get; private set; }
+
+ public object Item { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ItemVisibilityEventArgs.cs b/Xamarin.Forms.Core/ItemVisibilityEventArgs.cs
new file mode 100644
index 00000000..48166c13
--- /dev/null
+++ b/Xamarin.Forms.Core/ItemVisibilityEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class ItemVisibilityEventArgs : EventArgs
+ {
+ public ItemVisibilityEventArgs(object item)
+ {
+ Item = item;
+ }
+
+ public object Item { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ItemsView.cs b/Xamarin.Forms.Core/ItemsView.cs
new file mode 100644
index 00000000..51c105bd
--- /dev/null
+++ b/Xamarin.Forms.Core/ItemsView.cs
@@ -0,0 +1,92 @@
+using System.Collections;
+
+namespace Xamarin.Forms
+{
+ public abstract class ItemsView<TVisual> : View, IItemsView<TVisual> where TVisual : BindableObject
+ {
+ /*
+ public static readonly BindableProperty InfiniteScrollingProperty =
+ BindableProperty.Create<ItemsView, bool> (lv => lv.InfiniteScrolling, false);
+
+ public bool InfiniteScrolling
+ {
+ get { return (bool) GetValue (InfiniteScrollingProperty); }
+ set { SetValue (InfiniteScrollingProperty, value); }
+ }*/
+
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(ItemsView<TVisual>), null, propertyChanged: OnItemsSourceChanged);
+
+ public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ItemsView<TVisual>), null, validateValue: ValidateItemTemplate);
+
+ internal ItemsView()
+ {
+ TemplatedItems = new TemplatedItemsList<ItemsView<TVisual>, TVisual>(this, ItemsSourceProperty, ItemTemplateProperty);
+ }
+
+ public IEnumerable ItemsSource
+ {
+ get { return (IEnumerable)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
+
+ public DataTemplate ItemTemplate
+ {
+ get { return (DataTemplate)GetValue(ItemTemplateProperty); }
+ set { SetValue(ItemTemplateProperty, value); }
+ }
+
+ /*public void UpdateNonNotifyingList()
+ {
+ this.templatedItems.ForceUpdate();
+ }*/
+
+ internal ListProxy ListProxy
+ {
+ get { return TemplatedItems.ListProxy; }
+ }
+
+ internal TemplatedItemsList<ItemsView<TVisual>, TVisual> TemplatedItems { get; }
+
+ TVisual IItemsView<TVisual>.CreateDefault(object item)
+ {
+ return CreateDefault(item);
+ }
+
+ void IItemsView<TVisual>.SetupContent(TVisual content, int index)
+ {
+ SetupContent(content, index);
+ }
+
+ void IItemsView<TVisual>.UnhookContent(TVisual content)
+ {
+ UnhookContent(content);
+ }
+
+ protected abstract TVisual CreateDefault(object item);
+
+ protected virtual void SetupContent(TVisual content, int index)
+ {
+ }
+
+ protected virtual void UnhookContent(TVisual content)
+ {
+ }
+
+ static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var element = newValue as Element;
+ if (element == null)
+ return;
+ element.Parent = (Element)bindable;
+ }
+
+ static bool ValidateItemTemplate(BindableObject b, object v)
+ {
+ var lv = b as ListView;
+ if (lv == null)
+ return true;
+
+ return !(lv.CachingStrategy == ListViewCachingStrategy.RetainElement && lv.ItemTemplate is DataTemplateSelector);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ItemsViewSimple.cs b/Xamarin.Forms.Core/ItemsViewSimple.cs
new file mode 100644
index 00000000..fd631838
--- /dev/null
+++ b/Xamarin.Forms.Core/ItemsViewSimple.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ public abstract class ItemsView : View, IItemViewController
+ {
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(ItemsView), Enumerable.Empty<object>());
+
+ public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ItemsView));
+
+ ItemSource _itemSource;
+
+ internal ItemsView()
+ {
+ }
+
+ public int Count => _itemSource.Count;
+
+ public IEnumerable ItemsSource
+ {
+ get { return (IEnumerable)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
+
+ public DataTemplate ItemTemplate
+ {
+ get { return (DataTemplate)GetValue(ItemTemplateProperty); }
+ set { SetValue(ItemTemplateProperty, value); }
+ }
+
+ void IItemViewController.BindView(View view, object item)
+ {
+ view.BindingContext = item;
+ }
+
+ View IItemViewController.CreateView(object type)
+ {
+ var dataTemplate = (DataTemplate)type;
+ object content = dataTemplate.CreateContent();
+ var view = (View)content;
+ view.Parent = this;
+ return view;
+ }
+
+ object IItemViewController.GetItem(int index) => _itemSource[index];
+
+ object IItemViewController.GetItemType(object item)
+ {
+ DataTemplate dataTemplate = ItemTemplate;
+ var dataTemplateSelector = dataTemplate as DataTemplateSelector;
+ if (dataTemplateSelector != null)
+ dataTemplate = dataTemplateSelector.SelectTemplate(item, this);
+
+ if (item == null)
+ throw new ArgumentException($"No DataTemplate resolved for item: {item}.");
+
+ return dataTemplate;
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ if (propertyName == nameof(ItemsSource))
+ {
+ // abstract enumerable, IList, IList<T>, and IReadOnlyList<T>
+ _itemSource = new ItemSource(ItemsSource);
+
+ // subscribe to collection changed events
+ var dynamicItemSource = _itemSource as INotifyCollectionChanged;
+ if (dynamicItemSource != null)
+ {
+ new WeakNotifyCollectionChanged(dynamicItemSource, OnCollectionChange);
+ }
+ }
+
+ base.OnPropertyChanged(propertyName);
+ }
+
+ internal event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ void OnCollectionChange(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ CollectionChanged?.Invoke(sender, e);
+ }
+
+ sealed class WeakNotifyCollectionChanged
+ {
+ readonly INotifyCollectionChanged _source;
+ // prevent the itemSource from keeping the itemsView alive
+ readonly WeakReference<NotifyCollectionChangedEventHandler> _weakTarget;
+
+ public WeakNotifyCollectionChanged(INotifyCollectionChanged source, NotifyCollectionChangedEventHandler target)
+ {
+ _weakTarget = new WeakReference<NotifyCollectionChangedEventHandler>(target);
+ _source = source;
+ _source.CollectionChanged += OnCollectionChanged;
+ }
+
+ public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler weakTarget;
+ if (!_weakTarget.TryGetTarget(out weakTarget))
+ {
+ _source.CollectionChanged -= OnCollectionChanged;
+ return;
+ }
+
+ weakTarget(sender, e);
+ }
+ }
+
+ sealed class ItemSource : IEnumerable<object>, INotifyCollectionChanged
+ {
+ IndexableCollection _indexable;
+
+ internal ItemSource(IEnumerable enumerable)
+ {
+ _indexable = new IndexableCollection(enumerable);
+ var dynamicItemSource = enumerable as INotifyCollectionChanged;
+ if (dynamicItemSource != null)
+ dynamicItemSource.CollectionChanged += OnCollectionChanged;
+ }
+
+ public int Count => _indexable.Count;
+
+ public IEnumerable Enumerable => _indexable.Enumerable;
+
+ public object this[int index]
+ {
+ get
+ {
+ // madness ported from listProxy
+ CollectionSynchronizationContext syncContext = SyncContext;
+ if (syncContext != null)
+ {
+ object value = null;
+ syncContext.Callback(Enumerable, SyncContext.Context, () => value = _indexable[index], false);
+
+ return value;
+ }
+
+ return _indexable[index];
+ }
+ }
+
+ CollectionSynchronizationContext SyncContext
+ {
+ get
+ {
+ CollectionSynchronizationContext syncContext;
+ BindingBase.TryGetSynchronizedCollection(Enumerable, out syncContext);
+ return syncContext;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator<object> IEnumerable<object>.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ public int IndexOf(object item)
+ {
+ // madness ported from listProxy
+ CollectionSynchronizationContext syncContext = SyncContext;
+ if (syncContext != null)
+ {
+ int value = -1;
+ syncContext.Callback(Enumerable, SyncContext.Context, () => value = _indexable.IndexOf(item), false);
+
+ return value;
+ }
+
+ return _indexable.IndexOf(item);
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ Action onCollectionChanged = () =>
+ {
+ if (CollectionChanged != null)
+ CollectionChanged(this, e);
+ };
+
+ // madness ported from listProxy
+ CollectionSynchronizationContext syncContext = SyncContext;
+ if (syncContext != null)
+ {
+ syncContext.Callback(Enumerable, syncContext.Context, () => Device.BeginInvokeOnMainThread(onCollectionChanged), false);
+ }
+
+ else if (Device.IsInvokeRequired)
+ Device.BeginInvokeOnMainThread(onCollectionChanged);
+
+ else
+ onCollectionChanged();
+ }
+
+ internal struct Enumerator : IEnumerator<object>
+ {
+ readonly ItemSource _itemSource;
+ int _index;
+
+ internal Enumerator(ItemSource itemSource) : this()
+ {
+ _itemSource = itemSource;
+ }
+
+ public bool MoveNext()
+ {
+ if (_index == _itemSource.Count)
+ return false;
+
+ Current = _itemSource[_index++];
+ return true;
+ }
+
+ public object Current { get; private set; }
+
+ public void Reset()
+ {
+ Current = null;
+ _index = 0;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ struct IndexableCollection : IEnumerable<object>
+ {
+ internal IndexableCollection(IEnumerable list)
+ {
+ Enumerable = list;
+
+ if (list is IList)
+ return;
+
+ if (list is IList<object>)
+ return;
+
+ if (list is IReadOnlyList<object>)
+ return;
+
+ Enumerable = list.Cast<object>().ToArray();
+ }
+
+ internal IEnumerable Enumerable { get; }
+
+ internal int Count
+ {
+ get
+ {
+ var list = Enumerable as IList;
+ if (list != null)
+ return list.Count;
+
+ var listOf = Enumerable as IList<object>;
+ if (listOf != null)
+ return listOf.Count;
+
+ var readOnlyList = (IReadOnlyList<object>)Enumerable;
+ return readOnlyList.Count;
+ }
+ }
+
+ internal object this[int index]
+ {
+ get
+ {
+ var list = Enumerable as IList;
+ if (list != null)
+ return list[index];
+
+ var listOf = Enumerable as IList<object>;
+ if (listOf != null)
+ return listOf[index];
+
+ var readOnlyList = (IReadOnlyList<object>)Enumerable;
+ return readOnlyList[index];
+ }
+ }
+
+ internal int IndexOf(object item)
+ {
+ var list = Enumerable as IList;
+ if (list != null)
+ return list.IndexOf(item);
+
+ var listOf = Enumerable as IList<object>;
+ if (listOf != null)
+ return listOf.IndexOf(item);
+
+ var readOnlyList = (IReadOnlyList<object>)Enumerable;
+ return readOnlyList.IndexOf(item);
+ }
+
+ public IEnumerator<object> GetEnumerator()
+ {
+ var list = Enumerable as IList;
+ if (list != null)
+ return list.Cast<object>().GetEnumerator();
+
+ var listOf = Enumerable as IList<object>;
+ if (listOf != null)
+ return listOf.GetEnumerator();
+
+ var readOnlyList = (IReadOnlyList<object>)Enumerable;
+ return readOnlyList.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Keyboard.cs b/Xamarin.Forms.Core/Keyboard.cs
new file mode 100644
index 00000000..56d5ca8c
--- /dev/null
+++ b/Xamarin.Forms.Core/Keyboard.cs
@@ -0,0 +1,64 @@
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(KeyboardTypeConverter))]
+ public class Keyboard
+ {
+ static Keyboard s_def;
+
+ static Keyboard s_email;
+
+ static Keyboard s_text;
+
+ static Keyboard s_url;
+
+ static Keyboard s_numeric;
+
+ static Keyboard s_telephone;
+
+ static Keyboard s_chat;
+
+ internal Keyboard()
+ {
+ }
+
+ public static Keyboard Chat
+ {
+ get { return s_chat ?? (s_chat = new ChatKeyboard()); }
+ }
+
+ public static Keyboard Default
+ {
+ get { return s_def ?? (s_def = new Keyboard()); }
+ }
+
+ public static Keyboard Email
+ {
+ get { return s_email ?? (s_email = new EmailKeyboard()); }
+ }
+
+ public static Keyboard Numeric
+ {
+ get { return s_numeric ?? (s_numeric = new NumericKeyboard()); }
+ }
+
+ public static Keyboard Telephone
+ {
+ get { return s_telephone ?? (s_telephone = new TelephoneKeyboard()); }
+ }
+
+ public static Keyboard Text
+ {
+ get { return s_text ?? (s_text = new TextKeyboard()); }
+ }
+
+ public static Keyboard Url
+ {
+ get { return s_url ?? (s_url = new UrlKeyboard()); }
+ }
+
+ public static Keyboard Create(KeyboardFlags flags)
+ {
+ return new CustomKeyboard(flags);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/KeyboardFlags.cs b/Xamarin.Forms.Core/KeyboardFlags.cs
new file mode 100644
index 00000000..5ac2f1cc
--- /dev/null
+++ b/Xamarin.Forms.Core/KeyboardFlags.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ public enum KeyboardFlags
+ {
+ CapitalizeSentence = 1,
+ Spellcheck = 1 << 1,
+ Suggestions = 1 << 2,
+ All = ~0
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/KeyboardTypeConverter.cs b/Xamarin.Forms.Core/KeyboardTypeConverter.cs
new file mode 100644
index 00000000..4a93010b
--- /dev/null
+++ b/Xamarin.Forms.Core/KeyboardTypeConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ public class KeyboardTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ string[] parts = value.Split('.');
+ if (parts.Length == 1 || (parts.Length == 2 && parts[0] == "Keyboard"))
+ {
+ string keyboard = parts[parts.Length - 1];
+ FieldInfo field = typeof(Keyboard).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == keyboard);
+ if (field != null)
+ return (Keyboard)field.GetValue(null);
+ PropertyInfo property = typeof(Keyboard).GetProperties().FirstOrDefault(pi => pi.Name == keyboard && pi.CanRead && pi.GetMethod.IsStatic);
+ if (property != null)
+ return (Keyboard)property.GetValue(null, null);
+ }
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Keyboard)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Label.cs b/Xamarin.Forms.Core/Label.cs
new file mode 100644
index 00000000..fed021f8
--- /dev/null
+++ b/Xamarin.Forms.Core/Label.cs
@@ -0,0 +1,270 @@
+using System;
+using System.ComponentModel;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Text")]
+ [RenderWith(typeof(_LabelRenderer))]
+ public class Label : View, IFontElement
+ {
+ public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(Label), TextAlignment.Start,
+ propertyChanged: OnHorizontalTextAlignmentPropertyChanged);
+
+ [Obsolete("XAlignProperty is obsolete. Please use HorizontalTextAlignmentProperty instead.")] public static readonly BindableProperty XAlignProperty = HorizontalTextAlignmentProperty;
+
+ public static readonly BindableProperty VerticalTextAlignmentProperty = BindableProperty.Create("VerticalTextAlignment", typeof(TextAlignment), typeof(Label), TextAlignment.Start,
+ propertyChanged: OnVerticalTextAlignmentPropertyChanged);
+
+ [Obsolete("YAlignProperty is obsolete. Please use VerticalTextAlignmentProperty instead.")] public static readonly BindableProperty YAlignProperty = VerticalTextAlignmentProperty;
+
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(Label), Color.Default);
+
+ public static readonly BindableProperty FontProperty = BindableProperty.Create("Font", typeof(Font), typeof(Label), default(Font), propertyChanged: FontStructPropertyChanged);
+
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Label), default(string), propertyChanged: OnTextPropertyChanged);
+
+ public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(Label), default(string), propertyChanged: OnFontFamilyChanged);
+
+ public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(Label), -1.0, propertyChanged: OnFontSizeChanged,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (Label)bindable));
+
+ public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(Label), FontAttributes.None,
+ propertyChanged: OnFontAttributesChanged);
+
+ public static readonly BindableProperty FormattedTextProperty = BindableProperty.Create("FormattedText", typeof(FormattedString), typeof(Label), default(FormattedString),
+ propertyChanging: (bindable, oldvalue, newvalue) =>
+ {
+ if (oldvalue != null)
+ ((FormattedString)oldvalue).PropertyChanged -= ((Label)bindable).OnFormattedTextChanged;
+ }, propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ if (newvalue != null)
+ ((FormattedString)newvalue).PropertyChanged += ((Label)bindable).OnFormattedTextChanged;
+ ((Label)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ if (newvalue != null)
+ ((Label)bindable).Text = null;
+ });
+
+ public static readonly BindableProperty LineBreakModeProperty = BindableProperty.Create("LineBreakMode", typeof(LineBreakMode), typeof(Label), LineBreakMode.WordWrap,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((Label)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged));
+
+ bool _cancelEvents;
+
+ [Obsolete("Please use the Font attributes which are on the class itself. Obsoleted in v1.3.0")]
+ public Font Font
+ {
+ get { return (Font)GetValue(FontProperty); }
+ set { SetValue(FontProperty, value); }
+ }
+
+ public FormattedString FormattedText
+ {
+ get { return (FormattedString)GetValue(FormattedTextProperty); }
+ set { SetValue(FormattedTextProperty, value); }
+ }
+
+ public TextAlignment HorizontalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
+ set { SetValue(HorizontalTextAlignmentProperty, value); }
+ }
+
+ public LineBreakMode LineBreakMode
+ {
+ get { return (LineBreakMode)GetValue(LineBreakModeProperty); }
+ set { SetValue(LineBreakModeProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ public TextAlignment VerticalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(VerticalTextAlignmentProperty); }
+ set { SetValue(VerticalTextAlignmentProperty, value); }
+ }
+
+ [Obsolete("XAlign is obsolete. Please use HorizontalTextAlignment instead.")]
+ public TextAlignment XAlign
+ {
+ get { return (TextAlignment)GetValue(XAlignProperty); }
+ set { SetValue(XAlignProperty, value); }
+ }
+
+ [Obsolete("YAlign is obsolete. Please use VerticalTextAlignment instead.")]
+ public TextAlignment YAlign
+ {
+ get { return (TextAlignment)GetValue(YAlignProperty); }
+ set { SetValue(YAlignProperty, value); }
+ }
+
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ static void FontStructPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (Label)bindable;
+ if (label._cancelEvents)
+ return;
+
+ label._cancelEvents = true;
+
+ var font = (Font)newValue;
+ if (font == Font.Default)
+ {
+ label.FontFamily = null;
+ label.FontSize = Device.GetNamedSize(NamedSize.Default, label);
+ label.FontAttributes = FontAttributes.None;
+ }
+ else
+ {
+ label.FontFamily = font.FontFamily;
+ if (font.UseNamedSize)
+ {
+ label.FontSize = Device.GetNamedSize(font.NamedSize, label.GetType(), true);
+ }
+ else
+ {
+ label.FontSize = font.FontSize;
+ }
+ label.FontAttributes = font.FontAttributes;
+ }
+
+ label._cancelEvents = false;
+
+ label.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ static void OnFontAttributesChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (Label)bindable;
+
+ if (label._cancelEvents)
+ return;
+
+ label._cancelEvents = true;
+
+ var attributes = (FontAttributes)newValue;
+
+ object[] values = label.GetValues(FontFamilyProperty, FontSizeProperty);
+ var family = (string)values[0];
+ if (family != null)
+ {
+ label.Font = Font.OfSize(family, (double)values[1]).WithAttributes(attributes);
+ }
+ else
+ {
+ label.Font = Font.SystemFontOfSize((double)values[1], attributes);
+ }
+
+ label._cancelEvents = false;
+
+ label.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ static void OnFontFamilyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (Label)bindable;
+ if (label._cancelEvents)
+ return;
+
+ label._cancelEvents = true;
+
+ object[] values = label.GetValues(FontSizeProperty, FontAttributesProperty);
+
+ var family = (string)newValue;
+ if (family != null)
+ {
+ label.Font = Font.OfSize(family, (double)values[0]).WithAttributes((FontAttributes)values[1]);
+ }
+ else
+ {
+ label.Font = Font.SystemFontOfSize((double)values[0], (FontAttributes)values[1]);
+ }
+
+ label._cancelEvents = false;
+ label.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ static void OnFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (Label)bindable;
+ if (label._cancelEvents)
+ return;
+
+ label._cancelEvents = true;
+
+ object[] values = label.GetValues(FontFamilyProperty, FontAttributesProperty);
+
+ var size = (double)newValue;
+ var family = (string)values[0];
+ if (family != null)
+ {
+ label.Font = Font.OfSize(family, size).WithAttributes((FontAttributes)values[1]);
+ }
+ else
+ {
+ label.Font = Font.SystemFontOfSize(size, (FontAttributes)values[1]);
+ }
+
+ label._cancelEvents = false;
+
+ label.InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ void OnFormattedTextChanged(object sender, PropertyChangedEventArgs e)
+ {
+ OnPropertyChanged("FormattedText");
+ }
+
+ static void OnHorizontalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (Label)bindable;
+ label.OnPropertyChanged(nameof(XAlign));
+ }
+
+ static void OnTextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ var label = (Label)bindable;
+ LineBreakMode breakMode = label.LineBreakMode;
+ bool isVerticallyFixed = (label.Constraint & LayoutConstraint.VerticallyFixed) != 0;
+ bool isSingleLine = !(breakMode == LineBreakMode.CharacterWrap || breakMode == LineBreakMode.WordWrap);
+ if (!isVerticallyFixed || !isSingleLine)
+ ((Label)bindable).InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ if (newvalue != null)
+ ((Label)bindable).FormattedText = null;
+ }
+
+ static void OnVerticalTextAlignmentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var label = (Label)bindable;
+ label.OnPropertyChanged(nameof(YAlign));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Layout.cs b/Xamarin.Forms.Core/Layout.cs
new file mode 100644
index 00000000..c611777c
--- /dev/null
+++ b/Xamarin.Forms.Core/Layout.cs
@@ -0,0 +1,433 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Children")]
+ public abstract class Layout<T> : Layout, IViewContainer<T> where T : View
+ {
+ readonly ElementCollection<T> _children;
+
+ protected Layout()
+ {
+ _children = new ElementCollection<T>(InternalChildren);
+ }
+
+ public IList<T> Children
+ {
+ get { return _children; }
+ }
+
+ protected virtual void OnAdded(T view)
+ {
+ }
+
+ protected override void OnChildAdded(Element child)
+ {
+ base.OnChildAdded(child);
+
+ var typedChild = child as T;
+ if (typedChild != null)
+ OnAdded(typedChild);
+ }
+
+ protected override void OnChildRemoved(Element child)
+ {
+ base.OnChildRemoved(child);
+
+ var typedChild = child as T;
+ if (typedChild != null)
+ OnRemoved(typedChild);
+ }
+
+ protected virtual void OnRemoved(T view)
+ {
+ }
+ }
+
+ public abstract class Layout : View, ILayout, ILayoutController
+ {
+ public static readonly BindableProperty IsClippedToBoundsProperty = BindableProperty.Create("IsClippedToBounds", typeof(bool), typeof(Layout), false);
+
+ public static readonly BindableProperty PaddingProperty = BindableProperty.Create("Padding", typeof(Thickness), typeof(Layout), default(Thickness), propertyChanged: (bindable, old, newValue) =>
+ {
+ var layout = (Layout)bindable;
+ layout.UpdateChildrenLayout();
+ });
+
+ static IList<KeyValuePair<Layout, int>> s_resolutionList = new List<KeyValuePair<Layout, int>>();
+ static bool s_relayoutInProgress;
+ bool _allocatedFlag;
+
+ bool _hasDoneLayout;
+ Size _lastLayoutSize = new Size(-1, -1);
+
+ ReadOnlyCollection<Element> _logicalChildren;
+
+ protected Layout()
+ {
+ InternalChildren.CollectionChanged += InternalChildrenOnCollectionChanged;
+ }
+
+ public bool IsClippedToBounds
+ {
+ get { return (bool)GetValue(IsClippedToBoundsProperty); }
+ set { SetValue(IsClippedToBoundsProperty, value); }
+ }
+
+ public Thickness Padding
+ {
+ get { return (Thickness)GetValue(PaddingProperty); }
+ set { SetValue(PaddingProperty, value); }
+ }
+
+ internal ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>();
+
+ internal override ReadOnlyCollection<Element> LogicalChildren
+ {
+ get { return _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(InternalChildren)); }
+ }
+
+ public event EventHandler LayoutChanged;
+
+ IReadOnlyList<Element> ILayoutController.Children
+ {
+ get { return InternalChildren; }
+ }
+
+ public void ForceLayout()
+ {
+ SizeAllocated(Width, Height);
+ }
+
+ [Obsolete("Use Measure")]
+ public sealed override SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ SizeRequest size = base.GetSizeRequest(widthConstraint - Padding.HorizontalThickness, heightConstraint - Padding.VerticalThickness);
+ return new SizeRequest(new Size(size.Request.Width + Padding.HorizontalThickness, size.Request.Height + Padding.VerticalThickness),
+ new Size(size.Minimum.Width + Padding.HorizontalThickness, size.Minimum.Height + Padding.VerticalThickness));
+ }
+
+ public static void LayoutChildIntoBoundingRegion(VisualElement child, Rectangle region)
+ {
+ var view = child as View;
+ if (view == null)
+ {
+ child.Layout(region);
+ return;
+ }
+
+ LayoutOptions horizontalOptions = view.HorizontalOptions;
+ if (horizontalOptions.Alignment != LayoutAlignment.Fill)
+ {
+ SizeRequest request = child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
+ double diff = Math.Max(0, region.Width - request.Request.Width);
+ region.X += (int)(diff * horizontalOptions.Alignment.ToDouble());
+ region.Width -= diff;
+ }
+
+ LayoutOptions verticalOptions = view.VerticalOptions;
+ if (verticalOptions.Alignment != LayoutAlignment.Fill)
+ {
+ SizeRequest request = child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
+ double diff = Math.Max(0, region.Height - request.Request.Height);
+ region.Y += (int)(diff * verticalOptions.Alignment.ToDouble());
+ region.Height -= diff;
+ }
+
+ Thickness margin = view.Margin;
+ region.X += margin.Left;
+ region.Width -= margin.HorizontalThickness;
+ region.Y += margin.Top;
+ region.Height -= margin.VerticalThickness;
+
+ child.Layout(region);
+ }
+
+ public void LowerChild(View view)
+ {
+ if (!InternalChildren.Contains(view) || InternalChildren.First() == view)
+ return;
+
+ InternalChildren.Move(InternalChildren.IndexOf(view), 0);
+ OnChildrenReordered();
+ }
+
+ public void RaiseChild(View view)
+ {
+ if (!InternalChildren.Contains(view) || InternalChildren.Last() == view)
+ return;
+
+ InternalChildren.Move(InternalChildren.IndexOf(view), InternalChildren.Count - 1);
+ OnChildrenReordered();
+ }
+
+ protected virtual void InvalidateLayout()
+ {
+ _hasDoneLayout = false;
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ if (!_hasDoneLayout)
+ ForceLayout();
+ }
+
+ protected abstract void LayoutChildren(double x, double y, double width, double height);
+
+ protected void OnChildMeasureInvalidated(object sender, EventArgs e)
+ {
+ InvalidationTrigger trigger = (e as InvalidationEventArgs)?.Trigger ?? InvalidationTrigger.Undefined;
+ OnChildMeasureInvalidated((VisualElement)sender, trigger);
+ OnChildMeasureInvalidated();
+ }
+
+ protected virtual void OnChildMeasureInvalidated()
+ {
+ }
+
+ protected override void OnSizeAllocated(double width, double height)
+ {
+ _allocatedFlag = true;
+ base.OnSizeAllocated(width, height);
+ UpdateChildrenLayout();
+ }
+
+ protected virtual bool ShouldInvalidateOnChildAdded(View child)
+ {
+ return true;
+ }
+
+ protected virtual bool ShouldInvalidateOnChildRemoved(View child)
+ {
+ return true;
+ }
+
+ protected void UpdateChildrenLayout()
+ {
+ _hasDoneLayout = true;
+
+ if (!ShouldLayoutChildren())
+ return;
+
+ var oldBounds = new Rectangle[LogicalChildren.Count];
+ for (var index = 0; index < oldBounds.Length; index++)
+ {
+ var c = (VisualElement)LogicalChildren[index];
+ oldBounds[index] = c.Bounds;
+ }
+
+ double width = Width;
+ double height = Height;
+
+ double x = Padding.Left;
+ double y = Padding.Top;
+ double w = Math.Max(0, width - Padding.HorizontalThickness);
+ double h = Math.Max(0, height - Padding.VerticalThickness);
+
+ LayoutChildren(x, y, w, h);
+
+ for (var i = 0; i < oldBounds.Length; i++)
+ {
+ Rectangle oldBound = oldBounds[i];
+ Rectangle newBound = ((VisualElement)LogicalChildren[i]).Bounds;
+ if (oldBound != newBound)
+ {
+ EventHandler handler = LayoutChanged;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ return;
+ }
+ }
+
+ _lastLayoutSize = new Size(width, height);
+ }
+
+ internal static void LayoutChildIntoBoundingRegion(View child, Rectangle region, SizeRequest childSizeRequest)
+ {
+ if (region.Size != childSizeRequest.Request)
+ {
+ bool canUseAlreadyDoneRequest = region.Width >= childSizeRequest.Request.Width && region.Height >= childSizeRequest.Request.Height;
+
+ if (child.HorizontalOptions.Alignment != LayoutAlignment.Fill)
+ {
+ SizeRequest request = canUseAlreadyDoneRequest ? childSizeRequest : child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
+ double diff = Math.Max(0, region.Width - request.Request.Width);
+ region.X += (int)(diff * child.HorizontalOptions.Alignment.ToDouble());
+ region.Width -= diff;
+ }
+
+ if (child.VerticalOptions.Alignment != LayoutAlignment.Fill)
+ {
+ SizeRequest request = canUseAlreadyDoneRequest ? childSizeRequest : child.Measure(region.Width, region.Height, MeasureFlags.IncludeMargins);
+ double diff = Math.Max(0, region.Height - request.Request.Height);
+ region.Y += (int)(diff * child.VerticalOptions.Alignment.ToDouble());
+ region.Height -= diff;
+ }
+ }
+
+ Thickness margin = child.Margin;
+ region.X += margin.Left;
+ region.Width -= margin.HorizontalThickness;
+ region.Y += margin.Top;
+ region.Height -= margin.VerticalThickness;
+
+ child.Layout(region);
+ }
+
+ internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger)
+ {
+ ReadOnlyCollection<Element> children = LogicalChildren;
+ int count = children.Count;
+ for (var index = 0; index < count; index++)
+ {
+ var v = LogicalChildren[index] as VisualElement;
+ if (v != null && v.IsVisible && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent))
+ return;
+ }
+
+ var view = child as View;
+ if (view != null)
+ {
+ // we can ignore the request if we are either fully constrained or when the size request changes and we were already fully constrainted
+ if ((trigger == InvalidationTrigger.MeasureChanged && view.Constraint == LayoutConstraint.Fixed) ||
+ (trigger == InvalidationTrigger.SizeRequestChanged && view.ComputedConstraint == LayoutConstraint.Fixed))
+ {
+ return;
+ }
+ if (trigger == InvalidationTrigger.HorizontalOptionsChanged || trigger == InvalidationTrigger.VerticalOptionsChanged)
+ {
+ ComputeConstraintForView(view);
+ }
+ }
+
+ _allocatedFlag = false;
+ if (trigger == InvalidationTrigger.RendererReady)
+ {
+ InvalidateMeasure(InvalidationTrigger.RendererReady);
+ }
+ else
+ {
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ s_resolutionList.Add(new KeyValuePair<Layout, int>(this, GetElementDepth(this)));
+ if (!s_relayoutInProgress)
+ {
+ s_relayoutInProgress = true;
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ // if thread safety mattered we would need to lock this and compareexchange above
+ IList<KeyValuePair<Layout, int>> copy = s_resolutionList;
+ s_resolutionList = new List<KeyValuePair<Layout, int>>();
+ s_relayoutInProgress = false;
+
+ foreach (KeyValuePair<Layout, int> kvp in copy.OrderBy(kvp => kvp.Value))
+ {
+ Layout layout = kvp.Key;
+ double width = layout.Width, height = layout.Height;
+ if (!layout._allocatedFlag && width >= 0 && height >= 0)
+ {
+ layout.SizeAllocated(width, height);
+ }
+ }
+ });
+ }
+ }
+
+ internal override void OnIsVisibleChanged(bool oldValue, bool newValue)
+ {
+ base.OnIsVisibleChanged(oldValue, newValue);
+ if (newValue)
+ {
+ if (_lastLayoutSize != new Size(Width, Height))
+ {
+ UpdateChildrenLayout();
+ }
+ }
+ }
+
+ static int GetElementDepth(Element view)
+ {
+ var result = 0;
+ while (view.Parent != null)
+ {
+ result++;
+ view = view.Parent;
+ }
+ return result;
+ }
+
+ void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.Action == NotifyCollectionChangedAction.Move)
+ {
+ return;
+ }
+
+ if (e.OldItems != null)
+ {
+ foreach (object item in e.OldItems)
+ {
+ var v = item as View;
+ if (v == null)
+ continue;
+
+ OnInternalRemoved(v);
+ }
+ }
+
+ if (e.NewItems != null)
+ {
+ foreach (object item in e.NewItems)
+ {
+ var v = item as View;
+ if (v == null)
+ continue;
+
+ if (item == this)
+ throw new InvalidOperationException("Can not add self to own child collection.");
+
+ OnInternalAdded(v);
+ }
+ }
+ }
+
+ void OnInternalAdded(View view)
+ {
+ OnChildAdded(view);
+ if (ShouldInvalidateOnChildAdded(view))
+ InvalidateLayout();
+
+ view.MeasureInvalidated += OnChildMeasureInvalidated;
+ }
+
+ void OnInternalRemoved(View view)
+ {
+ view.MeasureInvalidated -= OnChildMeasureInvalidated;
+
+ OnChildRemoved(view);
+ if (ShouldInvalidateOnChildRemoved(view))
+ InvalidateLayout();
+ }
+
+ bool ShouldLayoutChildren()
+ {
+ if (!LogicalChildren.Any() || Width <= 0 || Height <= 0 || !IsVisible || !IsNativeStateConsistent || DisableLayout)
+ return false;
+
+ foreach (Element element in VisibleDescendants())
+ {
+ var visual = element as VisualElement;
+ if (visual == null || !visual.IsVisible)
+ continue;
+
+ if (!visual.IsPlatformEnabled || !visual.IsNativeStateConsistent)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LayoutAlignment.cs b/Xamarin.Forms.Core/LayoutAlignment.cs
new file mode 100644
index 00000000..04b03e95
--- /dev/null
+++ b/Xamarin.Forms.Core/LayoutAlignment.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ public enum LayoutAlignment
+ {
+ Start = 0,
+ Center = 1,
+ End = 2,
+ Fill = 3
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LayoutAlignmentExtensions.cs b/Xamarin.Forms.Core/LayoutAlignmentExtensions.cs
new file mode 100644
index 00000000..a7efcf39
--- /dev/null
+++ b/Xamarin.Forms.Core/LayoutAlignmentExtensions.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal static class LayoutAlignmentExtensions
+ {
+ public static double ToDouble(this LayoutAlignment align)
+ {
+ switch (align)
+ {
+ case LayoutAlignment.Start:
+ return 0;
+ case LayoutAlignment.Center:
+ return 0.5;
+ case LayoutAlignment.End:
+ return 1;
+ }
+ throw new ArgumentOutOfRangeException("align");
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LayoutConstraint.cs b/Xamarin.Forms.Core/LayoutConstraint.cs
new file mode 100644
index 00000000..a9ddc8ec
--- /dev/null
+++ b/Xamarin.Forms.Core/LayoutConstraint.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ internal enum LayoutConstraint
+ {
+ None = 0,
+ HorizontallyFixed = 1 << 0,
+ VerticallyFixed = 1 << 1,
+ Fixed = HorizontallyFixed | VerticallyFixed
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LayoutExpandFlag.cs b/Xamarin.Forms.Core/LayoutExpandFlag.cs
new file mode 100644
index 00000000..613d0b4a
--- /dev/null
+++ b/Xamarin.Forms.Core/LayoutExpandFlag.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ internal enum LayoutExpandFlag
+ {
+ Expand = 4
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LayoutOptions.cs b/Xamarin.Forms.Core/LayoutOptions.cs
new file mode 100644
index 00000000..a3a900b0
--- /dev/null
+++ b/Xamarin.Forms.Core/LayoutOptions.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [TypeConverter(typeof(LayoutOptionsConverter))]
+ public struct LayoutOptions
+ {
+ int _flags;
+
+ public static readonly LayoutOptions Start = new LayoutOptions(LayoutAlignment.Start, false);
+ public static readonly LayoutOptions Center = new LayoutOptions(LayoutAlignment.Center, false);
+ public static readonly LayoutOptions End = new LayoutOptions(LayoutAlignment.End, false);
+ public static readonly LayoutOptions Fill = new LayoutOptions(LayoutAlignment.Fill, false);
+ public static readonly LayoutOptions StartAndExpand = new LayoutOptions(LayoutAlignment.Start, true);
+ public static readonly LayoutOptions CenterAndExpand = new LayoutOptions(LayoutAlignment.Center, true);
+ public static readonly LayoutOptions EndAndExpand = new LayoutOptions(LayoutAlignment.End, true);
+ public static readonly LayoutOptions FillAndExpand = new LayoutOptions(LayoutAlignment.Fill, true);
+
+ public LayoutOptions(LayoutAlignment alignment, bool expands)
+ {
+ var a = (int)alignment;
+ if (a < 0 || a > 3)
+ throw new ArgumentOutOfRangeException();
+ _flags = (int)alignment | (expands ? (int)LayoutExpandFlag.Expand : 0);
+ }
+
+ public LayoutAlignment Alignment
+ {
+ get { return (LayoutAlignment)(_flags & 3); }
+ set { _flags = (_flags & ~3) | (int)value; }
+ }
+
+ public bool Expands
+ {
+ get { return (_flags & (int)LayoutExpandFlag.Expand) != 0; }
+ set { _flags = (_flags & 3) | (value ? (int)LayoutExpandFlag.Expand : 0); }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LayoutOptionsConverter.cs b/Xamarin.Forms.Core/LayoutOptionsConverter.cs
new file mode 100644
index 00000000..746e56ce
--- /dev/null
+++ b/Xamarin.Forms.Core/LayoutOptionsConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ public sealed class LayoutOptionsConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ string[] parts = value.Split('.');
+ if (parts.Length > 2 || (parts.Length == 2 && parts[0] != "LayoutOptions"))
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(LayoutOptions)));
+ value = parts[parts.Length - 1];
+ FieldInfo field = typeof(LayoutOptions).GetFields().FirstOrDefault(fi => fi.IsStatic && fi.Name == value);
+ if (field != null)
+ return (LayoutOptions)field.GetValue(null);
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(LayoutOptions)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LineBreakMode.cs b/Xamarin.Forms.Core/LineBreakMode.cs
new file mode 100644
index 00000000..c7a36bb8
--- /dev/null
+++ b/Xamarin.Forms.Core/LineBreakMode.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms
+{
+ public enum LineBreakMode
+ {
+ NoWrap,
+ WordWrap,
+ CharacterWrap,
+ HeadTruncation,
+ TailTruncation,
+ MiddleTruncation
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ListProxy.cs b/Xamarin.Forms.Core/ListProxy.cs
new file mode 100644
index 00000000..229dcb6b
--- /dev/null
+++ b/Xamarin.Forms.Core/ListProxy.cs
@@ -0,0 +1,488 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal sealed class ListProxy : IReadOnlyList<object>, IList, INotifyCollectionChanged
+ {
+ readonly ICollection _collection;
+ readonly IList _list;
+ readonly int _windowSize;
+
+ IEnumerator _enumerator;
+ int _enumeratorIndex;
+
+ bool _finished;
+ HashSet<int> _indexesCounted;
+
+ Dictionary<int, object> _items;
+ int _version;
+
+ int _windowIndex;
+
+ internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue)
+ {
+ _windowSize = windowSize;
+
+ ProxiedEnumerable = enumerable;
+ _collection = enumerable as ICollection;
+
+ if (_collection == null && enumerable is IReadOnlyCollection<object>)
+ _collection = new ReadOnlyListAdapter((IReadOnlyCollection<object>)enumerable);
+
+ _list = enumerable as IList;
+ if (_list == null && enumerable is IReadOnlyList<object>)
+ _list = new ReadOnlyListAdapter((IReadOnlyList<object>)enumerable);
+
+ var changed = enumerable as INotifyCollectionChanged;
+ if (changed != null)
+ new WeakNotifyProxy(this, changed);
+ }
+
+ public IEnumerable ProxiedEnumerable { get; }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<object> GetEnumerator()
+ {
+ return new ProxyEnumerator(this);
+ }
+
+ /// <summary>
+ /// Gets whether or not the current window contains the <paramref name="item" />.
+ /// </summary>
+ /// <param name="item">The item to search for.</param>
+ /// <returns><c>true</c> if the item was found in a list or the current window, <c>false</c> otherwise.</returns>
+ public bool Contains(object item)
+ {
+ if (_list != null)
+ return _list.Contains(item);
+
+ EnsureWindowCreated();
+
+ if (_items != null)
+ return _items.Values.Contains(item);
+
+ return false;
+ }
+
+ /// <summary>
+ /// Gets the index for the <paramref name="item" /> if in a list or the current window.
+ /// </summary>
+ /// <param name="item">The item to search for.</param>
+ /// <returns>The index of the item if in a list or the current window, -1 otherwise.</returns>
+ public int IndexOf(object item)
+ {
+ if (_list != null)
+ return _list.IndexOf(item);
+
+ EnsureWindowCreated();
+
+ if (_items != null)
+ {
+ foreach (KeyValuePair<int, object> kvp in _items)
+ {
+ if (Equals(kvp.Value, item))
+ return kvp.Key;
+ }
+ }
+
+ return -1;
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ public int Count
+ {
+ get
+ {
+ if (_collection != null)
+ return _collection.Count;
+
+ EnsureWindowCreated();
+
+ if (_indexesCounted != null)
+ return _indexesCounted.Count;
+
+ return 0;
+ }
+ }
+
+ public object this[int index]
+ {
+ get
+ {
+ object value;
+ if (!TryGetValue(index, out value))
+ throw new ArgumentOutOfRangeException("index");
+
+ return value;
+ }
+ }
+
+ public void Clear()
+ {
+ _version++;
+ _finished = false;
+ _windowIndex = 0;
+ _enumeratorIndex = 0;
+
+ if (_enumerator != null)
+ {
+ var dispose = _enumerator as IDisposable;
+ if (dispose != null)
+ dispose.Dispose();
+
+ _enumerator = null;
+ }
+
+ if (_items != null)
+ _items.Clear();
+ if (_indexesCounted != null)
+ _indexesCounted.Clear();
+
+ OnCountChanged();
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ public event EventHandler CountChanged;
+
+ void ClearRange(int index, int clearCount)
+ {
+ if (_items == null)
+ return;
+
+ for (int i = index; i < index + clearCount; i++)
+ _items.Remove(i);
+ }
+
+ bool CountIndex(int index)
+ {
+ if (_collection != null)
+ return false;
+
+ // A collection is used in case TryGetValue is called out of order.
+ if (_indexesCounted == null)
+ _indexesCounted = new HashSet<int>();
+
+ if (_indexesCounted.Contains(index))
+ return false;
+
+ _indexesCounted.Add(index);
+ return true;
+ }
+
+ void EnsureWindowCreated()
+ {
+ if (_items != null && _items.Count > 0)
+ return;
+
+ object value;
+ TryGetValue(0, out value);
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ Action action;
+ if (_list == null)
+ {
+ action = Clear;
+ }
+ else
+ {
+ action = () =>
+ {
+ _version++;
+ OnCollectionChanged(e);
+ };
+ }
+
+ CollectionSynchronizationContext sync;
+ if (BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out sync))
+ {
+ sync.Callback(ProxiedEnumerable, sync.Context, () =>
+ {
+ e = e.WithCount(Count);
+ Device.BeginInvokeOnMainThread(action);
+ }, false);
+ }
+ else
+ {
+ e = e.WithCount(Count);
+ if (Device.IsInvokeRequired)
+ Device.BeginInvokeOnMainThread(action);
+ else
+ action();
+ }
+ }
+
+ void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler changed = CollectionChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void OnCountChanged()
+ {
+ EventHandler changed = CountChanged;
+ if (changed != null)
+ changed(this, EventArgs.Empty);
+ }
+
+ bool TryGetValue(int index, out object value)
+ {
+ value = null;
+
+ CollectionSynchronizationContext syncContext;
+ BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out syncContext);
+
+ if (_list != null)
+ {
+ object indexedValue = null;
+ var inRange = false;
+ Action getFromList = () =>
+ {
+ if (index >= _list.Count)
+ return;
+
+ indexedValue = _list[index];
+ inRange = true;
+ };
+
+ if (syncContext != null)
+ syncContext.Callback(ProxiedEnumerable, syncContext.Context, getFromList, false);
+ else
+ getFromList();
+
+ value = indexedValue;
+ return inRange;
+ }
+
+ if (_collection != null && index >= _collection.Count)
+ return false;
+ if (_items != null)
+ {
+ bool found = _items.TryGetValue(index, out value);
+ if (found || _finished)
+ return found;
+ }
+
+ if (index >= _windowIndex + _windowSize)
+ {
+ int newIndex = index - _windowSize / 2;
+ ClearRange(_windowIndex, newIndex - _windowIndex);
+ _windowIndex = newIndex;
+ }
+ else if (index < _windowIndex)
+ {
+ int clearIndex = _windowIndex;
+ int clearSize = _windowSize;
+ if (clearIndex <= index + clearSize)
+ {
+ int diff = index + clearSize - clearIndex;
+ clearIndex += diff + 1;
+ clearSize -= diff;
+ }
+
+ ClearRange(clearIndex, clearSize);
+ _windowIndex = 0;
+
+ var dispose = _enumerator as IDisposable;
+ if (dispose != null)
+ dispose.Dispose();
+
+ _enumerator = null;
+ _enumeratorIndex = 0;
+ }
+
+ if (_enumerator == null)
+ _enumerator = ProxiedEnumerable.GetEnumerator();
+ if (_items == null)
+ _items = new Dictionary<int, object>();
+
+ var countChanged = false;
+ int end = _windowIndex + _windowSize;
+
+ for (; _enumeratorIndex < end; _enumeratorIndex++)
+ {
+ var moved = false;
+ Action move = () =>
+ {
+ try
+ {
+ moved = _enumerator.MoveNext();
+ }
+ catch (InvalidOperationException ioex)
+ {
+ throw new InvalidOperationException("You must call UpdateNonNotifyingList() after updating a list that does not implement INotifyCollectionChanged", ioex);
+ }
+
+ if (!moved)
+ {
+ var dispose = _enumerator as IDisposable;
+ if (dispose != null)
+ dispose.Dispose();
+
+ _enumerator = null;
+ _enumeratorIndex = 0;
+ _finished = true;
+ }
+ };
+
+ if (syncContext == null)
+ move();
+ else
+ syncContext.Callback(ProxiedEnumerable, syncContext.Context, move, false);
+
+ if (!moved)
+ break;
+
+ if (CountIndex(_enumeratorIndex))
+ countChanged = true;
+
+ if (_enumeratorIndex >= _windowIndex)
+ _items.Add(_enumeratorIndex, _enumerator.Current);
+ }
+
+ if (countChanged)
+ OnCountChanged();
+
+ return _items.TryGetValue(index, out value);
+ }
+
+ class WeakNotifyProxy
+ {
+ readonly WeakReference<INotifyCollectionChanged> _weakCollection;
+ readonly WeakReference<ListProxy> _weakProxy;
+
+ public WeakNotifyProxy(ListProxy proxy, INotifyCollectionChanged incc)
+ {
+ incc.CollectionChanged += OnCollectionChanged;
+
+ _weakProxy = new WeakReference<ListProxy>(proxy);
+ _weakCollection = new WeakReference<INotifyCollectionChanged>(incc);
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ ListProxy proxy;
+ if (!_weakProxy.TryGetTarget(out proxy))
+ {
+ INotifyCollectionChanged collection;
+ if (_weakCollection.TryGetTarget(out collection))
+ collection.CollectionChanged -= OnCollectionChanged;
+
+ return;
+ }
+
+ proxy.OnCollectionChanged(sender, e);
+ }
+ }
+
+ class ProxyEnumerator : IEnumerator<object>
+ {
+ readonly ListProxy _proxy;
+ readonly int _version;
+
+ int _index;
+
+ public ProxyEnumerator(ListProxy proxy)
+ {
+ _proxy = proxy;
+ _version = proxy._version;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ if (_proxy._version != _version)
+ throw new InvalidOperationException();
+
+ object value;
+ bool next = _proxy.TryGetValue(_index++, out value);
+ if (next)
+ Current = value;
+
+ return next;
+ }
+
+ public void Reset()
+ {
+ _index = 0;
+ Current = null;
+ }
+
+ public object Current { get; private set; }
+ }
+
+ #region IList
+
+ object IList.this[int index]
+ {
+ get { return this[index]; }
+ set { throw new NotSupportedException(); }
+ }
+
+ bool IList.IsReadOnly
+ {
+ get { return true; }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get { return false; }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get { return false; }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ int IList.Add(object item)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Remove(object item)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Insert(int index, object item)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ListView.cs b/Xamarin.Forms.Core/ListView.cs
new file mode 100644
index 00000000..3d29033a
--- /dev/null
+++ b/Xamarin.Forms.Core/ListView.cs
@@ -0,0 +1,540 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using System.Windows.Input;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_ListViewRenderer))]
+ public class ListView : ItemsView<Cell>, IListViewController
+
+ {
+ public static readonly BindableProperty IsPullToRefreshEnabledProperty = BindableProperty.Create("IsPullToRefreshEnabled", typeof(bool), typeof(ListView), false);
+
+ public static readonly BindableProperty IsRefreshingProperty = BindableProperty.Create("IsRefreshing", typeof(bool), typeof(ListView), false, BindingMode.TwoWay);
+
+ public static readonly BindableProperty RefreshCommandProperty = BindableProperty.Create("RefreshCommand", typeof(ICommand), typeof(ListView), null, propertyChanged: OnRefreshCommandChanged);
+
+ public static readonly BindableProperty HeaderProperty = BindableProperty.Create("Header", typeof(object), typeof(ListView), null, propertyChanged: OnHeaderChanged);
+
+ public static readonly BindableProperty HeaderTemplateProperty = BindableProperty.Create("HeaderTemplate", typeof(DataTemplate), typeof(ListView), null, propertyChanged: OnHeaderTemplateChanged,
+ validateValue: ValidateHeaderFooterTemplate);
+
+ public static readonly BindableProperty FooterProperty = BindableProperty.Create("Footer", typeof(object), typeof(ListView), null, propertyChanged: OnFooterChanged);
+
+ public static readonly BindableProperty FooterTemplateProperty = BindableProperty.Create("FooterTemplate", typeof(DataTemplate), typeof(ListView), null, propertyChanged: OnFooterTemplateChanged,
+ validateValue: ValidateHeaderFooterTemplate);
+
+ public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(object), typeof(ListView), null, BindingMode.OneWayToSource,
+ propertyChanged: OnSelectedItemChanged);
+
+ public static readonly BindableProperty HasUnevenRowsProperty = BindableProperty.Create("HasUnevenRows", typeof(bool), typeof(ListView), false);
+
+ public static readonly BindableProperty RowHeightProperty = BindableProperty.Create("RowHeight", typeof(int), typeof(ListView), -1);
+
+ public static readonly BindableProperty GroupHeaderTemplateProperty = BindableProperty.Create("GroupHeaderTemplate", typeof(DataTemplate), typeof(ListView), null,
+ propertyChanged: OnGroupHeaderTemplateChanged);
+
+ public static readonly BindableProperty IsGroupingEnabledProperty = BindableProperty.Create("IsGroupingEnabled", typeof(bool), typeof(ListView), false);
+
+ public static readonly BindableProperty SeparatorVisibilityProperty = BindableProperty.Create("SeparatorVisibility", typeof(SeparatorVisibility), typeof(ListView), SeparatorVisibility.Default);
+
+ public static readonly BindableProperty SeparatorColorProperty = BindableProperty.Create("SeparatorColor", typeof(Color), typeof(ListView), Color.Default);
+
+ BindingBase _groupDisplayBinding;
+
+ BindingBase _groupShortNameBinding;
+ Element _headerElement;
+ Element _footerElement;
+
+ ScrollToRequestedEventArgs _pendingScroll;
+ int _previousGroupSelected = -1;
+ int _previousRowSelected = -1;
+
+ /// <summary>
+ /// Controls whether anything happens in BeginRefresh(), is set based on RefreshCommand.CanExecute
+ /// </summary>
+ bool _refreshAllowed = true;
+
+ public ListView()
+ {
+ TakePerformanceHit = false;
+
+ VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
+
+ TemplatedItems.IsGroupingEnabledProperty = IsGroupingEnabledProperty;
+ TemplatedItems.GroupHeaderTemplateProperty = GroupHeaderTemplateProperty;
+ }
+
+ public ListView([Parameter("CachingStrategy")] ListViewCachingStrategy cachingStrategy) : this()
+ {
+ if (Device.OS == TargetPlatform.Android || Device.OS == TargetPlatform.iOS)
+ CachingStrategy = cachingStrategy;
+ }
+
+ public object Footer
+ {
+ get { return GetValue(FooterProperty); }
+ set { SetValue(FooterProperty, value); }
+ }
+
+ public DataTemplate FooterTemplate
+ {
+ get { return (DataTemplate)GetValue(FooterTemplateProperty); }
+ set { SetValue(FooterTemplateProperty, value); }
+ }
+
+ public BindingBase GroupDisplayBinding
+ {
+ get { return _groupDisplayBinding; }
+ set
+ {
+ if (_groupDisplayBinding == value)
+ return;
+
+ OnPropertyChanging();
+ BindingBase oldValue = value;
+ _groupDisplayBinding = value;
+ OnGroupDisplayBindingChanged(this, oldValue, _groupDisplayBinding);
+ TemplatedItems.GroupDisplayBinding = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public DataTemplate GroupHeaderTemplate
+ {
+ get { return (DataTemplate)GetValue(GroupHeaderTemplateProperty); }
+ set { SetValue(GroupHeaderTemplateProperty, value); }
+ }
+
+ public BindingBase GroupShortNameBinding
+ {
+ get { return _groupShortNameBinding; }
+ set
+ {
+ if (_groupShortNameBinding == value)
+ return;
+
+ OnPropertyChanging();
+ _groupShortNameBinding = value;
+ TemplatedItems.GroupShortNameBinding = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool HasUnevenRows
+ {
+ get { return (bool)GetValue(HasUnevenRowsProperty); }
+ set { SetValue(HasUnevenRowsProperty, value); }
+ }
+
+ public object Header
+ {
+ get { return GetValue(HeaderProperty); }
+ set { SetValue(HeaderProperty, value); }
+ }
+
+ public DataTemplate HeaderTemplate
+ {
+ get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
+ set { SetValue(HeaderTemplateProperty, value); }
+ }
+
+ public bool IsGroupingEnabled
+ {
+ get { return (bool)GetValue(IsGroupingEnabledProperty); }
+ set { SetValue(IsGroupingEnabledProperty, value); }
+ }
+
+ public bool IsPullToRefreshEnabled
+ {
+ get { return (bool)GetValue(IsPullToRefreshEnabledProperty); }
+ set { SetValue(IsPullToRefreshEnabledProperty, value); }
+ }
+
+ public bool IsRefreshing
+ {
+ get { return (bool)GetValue(IsRefreshingProperty); }
+ set { SetValue(IsRefreshingProperty, value); }
+ }
+
+ public ICommand RefreshCommand
+ {
+ get { return (ICommand)GetValue(RefreshCommandProperty); }
+ set { SetValue(RefreshCommandProperty, value); }
+ }
+
+ public int RowHeight
+ {
+ get { return (int)GetValue(RowHeightProperty); }
+ set { SetValue(RowHeightProperty, value); }
+ }
+
+ public object SelectedItem
+ {
+ get { return GetValue(SelectedItemProperty); }
+ set { SetValue(SelectedItemProperty, value); }
+ }
+
+ public Color SeparatorColor
+ {
+ get { return (Color)GetValue(SeparatorColorProperty); }
+ set { SetValue(SeparatorColorProperty, value); }
+ }
+
+ public SeparatorVisibility SeparatorVisibility
+ {
+ get { return (SeparatorVisibility)GetValue(SeparatorVisibilityProperty); }
+ set { SetValue(SeparatorVisibilityProperty, value); }
+ }
+
+ internal ListViewCachingStrategy CachingStrategy { get; private set; }
+
+ internal bool TakePerformanceHit { get; set; }
+
+ bool RefreshAllowed
+ {
+ set
+ {
+ if (_refreshAllowed == value)
+ return;
+
+ _refreshAllowed = value;
+ OnPropertyChanged();
+ }
+ get { return _refreshAllowed; }
+ }
+
+ Element IListViewController.FooterElement
+ {
+ get { return _footerElement; }
+ }
+
+ Element IListViewController.HeaderElement
+ {
+ get { return _headerElement; }
+ }
+
+ bool IListViewController.RefreshAllowed
+ {
+ get { return RefreshAllowed; }
+ }
+
+ void IListViewController.SendCellAppearing(Cell cell)
+ {
+ EventHandler<ItemVisibilityEventArgs> handler = ItemAppearing;
+ if (handler != null)
+ handler(this, new ItemVisibilityEventArgs(cell.BindingContext));
+ }
+
+ void IListViewController.SendCellDisappearing(Cell cell)
+ {
+ EventHandler<ItemVisibilityEventArgs> handler = ItemDisappearing;
+ if (handler != null)
+ handler(this, new ItemVisibilityEventArgs(cell.BindingContext));
+ }
+
+ void IListViewController.SendRefreshing()
+ {
+ BeginRefresh();
+ }
+
+ public void BeginRefresh()
+ {
+ if (!RefreshAllowed)
+ return;
+
+ SetValueCore(IsRefreshingProperty, true);
+ OnRefreshing(EventArgs.Empty);
+
+ ICommand command = RefreshCommand;
+ if (command != null)
+ command.Execute(null);
+ }
+
+ public void EndRefresh()
+ {
+ SetValueCore(IsRefreshingProperty, false);
+ }
+
+ public event EventHandler<ItemVisibilityEventArgs> ItemAppearing;
+
+ public event EventHandler<ItemVisibilityEventArgs> ItemDisappearing;
+
+ public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
+
+ public event EventHandler<ItemTappedEventArgs> ItemTapped;
+
+ public event EventHandler Refreshing;
+
+ public void ScrollTo(object item, ScrollToPosition position, bool animated)
+ {
+ if (!Enum.IsDefined(typeof(ScrollToPosition), position))
+ throw new ArgumentException("position is not a valid ScrollToPosition", "position");
+
+ var args = new ScrollToRequestedEventArgs(item, position, animated);
+ if (IsPlatformEnabled)
+ OnScrollToRequested(args);
+ else
+ _pendingScroll = args;
+ }
+
+ public void ScrollTo(object item, object group, ScrollToPosition position, bool animated)
+ {
+ if (!IsGroupingEnabled)
+ throw new InvalidOperationException("Grouping is not enabled");
+ if (!Enum.IsDefined(typeof(ScrollToPosition), position))
+ throw new ArgumentException("position is not a valid ScrollToPosition", "position");
+
+ var args = new ScrollToRequestedEventArgs(item, group, position, animated);
+ if (IsPlatformEnabled)
+ OnScrollToRequested(args);
+ else
+ _pendingScroll = args;
+ }
+
+ protected override Cell CreateDefault(object item)
+ {
+ string text = null;
+ if (item != null)
+ text = item.ToString();
+
+ return new TextCell { Text = text };
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ var minimumSize = new Size(40, 40);
+ Size request;
+
+ double width = Math.Min(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height);
+
+ var list = ItemsSource as IList;
+ if (list != null && HasUnevenRows == false && RowHeight > 0 && !IsGroupingEnabled)
+ {
+ // we can calculate this
+ request = new Size(width, list.Count * RowHeight);
+ }
+ else
+ {
+ // probably not worth it
+ request = new Size(width, Math.Max(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height));
+ }
+
+ return new SizeRequest(request, minimumSize);
+ }
+
+ protected override void SetupContent(Cell content, int index)
+ {
+ base.SetupContent(content, index);
+ content.Parent = this;
+ }
+
+ protected override void UnhookContent(Cell content)
+ {
+ base.UnhookContent(content);
+ content.Parent = null;
+ }
+
+ internal Cell CreateDefaultCell(object item)
+ {
+ return CreateDefault(item);
+ }
+
+ internal void NotifyRowTapped(int groupIndex, int inGroupIndex, Cell cell = null)
+ {
+ TemplatedItemsList<ItemsView<Cell>, Cell> group = TemplatedItems.GetGroup(groupIndex);
+
+ bool changed = _previousGroupSelected != groupIndex || _previousRowSelected != inGroupIndex;
+
+ _previousRowSelected = inGroupIndex;
+ _previousGroupSelected = groupIndex;
+ if (cell == null)
+ {
+ cell = group[inGroupIndex];
+ }
+
+ // Set SelectedItem before any events so we don't override any changes they may have made.
+ SetValueCore(SelectedItemProperty, cell.BindingContext, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource | (changed ? SetValueFlags.RaiseOnEqual : 0));
+
+ cell.OnTapped();
+
+ ItemTapped?.Invoke(this, new ItemTappedEventArgs(group, cell.BindingContext));
+ }
+
+ internal void NotifyRowTapped(int index, Cell cell = null)
+ {
+ if (IsGroupingEnabled)
+ {
+ int leftOver;
+ int groupIndex = TemplatedItems.GetGroupIndexFromGlobal(index, out leftOver);
+
+ NotifyRowTapped(groupIndex, leftOver - 1, cell);
+ }
+ else
+ NotifyRowTapped(0, index, cell);
+ }
+
+ internal override void OnIsPlatformEnabledChanged()
+ {
+ base.OnIsPlatformEnabledChanged();
+
+ if (IsPlatformEnabled && _pendingScroll != null)
+ {
+ OnScrollToRequested(_pendingScroll);
+ _pendingScroll = null;
+ }
+ }
+
+ internal event EventHandler<ScrollToRequestedEventArgs> ScrollToRequested;
+
+ void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs)
+ {
+ RefreshAllowed = RefreshCommand.CanExecute(null);
+ }
+
+ static void OnFooterChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var lv = (ListView)bindable;
+ lv.OnHeaderOrFooterChanged(ref lv._footerElement, "FooterElement", newValue, lv.FooterTemplate, false);
+ }
+
+ static void OnFooterTemplateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var lv = (ListView)bindable;
+ lv.OnHeaderOrFooterChanged(ref lv._footerElement, "FooterElement", lv.Footer, (DataTemplate)newValue, true);
+ }
+
+ static void OnGroupDisplayBindingChanged(BindableObject bindable, BindingBase oldValue, BindingBase newValue)
+ {
+ var lv = (ListView)bindable;
+ if (newValue != null && lv.GroupHeaderTemplate != null)
+ {
+ lv.GroupHeaderTemplate = null;
+ Log.Warning("ListView", "GroupHeaderTemplate and GroupDisplayBinding can not be set at the same time, setting GroupHeaderTemplate to null");
+ }
+ }
+
+ static void OnGroupHeaderTemplateChanged(BindableObject bindable, object oldvalue, object newValue)
+ {
+ var lv = (ListView)bindable;
+ if (newValue != null && lv.GroupDisplayBinding != null)
+ {
+ lv.GroupDisplayBinding = null;
+ Debug.WriteLine("GroupHeaderTemplate and GroupDisplayBinding can not be set at the same time, setting GroupDisplayBinding to null");
+ }
+ }
+
+ static void OnHeaderChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var lv = (ListView)bindable;
+ lv.OnHeaderOrFooterChanged(ref lv._headerElement, "HeaderElement", newValue, lv.HeaderTemplate, false);
+ }
+
+ void OnHeaderOrFooterChanged(ref Element storage, string property, object dataObject, DataTemplate template, bool templateChanged)
+ {
+ if (dataObject == null)
+ {
+ if (!templateChanged)
+ {
+ OnPropertyChanging(property);
+ storage = null;
+ OnPropertyChanged(property);
+ }
+
+ return;
+ }
+
+ if (template == null)
+ {
+ var view = dataObject as Element;
+ if (view == null || view is Page)
+ view = new Label { Text = dataObject.ToString() };
+
+ view.Parent = this;
+ OnPropertyChanging(property);
+ storage = view;
+ OnPropertyChanged(property);
+ }
+ else if (storage == null || templateChanged)
+ {
+ OnPropertyChanging(property);
+ storage = template.CreateContent() as Element;
+ if (storage != null)
+ {
+ storage.BindingContext = dataObject;
+ storage.Parent = this;
+ }
+ OnPropertyChanged(property);
+ }
+ else
+ {
+ storage.BindingContext = dataObject;
+ }
+ }
+
+ static void OnHeaderTemplateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var lv = (ListView)bindable;
+ lv.OnHeaderOrFooterChanged(ref lv._headerElement, "HeaderElement", lv.Header, (DataTemplate)newValue, true);
+ }
+
+ static void OnRefreshCommandChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var lv = (ListView)bindable;
+ var oldCommand = (ICommand)oldValue;
+ var command = (ICommand)newValue;
+
+ lv.OnRefreshCommandChanged(oldCommand, command);
+ }
+
+ void OnRefreshCommandChanged(ICommand oldCommand, ICommand newCommand)
+ {
+ if (oldCommand != null)
+ {
+ oldCommand.CanExecuteChanged -= OnCommandCanExecuteChanged;
+ }
+
+ if (newCommand != null)
+ {
+ newCommand.CanExecuteChanged += OnCommandCanExecuteChanged;
+ RefreshAllowed = newCommand.CanExecute(null);
+ }
+ else
+ {
+ RefreshAllowed = true;
+ }
+ }
+
+ void OnRefreshing(EventArgs e)
+ {
+ EventHandler handler = Refreshing;
+ if (handler != null)
+ handler(this, e);
+ }
+
+ void OnScrollToRequested(ScrollToRequestedEventArgs e)
+ {
+ EventHandler<ScrollToRequestedEventArgs> handler = ScrollToRequested;
+ if (handler != null)
+ handler(this, e);
+ }
+
+ static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var list = (ListView)bindable;
+ if (list.ItemSelected != null)
+ list.ItemSelected(list, new SelectedItemChangedEventArgs(newValue));
+ }
+
+ static bool ValidateHeaderFooterTemplate(BindableObject bindable, object value)
+ {
+ if (value == null)
+ return true;
+ var template = (DataTemplate)value;
+ return template.CreateContent() is View;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ListViewCachingStrategy.cs b/Xamarin.Forms.Core/ListViewCachingStrategy.cs
new file mode 100644
index 00000000..7dd90196
--- /dev/null
+++ b/Xamarin.Forms.Core/ListViewCachingStrategy.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ public enum ListViewCachingStrategy
+ {
+ RetainElement = 0,
+ RecycleElement
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LockingSemaphore.cs b/Xamarin.Forms.Core/LockingSemaphore.cs
new file mode 100644
index 00000000..b9fd20a9
--- /dev/null
+++ b/Xamarin.Forms.Core/LockingSemaphore.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal class LockingSemaphore
+ {
+ static readonly Task Completed = Task.FromResult(true);
+ readonly Queue<TaskCompletionSource<bool>> _waiters = new Queue<TaskCompletionSource<bool>>();
+ int _currentCount;
+
+ public LockingSemaphore(int initialCount)
+ {
+ if (initialCount < 0)
+ throw new ArgumentOutOfRangeException("initialCount");
+ _currentCount = initialCount;
+ }
+
+ public void Release()
+ {
+ TaskCompletionSource<bool> toRelease = null;
+ lock(_waiters)
+ {
+ if (_waiters.Count > 0)
+ toRelease = _waiters.Dequeue();
+ else
+ ++_currentCount;
+ }
+ if (toRelease != null)
+ toRelease.TrySetResult(true);
+ }
+
+ public Task WaitAsync(CancellationToken token)
+ {
+ lock(_waiters)
+ {
+ if (_currentCount > 0)
+ {
+ --_currentCount;
+ return Completed;
+ }
+ var waiter = new TaskCompletionSource<bool>();
+ _waiters.Enqueue(waiter);
+ token.Register(() => waiter.TrySetCanceled());
+ return waiter.Task;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Log.cs b/Xamarin.Forms.Core/Log.cs
new file mode 100644
index 00000000..b8053e5d
--- /dev/null
+++ b/Xamarin.Forms.Core/Log.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal static class Log
+ {
+ static Log()
+ {
+ Listeners = new SynchronizedList<LogListener>();
+ }
+
+ public static IList<LogListener> Listeners { get; }
+
+ public static void Warning(string category, string message)
+ {
+ foreach (LogListener listener in Listeners)
+ listener.Warning(category, message);
+ }
+
+ public static void Warning(string category, string format, params object[] args)
+ {
+ Warning(category, string.Format(format, args));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/LogListener.cs b/Xamarin.Forms.Core/LogListener.cs
new file mode 100644
index 00000000..78222565
--- /dev/null
+++ b/Xamarin.Forms.Core/LogListener.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms
+{
+ internal abstract class LogListener
+ {
+ public abstract void Warning(string category, string message);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MasterBehavior.cs b/Xamarin.Forms.Core/MasterBehavior.cs
new file mode 100644
index 00000000..cd2dae2f
--- /dev/null
+++ b/Xamarin.Forms.Core/MasterBehavior.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.Forms
+{
+ public enum MasterBehavior
+ {
+ Default = 0,
+ SplitOnLandscape = 1,
+ Split = 2,
+ Popover = 3,
+ SplitOnPortrait = 4
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MasterDetailPage.cs b/Xamarin.Forms.Core/MasterDetailPage.cs
new file mode 100644
index 00000000..a0849aa4
--- /dev/null
+++ b/Xamarin.Forms.Core/MasterDetailPage.cs
@@ -0,0 +1,229 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_MasterDetailPageRenderer))]
+ public class MasterDetailPage : Page
+ {
+ public static readonly BindableProperty IsGestureEnabledProperty = BindableProperty.Create("IsGestureEnabled", typeof(bool), typeof(MasterDetailPage), true);
+
+ public static readonly BindableProperty IsPresentedProperty = BindableProperty.Create("IsPresented", typeof(bool), typeof(MasterDetailPage), default(bool),
+ propertyChanged: OnIsPresentedPropertyChanged, propertyChanging: OnIsPresentedPropertyChanging);
+
+ public static readonly BindableProperty MasterBehaviorProperty = BindableProperty.Create("MasterBehavior", typeof(MasterBehavior), typeof(MasterDetailPage), default(MasterBehavior),
+ propertyChanged: OnMasterBehaviorPropertyChanged);
+
+ Page _detail;
+
+ Rectangle _detailBounds;
+
+ Page _master;
+
+ Rectangle _masterBounds;
+
+ public Page Detail
+ {
+ get { return _detail; }
+ set
+ {
+ if (_detail != null && value == null)
+ throw new ArgumentNullException("value", "Detail cannot be set to null once a value is set.");
+
+ if (_detail == value)
+ return;
+
+ if (value.RealParent != null)
+ throw new InvalidOperationException("Detail must not already have a parent.");
+
+ OnPropertyChanging();
+ if (_detail != null)
+ InternalChildren.Remove(_detail);
+ _detail = value;
+ InternalChildren.Add(_detail);
+ OnPropertyChanged();
+ }
+ }
+
+ public bool IsGestureEnabled
+ {
+ get { return (bool)GetValue(IsGestureEnabledProperty); }
+ set { SetValue(IsGestureEnabledProperty, value); }
+ }
+
+ public bool IsPresented
+ {
+ get { return (bool)GetValue(IsPresentedProperty); }
+ set { SetValue(IsPresentedProperty, value); }
+ }
+
+ public Page Master
+ {
+ get { return _master; }
+ set
+ {
+ if (_master != null && value == null)
+ throw new ArgumentNullException("value", "Master cannot be set to null once a value is set");
+
+ if (string.IsNullOrEmpty(value.Title))
+ throw new InvalidOperationException("Title property must be set on Master page");
+
+ if (_master == value)
+ return;
+
+ if (value.RealParent != null)
+ throw new InvalidOperationException("Master must not already have a parent.");
+
+ OnPropertyChanging();
+ if (_master != null)
+ InternalChildren.Remove(_master);
+ _master = value;
+ InternalChildren.Add(_master);
+ OnPropertyChanged();
+ }
+ }
+
+ public MasterBehavior MasterBehavior
+ {
+ get { return (MasterBehavior)GetValue(MasterBehaviorProperty); }
+ set { SetValue(MasterBehaviorProperty, value); }
+ }
+
+ internal bool CanChangeIsPresented { get; set; } = true;
+
+ internal Rectangle DetailBounds
+ {
+ get { return _detailBounds; }
+ set
+ {
+ _detailBounds = value;
+ if (_detail == null)
+ throw new InvalidOperationException("Detail must be set before using a MasterDetailPage");
+ _detail.Layout(value);
+ }
+ }
+
+ internal Rectangle MasterBounds
+ {
+ get { return _masterBounds; }
+ set
+ {
+ _masterBounds = value;
+ if (_master == null)
+ throw new InvalidOperationException("Master must be set before using a MasterDetailPage");
+ _master.Layout(value);
+ }
+ }
+
+ internal bool ShouldShowSplitMode
+ {
+ get
+ {
+ if (Device.Idiom == TargetIdiom.Phone)
+ return false;
+
+ MasterBehavior behavior = MasterBehavior;
+ DeviceOrientation orientation = Device.Info.CurrentOrientation;
+
+ bool isSplitOnLandscape = (behavior == MasterBehavior.SplitOnLandscape || behavior == MasterBehavior.Default) && orientation.IsLandscape();
+ bool isSplitOnPortrait = behavior == MasterBehavior.SplitOnPortrait && orientation.IsPortrait();
+ return behavior == MasterBehavior.Split || isSplitOnLandscape || isSplitOnPortrait;
+ }
+ }
+
+ public event EventHandler IsPresentedChanged;
+
+ public virtual bool ShouldShowToolbarButton()
+ {
+ if (Device.Idiom == TargetIdiom.Phone)
+ return true;
+
+ MasterBehavior behavior = MasterBehavior;
+ DeviceOrientation orientation = Device.Info.CurrentOrientation;
+
+ bool isSplitOnLandscape = (behavior == MasterBehavior.SplitOnLandscape || behavior == MasterBehavior.Default) && orientation.IsLandscape();
+ bool isSplitOnPortrait = behavior == MasterBehavior.SplitOnPortrait && orientation.IsPortrait();
+ return behavior != MasterBehavior.Split && !isSplitOnLandscape && !isSplitOnPortrait;
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ if (Master == null || Detail == null)
+ throw new InvalidOperationException("Master and Detail must be set before using a MasterDetailPage");
+ _master.Layout(_masterBounds);
+ _detail.Layout(_detailBounds);
+ }
+
+ protected override void OnAppearing()
+ {
+ CanChangeIsPresented = true;
+ UpdateMasterBehavior(this);
+ base.OnAppearing();
+ }
+
+ protected override bool OnBackButtonPressed()
+ {
+ if (IsPresented)
+ {
+ if (Master.SendBackButtonPressed())
+ return true;
+ }
+
+ EventHandler<BackButtonPressedEventArgs> handler = BackButtonPressed;
+ if (handler != null)
+ {
+ var args = new BackButtonPressedEventArgs();
+ handler(this, args);
+ if (args.Handled)
+ return true;
+ }
+
+ if (Detail.SendBackButtonPressed())
+ {
+ return true;
+ }
+
+ return base.OnBackButtonPressed();
+ }
+
+ protected override void OnParentSet()
+ {
+ if (RealParent != null && (Master == null || Detail == null))
+ throw new InvalidOperationException("Master and Detail must be set before adding MasterDetailPage to a container");
+ base.OnParentSet();
+ }
+
+ internal event EventHandler<BackButtonPressedEventArgs> BackButtonPressed;
+
+ internal static void UpdateMasterBehavior(MasterDetailPage page)
+ {
+ if (page.ShouldShowSplitMode)
+ {
+ page.SetValueCore(IsPresentedProperty, true);
+ if (page.MasterBehavior != MasterBehavior.Default)
+ page.CanChangeIsPresented = false;
+ }
+ }
+
+ static void OnIsPresentedPropertyChanged(BindableObject sender, object oldValue, object newValue)
+ {
+ var page = (MasterDetailPage)sender;
+ EventHandler handler = page.IsPresentedChanged;
+ if (handler != null)
+ handler(page, EventArgs.Empty);
+ }
+
+ static void OnIsPresentedPropertyChanging(BindableObject sender, object oldValue, object newValue)
+ {
+ var page = (MasterDetailPage)sender;
+ if (!page.CanChangeIsPresented)
+ throw new InvalidOperationException(string.Format("Can't change IsPresented when setting {0}", page.MasterBehavior));
+ }
+
+ static void OnMasterBehaviorPropertyChanged(BindableObject sender, object oldValue, object newValue)
+ {
+ var page = (MasterDetailPage)sender;
+ UpdateMasterBehavior(page);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MeasureFlags.cs b/Xamarin.Forms.Core/MeasureFlags.cs
new file mode 100644
index 00000000..c2e5c80a
--- /dev/null
+++ b/Xamarin.Forms.Core/MeasureFlags.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ public enum MeasureFlags
+ {
+ None = 0,
+ IncludeMargins = 1 << 0
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MenuItem.cs b/Xamarin.Forms.Core/MenuItem.cs
new file mode 100644
index 00000000..3e830445
--- /dev/null
+++ b/Xamarin.Forms.Core/MenuItem.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Windows.Input;
+
+namespace Xamarin.Forms
+{
+ public class MenuItem : BaseMenuItem
+ {
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(MenuItem), null);
+
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(MenuItem), null,
+ propertyChanging: (bo, o, n) => ((MenuItem)bo).OnCommandChanging(), propertyChanged: (bo, o, n) => ((MenuItem)bo).OnCommandChanged());
+
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(MenuItem), null,
+ propertyChanged: (bo, o, n) => ((MenuItem)bo).OnCommandParameterChanged());
+
+ public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create("IsDestructive", typeof(bool), typeof(MenuItem), false);
+
+ public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(FileImageSource), typeof(MenuItem), default(FileImageSource));
+
+ internal static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(ToolbarItem), true);
+
+ public ICommand Command
+ {
+ get { return (ICommand)GetValue(CommandProperty); }
+ set { SetValue(CommandProperty, value); }
+ }
+
+ public object CommandParameter
+ {
+ get { return GetValue(CommandParameterProperty); }
+ set { SetValue(CommandParameterProperty, value); }
+ }
+
+ public FileImageSource Icon
+ {
+ get { return (FileImageSource)GetValue(IconProperty); }
+ set { SetValue(IconProperty, value); }
+ }
+
+ public bool IsDestructive
+ {
+ get { return (bool)GetValue(IsDestructiveProperty); }
+ set { SetValue(IsDestructiveProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ internal bool IsEnabled
+ {
+ get { return (bool)GetValue(IsEnabledProperty); }
+ set { SetValue(IsEnabledProperty, value); }
+ }
+
+ bool IsEnabledCore
+ {
+ set { SetValueCore(IsEnabledProperty, value); }
+ }
+
+ public event EventHandler Clicked;
+
+ protected virtual void OnClicked()
+ {
+ EventHandler handler = Clicked;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ internal void Activate()
+ {
+ if (Command != null)
+ {
+ if (IsEnabled)
+ Command.Execute(CommandParameter);
+ }
+
+ OnClicked();
+ }
+
+ void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs)
+ {
+ IsEnabledCore = Command.CanExecute(CommandParameter);
+ }
+
+ void OnCommandChanged()
+ {
+ if (Command == null)
+ {
+ IsEnabledCore = true;
+ return;
+ }
+
+ IsEnabledCore = Command.CanExecute(CommandParameter);
+
+ Command.CanExecuteChanged += OnCommandCanExecuteChanged;
+ }
+
+ void OnCommandChanging()
+ {
+ if (Command == null)
+ return;
+
+ Command.CanExecuteChanged -= OnCommandCanExecuteChanged;
+ }
+
+ void OnCommandParameterChanged()
+ {
+ if (Command == null)
+ return;
+
+ IsEnabledCore = Command.CanExecute(CommandParameter);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MergedStyle.cs b/Xamarin.Forms.Core/MergedStyle.cs
new file mode 100644
index 00000000..9f9c68bf
--- /dev/null
+++ b/Xamarin.Forms.Core/MergedStyle.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ public partial class VisualElement
+ {
+ sealed class MergedStyle : IStyle
+ {
+ ////If the base type is one of these, stop registering dynamic resources further
+ ////The last one (typeof(Element)) is a safety guard as we might be creating VisualElement directly in internal code
+ static readonly IList<Type> s_stopAtTypes = new List<Type> { typeof(View), typeof(Layout<>), typeof(VisualElement), typeof(Element) };
+
+ readonly BindableProperty _classStyleProperty = BindableProperty.Create("ClassStyle", typeof(IList<Style>), typeof(VisualElement), default(IList<Style>),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.OnClassStyleChanged());
+
+ readonly List<BindableProperty> _implicitStyles = new List<BindableProperty>();
+
+ IStyle _classStyle;
+
+ IStyle _implicitStyle;
+
+ IStyle _style;
+
+ string _styleClass;
+
+ public MergedStyle(Type targetType, BindableObject target)
+ {
+ Target = target;
+ TargetType = targetType;
+ RegisterImplicitStyles();
+ Apply(Target);
+ }
+
+ public IStyle Style
+ {
+ get { return _style; }
+ set { SetStyle(ImplicitStyle, ClassStyle, value); }
+ }
+
+ public string StyleClass
+ {
+ get { return _styleClass; }
+ set
+ {
+ string val = string.IsNullOrWhiteSpace(value) ? null : value.Trim();
+ if (_styleClass == val)
+ return;
+
+ if (_styleClass != null)
+ Target.RemoveDynamicResource(_classStyleProperty);
+
+ _styleClass = val;
+
+ if (_styleClass != null)
+ Target.SetDynamicResource(_classStyleProperty, Forms.Style.StyleClassPrefix + _styleClass);
+ }
+ }
+
+ public BindableObject Target { get; }
+
+ IStyle ClassStyle
+ {
+ get { return _classStyle; }
+ set { SetStyle(ImplicitStyle, value, Style); }
+ }
+
+ IStyle ImplicitStyle
+ {
+ get { return _implicitStyle; }
+ set { SetStyle(value, ClassStyle, Style); }
+ }
+
+ public void Apply(BindableObject bindable)
+ {
+ ImplicitStyle?.Apply(bindable);
+ ClassStyle?.Apply(bindable);
+ Style?.Apply(bindable);
+ }
+
+ public Type TargetType { get; }
+
+ public void UnApply(BindableObject bindable)
+ {
+ Style?.UnApply(bindable);
+ ClassStyle?.UnApply(bindable);
+ ImplicitStyle?.UnApply(bindable);
+ }
+
+ void OnClassStyleChanged()
+ {
+ var classStyles = Target.GetValue(_classStyleProperty) as IList<Style>;
+ if (classStyles == null)
+ ClassStyle = null;
+ else
+ {
+ ClassStyle = classStyles.FirstOrDefault(s => s.CanBeAppliedTo(TargetType));
+ }
+ }
+
+ void OnImplicitStyleChanged()
+ {
+ var first = true;
+ foreach (BindableProperty implicitStyleProperty in _implicitStyles)
+ {
+ var implicitStyle = (Style)Target.GetValue(implicitStyleProperty);
+ if (implicitStyle != null)
+ {
+ if (first || implicitStyle.ApplyToDerivedTypes)
+ {
+ ImplicitStyle = implicitStyle;
+ return;
+ }
+ }
+ first = false;
+ }
+ }
+
+ void RegisterImplicitStyles()
+ {
+ Type type = TargetType;
+ while (true)
+ {
+ BindableProperty implicitStyleProperty = BindableProperty.Create("ImplicitStyle", typeof(Style), typeof(VisualElement), default(Style),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.OnImplicitStyleChanged());
+ Target.SetDynamicResource(implicitStyleProperty, type.FullName);
+ _implicitStyles.Add(implicitStyleProperty);
+ type = type.GetTypeInfo().BaseType;
+ if (s_stopAtTypes.Contains(type))
+ return;
+ }
+ }
+
+ void SetStyle(IStyle implicitStyle, IStyle classStyle, IStyle style)
+ {
+ bool shouldReApplyStyle = implicitStyle != ImplicitStyle || classStyle != ClassStyle || Style != style;
+ bool shouldReApplyClassStyle = implicitStyle != ImplicitStyle || classStyle != ClassStyle;
+ bool shouldReApplyImplicitStyle = implicitStyle != ImplicitStyle && (Style as Style == null || ((Style)Style).CanCascade);
+
+ if (shouldReApplyStyle)
+ Style?.UnApply(Target);
+ if (shouldReApplyClassStyle)
+ ClassStyle?.UnApply(Target);
+ if (shouldReApplyImplicitStyle)
+ ImplicitStyle?.UnApply(Target);
+
+ _implicitStyle = implicitStyle;
+ _classStyle = classStyle;
+ _style = style;
+
+ if (shouldReApplyImplicitStyle)
+ ImplicitStyle?.Apply(Target);
+ if (shouldReApplyClassStyle)
+ ClassStyle?.Apply(Target);
+ if (shouldReApplyStyle)
+ Style?.Apply(Target);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MessagingCenter.cs b/Xamarin.Forms.Core/MessagingCenter.cs
new file mode 100644
index 00000000..973531ab
--- /dev/null
+++ b/Xamarin.Forms.Core/MessagingCenter.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ public static class MessagingCenter
+ {
+ static readonly Dictionary<Tuple<string, Type, Type>, List<Tuple<WeakReference, Action<object, object>>>> s_callbacks =
+ new Dictionary<Tuple<string, Type, Type>, List<Tuple<WeakReference, Action<object, object>>>>();
+
+ public static void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class
+ {
+ if (sender == null)
+ throw new ArgumentNullException("sender");
+ InnerSend(message, typeof(TSender), typeof(TArgs), sender, args);
+ }
+
+ public static void Send<TSender>(TSender sender, string message) where TSender : class
+ {
+ if (sender == null)
+ throw new ArgumentNullException("sender");
+ InnerSend(message, typeof(TSender), null, sender, null);
+ }
+
+ public static void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class
+ {
+ if (subscriber == null)
+ throw new ArgumentNullException("subscriber");
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ Action<object, object> wrap = (sender, args) =>
+ {
+ var send = (TSender)sender;
+ if (source == null || send == source)
+ callback((TSender)sender, (TArgs)args);
+ };
+
+ InnerSubscribe(subscriber, message, typeof(TSender), typeof(TArgs), wrap);
+ }
+
+ public static void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class
+ {
+ if (subscriber == null)
+ throw new ArgumentNullException("subscriber");
+ if (callback == null)
+ throw new ArgumentNullException("callback");
+
+ Action<object, object> wrap = (sender, args) =>
+ {
+ var send = (TSender)sender;
+ if (source == null || send == source)
+ callback((TSender)sender);
+ };
+
+ InnerSubscribe(subscriber, message, typeof(TSender), null, wrap);
+ }
+
+ public static void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class
+ {
+ InnerUnsubscribe(message, typeof(TSender), typeof(TArgs), subscriber);
+ }
+
+ public static void Unsubscribe<TSender>(object subscriber, string message) where TSender : class
+ {
+ InnerUnsubscribe(message, typeof(TSender), null, subscriber);
+ }
+
+ internal static void ClearSubscribers()
+ {
+ s_callbacks.Clear();
+ }
+
+ static void InnerSend(string message, Type senderType, Type argType, object sender, object args)
+ {
+ if (message == null)
+ throw new ArgumentNullException("message");
+ var key = new Tuple<string, Type, Type>(message, senderType, argType);
+ if (!s_callbacks.ContainsKey(key))
+ return;
+ List<Tuple<WeakReference, Action<object, object>>> actions = s_callbacks[key];
+ if (actions == null || !actions.Any())
+ return; // should not be reachable
+
+ // ok so this code looks a bit funky but here is the gist of the problem. It is possible that in the course
+ // of executing the callbacks for this message someone will subscribe/unsubscribe from the same message in
+ // the callback. This would invalidate the enumerator. To work around this we make a copy. However if you unsubscribe
+ // from a message you can fairly reasonably expect that you will therefor not receive a call. To fix this we then
+ // check that the item we are about to send the message to actually exists in the live list.
+ List<Tuple<WeakReference, Action<object, object>>> actionsCopy = actions.ToList();
+ foreach (Tuple<WeakReference, Action<object, object>> action in actionsCopy)
+ {
+ if (action.Item1.IsAlive && actions.Contains(action))
+ action.Item2(sender, args);
+ }
+ }
+
+ static void InnerSubscribe(object subscriber, string message, Type senderType, Type argType, Action<object, object> callback)
+ {
+ if (message == null)
+ throw new ArgumentNullException("message");
+ var key = new Tuple<string, Type, Type>(message, senderType, argType);
+ var value = new Tuple<WeakReference, Action<object, object>>(new WeakReference(subscriber), callback);
+ if (s_callbacks.ContainsKey(key))
+ {
+ s_callbacks[key].Add(value);
+ }
+ else
+ {
+ var list = new List<Tuple<WeakReference, Action<object, object>>> { value };
+ s_callbacks[key] = list;
+ }
+ }
+
+ static void InnerUnsubscribe(string message, Type senderType, Type argType, object subscriber)
+ {
+ if (subscriber == null)
+ throw new ArgumentNullException("subscriber");
+ if (message == null)
+ throw new ArgumentNullException("message");
+
+ var key = new Tuple<string, Type, Type>(message, senderType, argType);
+ if (!s_callbacks.ContainsKey(key))
+ return;
+ s_callbacks[key].RemoveAll(tuple => !tuple.Item1.IsAlive || tuple.Item1.Target == subscriber);
+ if (!s_callbacks[key].Any())
+ s_callbacks.Remove(key);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ModalEventArgs.cs b/Xamarin.Forms.Core/ModalEventArgs.cs
new file mode 100644
index 00000000..483ea5ad
--- /dev/null
+++ b/Xamarin.Forms.Core/ModalEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class ModalEventArgs : EventArgs
+ {
+ protected ModalEventArgs(Page modal)
+ {
+ Modal = modal;
+ }
+
+ public Page Modal { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ModalPoppedEventArgs.cs b/Xamarin.Forms.Core/ModalPoppedEventArgs.cs
new file mode 100644
index 00000000..3c17f009
--- /dev/null
+++ b/Xamarin.Forms.Core/ModalPoppedEventArgs.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public class ModalPoppedEventArgs : ModalEventArgs
+ {
+ public ModalPoppedEventArgs(Page modal) : base(modal)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ModalPoppingEventArgs.cs b/Xamarin.Forms.Core/ModalPoppingEventArgs.cs
new file mode 100644
index 00000000..57c7a657
--- /dev/null
+++ b/Xamarin.Forms.Core/ModalPoppingEventArgs.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.Forms
+{
+ public class ModalPoppingEventArgs : ModalEventArgs
+ {
+ public ModalPoppingEventArgs(Page modal) : base(modal)
+ {
+ }
+
+ public bool Cancel { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ModalPushedEventArgs.cs b/Xamarin.Forms.Core/ModalPushedEventArgs.cs
new file mode 100644
index 00000000..d09caf4d
--- /dev/null
+++ b/Xamarin.Forms.Core/ModalPushedEventArgs.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public class ModalPushedEventArgs : ModalEventArgs
+ {
+ public ModalPushedEventArgs(Page modal) : base(modal)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ModalPushingEventArgs.cs b/Xamarin.Forms.Core/ModalPushingEventArgs.cs
new file mode 100644
index 00000000..12396c9e
--- /dev/null
+++ b/Xamarin.Forms.Core/ModalPushingEventArgs.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public class ModalPushingEventArgs : ModalEventArgs
+ {
+ public ModalPushingEventArgs(Page modal) : base(modal)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/MultiPage.cs b/Xamarin.Forms.Core/MultiPage.cs
new file mode 100644
index 00000000..89fd7e9c
--- /dev/null
+++ b/Xamarin.Forms.Core/MultiPage.cs
@@ -0,0 +1,359 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Children")]
+ public abstract class MultiPage<T> : Page, IViewContainer<T>, IPageContainer<T>, IItemsView<T> where T : Page
+ {
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(MultiPage<>), null);
+
+ public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(MultiPage<>), null);
+
+ public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(object), typeof(MultiPage<>), null, BindingMode.TwoWay);
+
+ internal static readonly BindableProperty IndexProperty = BindableProperty.Create("Index", typeof(int), typeof(Page), -1);
+
+ readonly ElementCollection<T> _children;
+ readonly TemplatedItemsList<MultiPage<T>, T> _templatedItems;
+
+ T _current;
+
+ protected MultiPage()
+ {
+ _templatedItems = new TemplatedItemsList<MultiPage<T>, T>(this, ItemsSourceProperty, ItemTemplateProperty);
+ _templatedItems.CollectionChanged += OnTemplatedItemsChanged;
+
+ _children = new ElementCollection<T>(InternalChildren);
+ InternalChildren.CollectionChanged += OnChildrenChanged;
+ }
+
+ public IEnumerable ItemsSource
+ {
+ get { return (IEnumerable)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
+
+ public DataTemplate ItemTemplate
+ {
+ get { return (DataTemplate)GetValue(ItemTemplateProperty); }
+ set { SetValue(ItemTemplateProperty, value); }
+ }
+
+ public object SelectedItem
+ {
+ get { return GetValue(SelectedItemProperty); }
+ set { SetValue(SelectedItemProperty, value); }
+ }
+
+ T IItemsView<T>.CreateDefault(object item)
+ {
+ return CreateDefault(item);
+ }
+
+ void IItemsView<T>.SetupContent(T content, int index)
+ {
+ SetupContent(content, index);
+ }
+
+ void IItemsView<T>.UnhookContent(T content)
+ {
+ UnhookContent(content);
+ }
+
+ public T CurrentPage
+ {
+ get { return _current; }
+ set
+ {
+ if (_current == value)
+ return;
+
+ OnPropertyChanging();
+ _current = value;
+ OnPropertyChanged();
+ OnCurrentPageChanged();
+ }
+ }
+
+ public IList<T> Children
+ {
+ get { return _children; }
+ }
+
+ public event EventHandler CurrentPageChanged;
+
+ public event NotifyCollectionChangedEventHandler PagesChanged;
+
+ protected abstract T CreateDefault(object item);
+
+ protected override bool OnBackButtonPressed()
+ {
+ if (CurrentPage != null)
+ {
+ bool handled = CurrentPage.SendBackButtonPressed();
+ if (handled)
+ return true;
+ }
+
+ return base.OnBackButtonPressed();
+ }
+
+ protected override void OnChildAdded(Element child)
+ {
+ base.OnChildAdded(child);
+
+ ForceLayout();
+ }
+
+ protected virtual void OnCurrentPageChanged()
+ {
+ EventHandler changed = CurrentPageChanged;
+ if (changed != null)
+ changed(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnPagesChanged(NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler handler = PagesChanged;
+ if (handler != null)
+ handler(this, e);
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ if (propertyName == ItemsSourceProperty.PropertyName)
+ _children.IsReadOnly = ItemsSource != null;
+ else if (propertyName == SelectedItemProperty.PropertyName)
+ {
+ UpdateCurrentPage();
+ }
+ else if (propertyName == "CurrentPage" && ItemsSource != null)
+ {
+ if (CurrentPage == null)
+ {
+ SelectedItem = null;
+ }
+ else
+ {
+ int index = _templatedItems.IndexOf(CurrentPage);
+ SelectedItem = index != -1 ? _templatedItems.ListProxy[index] : null;
+ }
+ }
+
+ base.OnPropertyChanged(propertyName);
+ }
+
+ protected virtual void SetupContent(T content, int index)
+ {
+ }
+
+ protected virtual void UnhookContent(T content)
+ {
+ }
+
+ internal static int GetIndex(T page)
+ {
+ if (page == null)
+ throw new ArgumentNullException("page");
+
+ return (int)page.GetValue(IndexProperty);
+ }
+
+ internal T GetPageByIndex(int index)
+ {
+ foreach (T page in InternalChildren)
+ {
+ if (index == GetIndex(page))
+ return page;
+ }
+ return null;
+ }
+
+ internal static void SetIndex(Page page, int index)
+ {
+ if (page == null)
+ throw new ArgumentNullException("page");
+
+ page.SetValue(IndexProperty, index);
+ }
+
+ void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (Children.IsReadOnly)
+ return;
+
+ var i = 0;
+ foreach (T page in Children)
+ SetIndex(page, i++);
+
+ OnPagesChanged(e);
+
+ if (CurrentPage == null || Children.IndexOf(CurrentPage) == -1)
+ CurrentPage = Children.FirstOrDefault();
+ }
+
+ void OnTemplatedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (int i = e.NewStartingIndex; i < Children.Count; i++)
+ SetIndex((T)InternalChildren[i], i + e.NewItems.Count);
+
+ for (var i = 0; i < e.NewItems.Count; i++)
+ {
+ var page = (T)e.NewItems[i];
+ page.Owned = true;
+ int index = i + e.NewStartingIndex;
+ SetIndex(page, index);
+ InternalChildren.Insert(index, (T)e.NewItems[i]);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ int removeIndex = e.OldStartingIndex;
+ for (int i = removeIndex + e.OldItems.Count; i < Children.Count; i++)
+ SetIndex((T)InternalChildren[i], removeIndex++);
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ {
+ Element element = InternalChildren[e.OldStartingIndex];
+ InternalChildren.RemoveAt(e.OldStartingIndex);
+ element.Owned = false;
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ if (e.NewStartingIndex < 0 || e.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ if (e.NewStartingIndex == e.OldStartingIndex)
+ return;
+
+ bool movingForward = e.OldStartingIndex < e.NewStartingIndex;
+
+ if (movingForward)
+ {
+ int moveIndex = e.OldStartingIndex;
+ for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++)
+ SetIndex((T)InternalChildren[i], moveIndex++);
+ }
+ else
+ {
+ for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++)
+ {
+ var page = (T)InternalChildren[i + e.NewStartingIndex];
+ SetIndex(page, GetIndex(page) + e.OldItems.Count);
+ }
+ }
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ InternalChildren.RemoveAt(e.OldStartingIndex);
+
+ int insertIndex = e.NewStartingIndex;
+ if (movingForward)
+ insertIndex -= e.OldItems.Count - 1;
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ {
+ var page = (T)e.OldItems[i];
+ SetIndex(page, insertIndex + i);
+ InternalChildren.Insert(insertIndex + i, page);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ if (e.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (int i = e.OldStartingIndex; i - e.OldStartingIndex < e.OldItems.Count; i++)
+ {
+ Element element = InternalChildren[i];
+ InternalChildren.RemoveAt(i);
+ element.Owned = false;
+
+ T page = _templatedItems.GetOrCreateContent(i, e.NewItems[i - e.OldStartingIndex]);
+ page.Owned = true;
+ SetIndex(page, i);
+ InternalChildren.Insert(i, page);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ Reset();
+ return;
+ }
+
+ OnPagesChanged(e);
+ UpdateCurrentPage();
+ }
+
+ void Reset()
+ {
+ List<Element> snapshot = InternalChildren.ToList();
+
+ InternalChildren.Clear();
+
+ foreach (Element element in snapshot)
+ element.Owned = false;
+
+ for (var i = 0; i < _templatedItems.Count; i++)
+ {
+ T page = _templatedItems.GetOrCreateContent(i, _templatedItems.ListProxy[i]);
+ page.Owned = true;
+ SetIndex(page, i);
+ InternalChildren.Add(page);
+ }
+
+ var currentNeedsUpdate = true;
+
+ BatchBegin();
+
+ if (ItemsSource != null)
+ {
+ object selected = SelectedItem;
+ if (selected == null || !ItemsSource.Cast<object>().Contains(selected))
+ {
+ SelectedItem = ItemsSource.Cast<object>().FirstOrDefault();
+ currentNeedsUpdate = false;
+ }
+ }
+
+ if (currentNeedsUpdate)
+ UpdateCurrentPage();
+
+ OnPagesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+
+ BatchCommit();
+ }
+
+ void UpdateCurrentPage()
+ {
+ if (ItemsSource != null)
+ {
+ int index = _templatedItems.ListProxy.IndexOf(SelectedItem);
+ if (index == -1)
+ CurrentPage = (T)InternalChildren.FirstOrDefault();
+ else
+ CurrentPage = _templatedItems.GetOrCreateContent(index, SelectedItem);
+ }
+ else if (SelectedItem is T)
+ CurrentPage = (T)SelectedItem;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NameScopeExtensions.cs b/Xamarin.Forms.Core/NameScopeExtensions.cs
new file mode 100644
index 00000000..b9acb460
--- /dev/null
+++ b/Xamarin.Forms.Core/NameScopeExtensions.cs
@@ -0,0 +1,17 @@
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+ public static class NameScopeExtensions
+ {
+ public static T FindByName<T>(this Element element, string name)
+ {
+ return ((INameScope)element).FindByName<T>(name);
+ }
+
+ internal static T FindByName<T>(this INameScope namescope, string name)
+ {
+ return (T)namescope.FindByName(name);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NamedSize.cs b/Xamarin.Forms.Core/NamedSize.cs
new file mode 100644
index 00000000..92f7a579
--- /dev/null
+++ b/Xamarin.Forms.Core/NamedSize.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.Forms
+{
+ public enum NamedSize
+ {
+ Default = 0,
+ Micro = 1,
+ Small = 2,
+ Medium = 3,
+ Large = 4
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationEventArgs.cs b/Xamarin.Forms.Core/NavigationEventArgs.cs
new file mode 100644
index 00000000..0ccd4343
--- /dev/null
+++ b/Xamarin.Forms.Core/NavigationEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class NavigationEventArgs : EventArgs
+ {
+ public NavigationEventArgs(Page page)
+ {
+ if (page == null)
+ throw new ArgumentNullException("page");
+
+ Page = page;
+ }
+
+ public Page Page { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationMenu.cs b/Xamarin.Forms.Core/NavigationMenu.cs
new file mode 100644
index 00000000..2386dd29
--- /dev/null
+++ b/Xamarin.Forms.Core/NavigationMenu.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ // Mark as internal until renderers are ready for release after 1.0
+ [RenderWith(typeof(_NavigationMenuRenderer))]
+ internal class NavigationMenu : View
+ {
+ readonly List<Page> _targets = new List<Page>();
+
+ public IEnumerable<Page> Targets
+ {
+ get { return _targets; }
+ set
+ {
+ if (_targets.AsEnumerable().SequenceEqual(value))
+ return;
+
+ foreach (Page page in value)
+ {
+ VerifyTarget(page);
+ }
+
+ OnPropertyChanging();
+ _targets.Clear();
+ _targets.AddRange(value);
+ OnPropertyChanged();
+ }
+ }
+
+ public void Add(Page target)
+ {
+ if (_targets.Contains(target))
+ return;
+ VerifyTarget(target);
+
+ OnPropertyChanging("Targets");
+ _targets.Add(target);
+ OnPropertyChanged("Targets");
+ }
+
+ public void Remove(Page target)
+ {
+ if (_targets.Contains(target))
+ {
+ OnPropertyChanging("Targets");
+ if (_targets.Remove(target))
+ OnPropertyChanged("Targets");
+ }
+ }
+
+ internal void SendTargetSelected(Page target)
+ {
+ TargetSelected(target);
+ }
+
+ void TargetSelected(Page target)
+ {
+ Navigation.PushAsync(target);
+ }
+
+ void VerifyTarget(Page target)
+ {
+ if (target.Icon == null || string.IsNullOrWhiteSpace(target.Icon.File))
+ throw new Exception("Icon must be set for each page before adding them to a Navigation Menu");
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationModel.cs b/Xamarin.Forms.Core/NavigationModel.cs
new file mode 100644
index 00000000..4591d4a4
--- /dev/null
+++ b/Xamarin.Forms.Core/NavigationModel.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal class NavigationModel
+ {
+ readonly List<Page> _modalStack = new List<Page>();
+ readonly List<List<Page>> _navTree = new List<List<Page>>();
+
+ public Page CurrentPage
+ {
+ get
+ {
+ if (_navTree.Any())
+ return _navTree.Last().Last();
+ return null;
+ }
+ }
+
+ public IEnumerable<Page> Modals
+ {
+ get { return _modalStack; }
+ }
+
+ public IEnumerable<Page> Roots
+ {
+ get
+ {
+ foreach (List<Page> list in _navTree)
+ {
+ yield return list[0];
+ }
+ }
+ }
+
+ public IReadOnlyList<IReadOnlyList<Page>> Tree
+ {
+ get { return _navTree; }
+ }
+
+ public void Clear()
+ {
+ _navTree.Clear();
+ }
+
+ public void InsertPageBefore(Page page, Page before)
+ {
+ List<Page> currentStack = _navTree.Last();
+ int index = currentStack.IndexOf(before);
+
+ if (index == -1)
+ throw new ArgumentException("before must be in the current navigation context");
+
+ currentStack.Insert(index, page);
+ }
+
+ public Page Pop(Page ancestralNav)
+ {
+ ancestralNav = AncestorToRoot(ancestralNav);
+ foreach (List<Page> stack in _navTree)
+ {
+ if (stack.Contains(ancestralNav))
+ {
+ if (stack.Count <= 1)
+ throw new InvalidNavigationException("Can not pop final item in stack");
+ Page result = stack.Last();
+ stack.Remove(result);
+ return result;
+ }
+ }
+
+ throw new InvalidNavigationException("Popped from unpushed item?");
+ }
+
+ public Page PopModal()
+ {
+ if (_navTree.Count <= 1)
+ throw new InvalidNavigationException("Can't pop modal without any modals pushed");
+ Page modal = _navTree.Last().First();
+ _modalStack.Remove(modal);
+ _navTree.Remove(_navTree.Last());
+ return modal;
+ }
+
+ public Page PopTopPage()
+ {
+ Page itemToRemove;
+ if (_navTree.Count == 1)
+ {
+ if (_navTree[0].Count > 1)
+ {
+ itemToRemove = _navTree[0].Last();
+ _navTree[0].Remove(itemToRemove);
+ return itemToRemove;
+ }
+ return null;
+ }
+ itemToRemove = _navTree.Last().Last();
+ _navTree.Last().Remove(itemToRemove);
+ if (!_navTree.Last().Any())
+ {
+ _navTree.RemoveAt(_navTree.Count - 1);
+ }
+ return itemToRemove;
+ }
+
+ public void PopToRoot(Page ancestralNav)
+ {
+ ancestralNav = AncestorToRoot(ancestralNav);
+ foreach (List<Page> stack in _navTree)
+ {
+ if (stack.Contains(ancestralNav))
+ {
+ if (stack.Count <= 1)
+ throw new InvalidNavigationException("Can not pop final item in stack");
+ stack.RemoveRange(1, stack.Count - 1);
+ return;
+ }
+ }
+
+ throw new InvalidNavigationException("Popped from unpushed item?");
+ }
+
+ public void Push(Page page, Page ancestralNav)
+ {
+ if (ancestralNav == null)
+ {
+ if (_navTree.Any())
+ throw new InvalidNavigationException("Ancestor must be provided for all pushes except first");
+ _navTree.Add(new List<Page> { page });
+ return;
+ }
+
+ ancestralNav = AncestorToRoot(ancestralNav);
+
+ foreach (List<Page> stack in _navTree)
+ {
+ if (stack.Contains(ancestralNav))
+ {
+ stack.Add(page);
+ return;
+ }
+ }
+
+ throw new InvalidNavigationException("Invalid ancestor passed");
+ }
+
+ public void PushModal(Page page)
+ {
+ _navTree.Add(new List<Page> { page });
+ _modalStack.Add(page);
+ }
+
+ public bool RemovePage(Page page)
+ {
+ bool found;
+ List<Page> currentStack = _navTree.Last();
+ var i = 0;
+ while (!(found = currentStack.Remove(page)) && i < _navTree.Count - 1)
+ {
+ currentStack = _navTree[i++];
+ }
+
+ return found;
+ }
+
+ Page AncestorToRoot(Page ancestor)
+ {
+ Page result = ancestor;
+ while (!Application.IsApplicationOrNull(result.RealParent))
+ result = (Page)result.RealParent;
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationPage.cs b/Xamarin.Forms.Core/NavigationPage.cs
new file mode 100644
index 00000000..61545b11
--- /dev/null
+++ b/Xamarin.Forms.Core/NavigationPage.cs
@@ -0,0 +1,411 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_NavigationPageRenderer))]
+ public class NavigationPage : Page, IPageContainer<Page>
+ {
+ public static readonly BindableProperty BackButtonTitleProperty = BindableProperty.CreateAttached("BackButtonTitle", typeof(string), typeof(Page), null);
+
+ public static readonly BindableProperty HasNavigationBarProperty = BindableProperty.CreateAttached("HasNavigationBar", typeof(bool), typeof(Page), true);
+
+ public static readonly BindableProperty HasBackButtonProperty = BindableProperty.CreateAttached("HasBackButton", typeof(bool), typeof(NavigationPage), true);
+
+ [Obsolete("Use BarBackgroundColorProperty and BarTextColorProperty to change NavigationPage bar color properties")] public static readonly BindableProperty TintProperty =
+ BindableProperty.Create("Tint", typeof(Color), typeof(NavigationPage), Color.Default);
+
+ public static readonly BindableProperty BarBackgroundColorProperty = BindableProperty.Create("BarBackgroundColor", typeof(Color), typeof(NavigationPage), Color.Default);
+
+ public static readonly BindableProperty BarTextColorProperty = BindableProperty.Create("BarTextColor", typeof(Color), typeof(NavigationPage), Color.Default);
+
+ public static readonly BindableProperty TitleIconProperty = BindableProperty.CreateAttached("TitleIcon", typeof(FileImageSource), typeof(NavigationPage), default(FileImageSource));
+
+ static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null);
+ public static readonly BindableProperty CurrentPageProperty = CurrentPagePropertyKey.BindableProperty;
+
+ public NavigationPage()
+ {
+ Navigation = new NavigationImpl(this);
+ }
+
+ public NavigationPage(Page root) : this()
+ {
+ PushPage(root);
+ }
+
+ public Color BarBackgroundColor
+ {
+ get { return (Color)GetValue(BarBackgroundColorProperty); }
+ set { SetValue(BarBackgroundColorProperty, value); }
+ }
+
+ public Color BarTextColor
+ {
+ get { return (Color)GetValue(BarTextColorProperty); }
+ set { SetValue(BarTextColorProperty, value); }
+ }
+
+ [Obsolete("Use BarBackgroundColor and BarTextColor to change NavigationPage bar color properties")]
+ public Color Tint
+ {
+ get { return (Color)GetValue(TintProperty); }
+ set { SetValue(TintProperty, value); }
+ }
+
+ internal Task CurrentNavigationTask { get; set; }
+
+ internal Stack<Page> StackCopy
+ {
+ get
+ {
+ var result = new Stack<Page>(InternalChildren.Count);
+ foreach (Page page in InternalChildren)
+ result.Push(page);
+ return result;
+ }
+ }
+
+ internal int StackDepth
+ {
+ get { return InternalChildren.Count; }
+ }
+
+ public Page CurrentPage
+ {
+ get { return (Page)GetValue(CurrentPageProperty); }
+ private set { SetValue(CurrentPagePropertyKey, value); }
+ }
+
+ public static string GetBackButtonTitle(BindableObject page)
+ {
+ return (string)page.GetValue(BackButtonTitleProperty);
+ }
+
+ public static bool GetHasBackButton(Page page)
+ {
+ if (page == null)
+ throw new ArgumentNullException("page");
+ return (bool)page.GetValue(HasBackButtonProperty);
+ }
+
+ public static bool GetHasNavigationBar(BindableObject page)
+ {
+ return (bool)page.GetValue(HasNavigationBarProperty);
+ }
+
+ public static FileImageSource GetTitleIcon(BindableObject bindable)
+ {
+ return (FileImageSource)bindable.GetValue(TitleIconProperty);
+ }
+
+ public Task<Page> PopAsync()
+ {
+ return PopAsync(true);
+ }
+
+ public async Task<Page> PopAsync(bool animated)
+ {
+ if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+ Task oldTask = CurrentNavigationTask;
+ CurrentNavigationTask = tcs.Task;
+ await oldTask;
+
+ Page page = await PopAsyncInner(animated);
+ tcs.SetResult(true);
+ return page;
+ }
+
+ Task<Page> result = PopAsyncInner(animated);
+ CurrentNavigationTask = result;
+ return await result;
+ }
+
+ public event EventHandler<NavigationEventArgs> Popped;
+
+ public event EventHandler<NavigationEventArgs> PoppedToRoot;
+
+ public Task PopToRootAsync()
+ {
+ return PopToRootAsync(true);
+ }
+
+ public async Task PopToRootAsync(bool animated)
+ {
+ if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+ Task oldTask = CurrentNavigationTask;
+ CurrentNavigationTask = tcs.Task;
+ await oldTask;
+
+ await PopToRootAsyncInner(animated);
+ tcs.SetResult(true);
+ return;
+ }
+
+ Task result = PopToRootAsyncInner(animated);
+ CurrentNavigationTask = result;
+ await result;
+ }
+
+ public Task PushAsync(Page page)
+ {
+ return PushAsync(page, true);
+ }
+
+ public async Task PushAsync(Page page, bool animated)
+ {
+ if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+ Task oldTask = CurrentNavigationTask;
+ CurrentNavigationTask = tcs.Task;
+ await oldTask;
+
+ await PushAsyncInner(page, animated);
+ tcs.SetResult(true);
+ return;
+ }
+
+ CurrentNavigationTask = PushAsyncInner(page, animated);
+ await CurrentNavigationTask;
+ }
+
+ public event EventHandler<NavigationEventArgs> Pushed;
+
+ public static void SetBackButtonTitle(BindableObject page, string value)
+ {
+ page.SetValue(BackButtonTitleProperty, value);
+ }
+
+ public static void SetHasBackButton(Page page, bool value)
+ {
+ if (page == null)
+ throw new ArgumentNullException("page");
+ page.SetValue(HasBackButtonProperty, value);
+ }
+
+ public static void SetHasNavigationBar(BindableObject page, bool value)
+ {
+ page.SetValue(HasNavigationBarProperty, value);
+ }
+
+ public static void SetTitleIcon(BindableObject bindable, FileImageSource value)
+ {
+ bindable.SetValue(TitleIconProperty, value);
+ }
+
+ protected override bool OnBackButtonPressed()
+ {
+ if (CurrentPage.SendBackButtonPressed())
+ return true;
+
+ if (StackDepth > 1)
+ {
+ SafePop();
+ return true;
+ }
+
+ return base.OnBackButtonPressed();
+ }
+
+ internal event EventHandler<NavigationRequestedEventArgs> InsertPageBeforeRequested;
+
+ internal async Task<Page> PopAsyncInner(bool animated, bool fast = false)
+ {
+ if (StackDepth == 1)
+ {
+ return null;
+ }
+
+ var page = (Page)InternalChildren.Last();
+
+ var args = new NavigationRequestedEventArgs(page, animated);
+
+ var removed = true;
+
+ EventHandler<NavigationRequestedEventArgs> requestPop = PopRequested;
+ if (requestPop != null)
+ {
+ requestPop(this, args);
+
+ if (args.Task != null && !fast)
+ removed = await args.Task;
+ }
+
+ if (!removed && !fast)
+ return CurrentPage;
+
+ InternalChildren.Remove(page);
+
+ CurrentPage = (Page)InternalChildren.Last();
+
+ if (Popped != null)
+ Popped(this, args);
+
+ return page;
+ }
+
+ internal event EventHandler<NavigationRequestedEventArgs> PopRequested;
+
+ internal event EventHandler<NavigationRequestedEventArgs> PopToRootRequested;
+
+ internal event EventHandler<NavigationRequestedEventArgs> PushRequested;
+
+ internal event EventHandler<NavigationRequestedEventArgs> RemovePageRequested;
+
+ void InsertPageBefore(Page page, Page before)
+ {
+ if (!InternalChildren.Contains(before))
+ throw new ArgumentException("before must be a child of the NavigationPage", "before");
+
+ if (InternalChildren.Contains(page))
+ throw new ArgumentException("Cannot insert page which is already in the navigation stack");
+
+ EventHandler<NavigationRequestedEventArgs> handler = InsertPageBeforeRequested;
+ if (handler != null)
+ handler(this, new NavigationRequestedEventArgs(page, before, false));
+
+ int index = InternalChildren.IndexOf(before);
+ InternalChildren.Insert(index, page);
+
+ // Shouldn't be required?
+ if (Width > 0 && Height > 0)
+ ForceLayout();
+ }
+
+ async Task PopToRootAsyncInner(bool animated)
+ {
+ if (StackDepth == 1)
+ return;
+
+ var root = (Page)InternalChildren.First();
+
+ InternalChildren.ToArray().Where(c => c != root).ForEach(c => InternalChildren.Remove(c));
+
+ CurrentPage = root;
+
+ var args = new NavigationRequestedEventArgs(root, animated);
+
+ EventHandler<NavigationRequestedEventArgs> requestPopToRoot = PopToRootRequested;
+ if (requestPopToRoot != null)
+ {
+ requestPopToRoot(this, args);
+
+ if (args.Task != null)
+ await args.Task;
+ }
+
+ if (PoppedToRoot != null)
+ PoppedToRoot(this, new NavigationEventArgs(root));
+ }
+
+ async Task PushAsyncInner(Page page, bool animated)
+ {
+ if (InternalChildren.Contains(page))
+ return;
+
+ PushPage(page);
+
+ var args = new NavigationRequestedEventArgs(page, animated);
+
+ EventHandler<NavigationRequestedEventArgs> requestPush = PushRequested;
+ if (requestPush != null)
+ {
+ requestPush(this, args);
+
+ if (args.Task != null)
+ await args.Task;
+ }
+
+ if (Pushed != null)
+ Pushed(this, args);
+ }
+
+ void PushPage(Page page)
+ {
+ InternalChildren.Add(page);
+
+ CurrentPage = page;
+ }
+
+ void RemovePage(Page page)
+ {
+ if (page == CurrentPage && StackDepth <= 1)
+ throw new InvalidOperationException("Cannot remove root page when it is also the currently displayed page.");
+ if (page == CurrentPage)
+ {
+ Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider called PopAsync instead.");
+ PopAsync();
+ return;
+ }
+
+ if (!InternalChildren.Contains(page))
+ throw new ArgumentException("Page to remove must be contained on this Navigation Page");
+
+ EventHandler<NavigationRequestedEventArgs> handler = RemovePageRequested;
+ if (handler != null)
+ handler(this, new NavigationRequestedEventArgs(page, true));
+
+ InternalChildren.Remove(page);
+ }
+
+ void SafePop()
+ {
+ PopAsync(true).ContinueWith(t =>
+ {
+ if (t.IsFaulted)
+ throw t.Exception;
+ });
+ }
+
+ class NavigationImpl : NavigationProxy
+ {
+ readonly Lazy<ReadOnlyCastingList<Page, Element>> _castingList;
+
+ public NavigationImpl(NavigationPage owner)
+ {
+ Owner = owner;
+ _castingList = new Lazy<ReadOnlyCastingList<Page, Element>>(() => new ReadOnlyCastingList<Page, Element>(Owner.InternalChildren));
+ }
+
+ NavigationPage Owner { get; }
+
+ protected override IReadOnlyList<Page> GetNavigationStack()
+ {
+ return _castingList.Value;
+ }
+
+ protected override void OnInsertPageBefore(Page page, Page before)
+ {
+ Owner.InsertPageBefore(page, before);
+ }
+
+ protected override Task<Page> OnPopAsync(bool animated)
+ {
+ return Owner.PopAsync(animated);
+ }
+
+ protected override Task OnPopToRootAsync(bool animated)
+ {
+ return Owner.PopToRootAsync(animated);
+ }
+
+ protected override Task OnPushAsync(Page root, bool animated)
+ {
+ return Owner.PushAsync(root, animated);
+ }
+
+ protected override void OnRemovePage(Page page)
+ {
+ Owner.RemovePage(page);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationProxy.cs b/Xamarin.Forms.Core/NavigationProxy.cs
new file mode 100644
index 00000000..65e2fee9
--- /dev/null
+++ b/Xamarin.Forms.Core/NavigationProxy.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal class NavigationProxy : INavigation
+ {
+ INavigation _inner;
+ Lazy<List<Page>> _modalStack = new Lazy<List<Page>>(() => new List<Page>());
+
+ Lazy<List<Page>> _pushStack = new Lazy<List<Page>>(() => new List<Page>());
+
+ public INavigation Inner
+ {
+ get { return _inner; }
+ set
+ {
+ if (_inner == value)
+ return;
+ _inner = value;
+ // reverse so that things go into the new stack in the same order
+ // null out to release memory that will likely never be needed again
+
+ if (ReferenceEquals(_inner, null))
+ {
+ _pushStack = new Lazy<List<Page>>(() => new List<Page>());
+ _modalStack = new Lazy<List<Page>>(() => new List<Page>());
+ }
+ else
+ {
+ if (_pushStack != null && _pushStack.IsValueCreated)
+ {
+ foreach (Page page in _pushStack.Value)
+ {
+ _inner.PushAsync(page);
+ }
+ }
+
+ if (_modalStack != null && _modalStack.IsValueCreated)
+ {
+ foreach (Page page in _modalStack.Value)
+ {
+ _inner.PushModalAsync(page);
+ }
+ }
+
+ _pushStack = null;
+ _modalStack = null;
+ }
+ }
+ }
+
+ public void InsertPageBefore(Page page, Page before)
+ {
+ OnInsertPageBefore(page, before);
+ }
+
+ public IReadOnlyList<Page> ModalStack
+ {
+ get { return GetModalStack(); }
+ }
+
+ public IReadOnlyList<Page> NavigationStack
+ {
+ get { return GetNavigationStack(); }
+ }
+
+ public Task<Page> PopAsync()
+ {
+ return OnPopAsync(true);
+ }
+
+ public Task<Page> PopAsync(bool animated)
+ {
+ return OnPopAsync(animated);
+ }
+
+ public Task<Page> PopModalAsync()
+ {
+ return OnPopModal(true);
+ }
+
+ public Task<Page> PopModalAsync(bool animated)
+ {
+ return OnPopModal(animated);
+ }
+
+ public Task PopToRootAsync()
+ {
+ return OnPopToRootAsync(true);
+ }
+
+ public Task PopToRootAsync(bool animated)
+ {
+ return OnPopToRootAsync(animated);
+ }
+
+ public Task PushAsync(Page root)
+ {
+ return PushAsync(root, true);
+ }
+
+ public Task PushAsync(Page root, bool animated)
+ {
+ if (root.RealParent != null)
+ throw new InvalidOperationException("Page must not already have a parent.");
+ return OnPushAsync(root, animated);
+ }
+
+ public Task PushModalAsync(Page modal)
+ {
+ return PushModalAsync(modal, true);
+ }
+
+ public Task PushModalAsync(Page modal, bool animated)
+ {
+ if (modal.RealParent != null)
+ throw new InvalidOperationException("Page must not already have a parent.");
+ return OnPushModal(modal, animated);
+ }
+
+ public void RemovePage(Page page)
+ {
+ OnRemovePage(page);
+ }
+
+ protected virtual IReadOnlyList<Page> GetModalStack()
+ {
+ INavigation currentInner = Inner;
+ return currentInner == null ? _modalStack.Value : currentInner.ModalStack;
+ }
+
+ protected virtual IReadOnlyList<Page> GetNavigationStack()
+ {
+ INavigation currentInner = Inner;
+ return currentInner == null ? _pushStack.Value : currentInner.NavigationStack;
+ }
+
+ protected virtual void OnInsertPageBefore(Page page, Page before)
+ {
+ INavigation currentInner = Inner;
+ if (currentInner == null)
+ {
+ int index = _pushStack.Value.IndexOf(before);
+ if (index == -1)
+ throw new ArgumentException("before must be in the pushed stack of the current context");
+ _pushStack.Value.Insert(index, page);
+ }
+ else
+ {
+ currentInner.InsertPageBefore(page, before);
+ }
+ }
+
+ protected virtual Task<Page> OnPopAsync(bool animated)
+ {
+ INavigation inner = Inner;
+ return inner == null ? Task.FromResult(Pop()) : inner.PopAsync(animated);
+ }
+
+ protected virtual Task<Page> OnPopModal(bool animated)
+ {
+ INavigation innerNav = Inner;
+ return innerNav == null ? Task.FromResult(PopModal()) : innerNav.PopModalAsync(animated);
+ }
+
+ protected virtual Task OnPopToRootAsync(bool animated)
+ {
+ INavigation currentInner = Inner;
+ if (currentInner == null)
+ {
+ Page root = _pushStack.Value.Last();
+ _pushStack.Value.Clear();
+ _pushStack.Value.Add(root);
+ return Task.FromResult(root);
+ }
+ return currentInner.PopToRootAsync(animated);
+ }
+
+ protected virtual Task OnPushAsync(Page page, bool animated)
+ {
+ INavigation currentInner = Inner;
+ if (currentInner == null)
+ {
+ _pushStack.Value.Add(page);
+ return Task.FromResult(page);
+ }
+ return currentInner.PushAsync(page, animated);
+ }
+
+ protected virtual Task OnPushModal(Page modal, bool animated)
+ {
+ INavigation currentInner = Inner;
+ if (currentInner == null)
+ {
+ _modalStack.Value.Add(modal);
+ return Task.FromResult<object>(null);
+ }
+ return currentInner.PushModalAsync(modal, animated);
+ }
+
+ protected virtual void OnRemovePage(Page page)
+ {
+ INavigation currentInner = Inner;
+ if (currentInner == null)
+ {
+ _pushStack.Value.Remove(page);
+ }
+ else
+ {
+ currentInner.RemovePage(page);
+ }
+ }
+
+ Page Pop()
+ {
+ List<Page> list = _pushStack.Value;
+ Page result = list[list.Count - 1];
+ list.RemoveAt(list.Count - 1);
+ return result;
+ }
+
+ Page PopModal()
+ {
+ List<Page> list = _modalStack.Value;
+ Page result = list[list.Count - 1];
+ list.RemoveAt(list.Count - 1);
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationRequestedEventArgs.cs b/Xamarin.Forms.Core/NavigationRequestedEventArgs.cs
new file mode 100644
index 00000000..b21cffce
--- /dev/null
+++ b/Xamarin.Forms.Core/NavigationRequestedEventArgs.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal class NavigationRequestedEventArgs : NavigationEventArgs
+ {
+ public NavigationRequestedEventArgs(Page page, bool animated, bool realize = true) : base(page)
+ {
+ Animated = animated;
+ Realize = realize;
+ }
+
+ public NavigationRequestedEventArgs(Page page, Page before, bool animated) : this(page, animated)
+ {
+ BeforePage = before;
+ }
+
+ public bool Animated { get; set; }
+
+ public Page BeforePage { get; set; }
+
+ public bool Realize { get; set; }
+
+ public Task<bool> Task { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs
new file mode 100644
index 00000000..bb6a2b84
--- /dev/null
+++ b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsEx.cs
@@ -0,0 +1,65 @@
+using System.Collections;
+using System.Collections.Specialized;
+
+namespace Xamarin.Forms
+{
+ internal class NotifyCollectionChangedEventArgsEx : NotifyCollectionChangedEventArgs
+ {
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action) : base(action)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList changedItems) : base(action, changedItems)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList newItems, IList oldItems) : base(action, newItems, oldItems)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex) : base(action, newItems, oldItems, startingIndex)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList changedItems, int startingIndex) : base(action, changedItems, startingIndex)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, IList changedItems, int index, int oldIndex) : base(action, changedItems, index, oldIndex)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object changedItem) : base(action, changedItem)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object changedItem, int index) : base(action, changedItem, index)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object changedItem, int index, int oldIndex) : base(action, changedItem, index, oldIndex)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object newItem, object oldItem) : base(action, newItem, oldItem)
+ {
+ Count = count;
+ }
+
+ public NotifyCollectionChangedEventArgsEx(int count, NotifyCollectionChangedAction action, object newItem, object oldItem, int index) : base(action, newItem, oldItem, index)
+ {
+ Count = count;
+ }
+
+ public int Count { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs
new file mode 100644
index 00000000..2ddf92c4
--- /dev/null
+++ b/Xamarin.Forms.Core/NotifyCollectionChangedEventArgsExtensions.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Xamarin.Forms
+{
+ internal static class NotifyCollectionChangedEventArgsExtensions
+ {
+ public static void Apply<TFrom>(this NotifyCollectionChangedEventArgs self, IList<TFrom> from, IList<object> to)
+ {
+ self.Apply((o, i, b) => to.Insert(i, o), (o, i) => to.RemoveAt(i), () =>
+ {
+ to.Clear();
+ for (var i = 0; i < from.Count; i++)
+ to.Add(from[i]);
+ });
+ }
+
+ public static NotifyCollectionChangedAction Apply(this NotifyCollectionChangedEventArgs self, Action<object, int, bool> insert, Action<object, int> removeAt, Action reset)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+ if (reset == null)
+ throw new ArgumentNullException("reset");
+ if (insert == null)
+ throw new ArgumentNullException("insert");
+ if (removeAt == null)
+ throw new ArgumentNullException("removeAt");
+
+ switch (self.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (self.NewStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (var i = 0; i < self.NewItems.Count; i++)
+ insert(self.NewItems[i], i + self.NewStartingIndex, true);
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ if (self.NewStartingIndex < 0 || self.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (var i = 0; i < self.OldItems.Count; i++)
+ removeAt(self.OldItems[i], self.OldStartingIndex);
+
+ int insertIndex = self.NewStartingIndex;
+ if (self.OldStartingIndex < self.NewStartingIndex)
+ insertIndex -= self.OldItems.Count - 1;
+
+ for (var i = 0; i < self.OldItems.Count; i++)
+ insert(self.OldItems[i], insertIndex + i, false);
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (self.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (var i = 0; i < self.OldItems.Count; i++)
+ removeAt(self.OldItems[i], self.OldStartingIndex);
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ if (self.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (var i = 0; i < self.OldItems.Count; i++)
+ {
+ removeAt(self.OldItems[i], i + self.OldStartingIndex);
+ insert(self.OldItems[i], i + self.OldStartingIndex, true);
+ }
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ reset();
+ return NotifyCollectionChangedAction.Reset;
+ }
+
+ return self.Action;
+ }
+
+ public static NotifyCollectionChangedEventArgsEx WithCount(this NotifyCollectionChangedEventArgs e, int count)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Add, e.NewItems, e.NewStartingIndex);
+
+ case NotifyCollectionChangedAction.Remove:
+ return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Remove, e.OldItems, e.OldStartingIndex);
+
+ case NotifyCollectionChangedAction.Move:
+ return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Move, e.OldItems, e.NewStartingIndex, e.OldStartingIndex);
+
+ case NotifyCollectionChangedAction.Replace:
+ return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Replace, e.NewItems, e.OldItems, e.OldStartingIndex);
+
+ default:
+ return new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Reset);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NullEffect.cs b/Xamarin.Forms.Core/NullEffect.cs
new file mode 100644
index 00000000..509405d7
--- /dev/null
+++ b/Xamarin.Forms.Core/NullEffect.cs
@@ -0,0 +1,13 @@
+namespace Xamarin.Forms
+{
+ internal class NullEffect : Effect
+ {
+ protected override void OnAttached()
+ {
+ }
+
+ protected override void OnDetached()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NumericExtensions.cs b/Xamarin.Forms.Core/NumericExtensions.cs
new file mode 100644
index 00000000..6333c360
--- /dev/null
+++ b/Xamarin.Forms.Core/NumericExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal static class NumericExtensions
+ {
+ public static double Clamp(this double self, double min, double max)
+ {
+ return Math.Min(max, Math.Max(self, min));
+ }
+
+ public static int Clamp(this int self, int min, int max)
+ {
+ return Math.Min(max, Math.Max(self, min));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/NumericKeyboard.cs b/Xamarin.Forms.Core/NumericKeyboard.cs
new file mode 100644
index 00000000..08096bde
--- /dev/null
+++ b/Xamarin.Forms.Core/NumericKeyboard.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal sealed class NumericKeyboard : Keyboard
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ObservableList.cs b/Xamarin.Forms.Core/ObservableList.cs
new file mode 100644
index 00000000..82a6c493
--- /dev/null
+++ b/Xamarin.Forms.Core/ObservableList.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal class ObservableList<T> : ObservableCollection<T>
+ {
+ // There's lots of special-casing optimizations that could be done here
+ // but right now this is only being used for tests.
+
+ public void AddRange(IEnumerable<T> range)
+ {
+ if (range == null)
+ throw new ArgumentNullException("range");
+
+ List<T> items = range.ToList();
+ int index = Items.Count;
+ foreach (T item in items)
+ Items.Add(item);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items, index));
+ }
+
+ public void InsertRange(int index, IEnumerable<T> range)
+ {
+ if (index < 0 || index > Count)
+ throw new ArgumentOutOfRangeException("index");
+ if (range == null)
+ throw new ArgumentNullException("range");
+
+ int originalIndex = index;
+
+ List<T> items = range.ToList();
+ foreach (T item in items)
+ Items.Insert(index++, item);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items, originalIndex));
+ }
+
+ public void Move(int oldIndex, int newIndex, int count)
+ {
+ if (oldIndex < 0 || oldIndex + count > Count)
+ throw new ArgumentOutOfRangeException("oldIndex");
+ if (newIndex < 0 || newIndex + count > Count)
+ throw new ArgumentOutOfRangeException("newIndex");
+
+ var items = new List<T>(count);
+ for (var i = 0; i < count; i++)
+ {
+ T item = Items[oldIndex];
+ items.Add(item);
+ Items.RemoveAt(oldIndex);
+ }
+
+ int index = newIndex;
+ if (newIndex > oldIndex)
+ index -= items.Count - 1;
+
+ for (var i = 0; i < items.Count; i++)
+ Items.Insert(index + i, items[i]);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, items, newIndex, oldIndex));
+ }
+
+ public void RemoveAt(int index, int count)
+ {
+ if (index < 0 || index + count > Count)
+ throw new ArgumentOutOfRangeException("index");
+
+ T[] items = Items.Skip(index).Take(count).ToArray();
+ for (int i = index; i < count; i++)
+ Items.RemoveAt(i);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, index));
+ }
+
+ public void RemoveRange(IEnumerable<T> range)
+ {
+ if (range == null)
+ throw new ArgumentNullException("range");
+
+ List<T> items = range.ToList();
+ foreach (T item in items)
+ Items.Remove(item);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items));
+ }
+
+ public void ReplaceRange(int startIndex, IEnumerable<T> items)
+ {
+ if (items == null)
+ throw new ArgumentNullException("items");
+
+ T[] ritems = items.ToArray();
+
+ if (startIndex < 0 || startIndex + ritems.Length > Count)
+ throw new ArgumentOutOfRangeException("startIndex");
+
+ var oldItems = new T[ritems.Length];
+ for (var i = 0; i < ritems.Length; i++)
+ {
+ oldItems[i] = Items[i + startIndex];
+ Items[i + startIndex] = ritems[i];
+ }
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, ritems, oldItems, startIndex));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ObservableWrapper.cs b/Xamarin.Forms.Core/ObservableWrapper.cs
new file mode 100644
index 00000000..e8fc0c5b
--- /dev/null
+++ b/Xamarin.Forms.Core/ObservableWrapper.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal class ObservableWrapper<TTrack, TRestrict> : IList<TRestrict>, INotifyCollectionChanged where TTrack : Element where TRestrict : TTrack
+ {
+ readonly ObservableCollection<TTrack> _list;
+
+ public ObservableWrapper(ObservableCollection<TTrack> list)
+ {
+ if (list == null)
+ throw new ArgumentNullException("list");
+
+ _list = list;
+
+ list.CollectionChanged += ListOnCollectionChanged;
+ }
+
+ public void Add(TRestrict item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+ if (IsReadOnly)
+ throw new NotSupportedException("The collection is read-only.");
+
+ if (_list.Contains(item))
+ return;
+
+ item.Owned = true;
+ _list.Add(item);
+ }
+
+ public void Clear()
+ {
+ if (IsReadOnly)
+ throw new NotSupportedException("The collection is read-only.");
+
+ foreach (TRestrict item in _list.OfType<TRestrict>().ToArray())
+ {
+ _list.Remove(item);
+ item.Owned = false;
+ }
+ }
+
+ public bool Contains(TRestrict item)
+ {
+ return item.Owned && _list.Contains(item);
+ }
+
+ public void CopyTo(TRestrict[] array, int destIndex)
+ {
+ if (array.Length - destIndex < Count)
+ throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
+ foreach (TRestrict item in this)
+ {
+ array[destIndex] = item;
+ destIndex++;
+ }
+ }
+
+ public int Count
+ {
+ get { return _list.Where(i => i.Owned).OfType<TRestrict>().Count(); }
+ }
+
+ public bool IsReadOnly { get; internal set; }
+
+ public bool Remove(TRestrict item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+ if (IsReadOnly)
+ throw new NotSupportedException("The collection is read-only.");
+
+ if (!item.Owned)
+ return false;
+
+ if (_list.Remove(item))
+ {
+ item.Owned = false;
+ return true;
+ }
+ return false;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<TRestrict> GetEnumerator()
+ {
+ return _list.Where(i => i.Owned).OfType<TRestrict>().GetEnumerator();
+ }
+
+ public int IndexOf(TRestrict value)
+ {
+ int innerIndex = _list.IndexOf(value);
+ if (innerIndex == -1)
+ return -1;
+ return ToOuterIndex(innerIndex);
+ }
+
+ public void Insert(int index, TRestrict item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+ if (IsReadOnly)
+ throw new NotSupportedException("The collection is read-only.");
+
+ item.Owned = true;
+ _list.Insert(ToInnerIndex(index), item);
+ }
+
+ public TRestrict this[int index]
+ {
+ get { return (TRestrict)_list[ToInnerIndex(index)]; }
+ set
+ {
+ int innerIndex = ToInnerIndex(index);
+ if (value != null)
+ value.Owned = true;
+ TTrack old = _list[innerIndex];
+ _list[innerIndex] = value;
+
+ if (old != null)
+ old.Owned = false;
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ if (IsReadOnly)
+ throw new NotSupportedException("The collection is read-only");
+ int innerIndex = ToInnerIndex(index);
+ TTrack item = _list[innerIndex];
+ if (item.Owned)
+ {
+ _list.RemoveAt(innerIndex);
+ item.Owned = false;
+ }
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ void ListOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler handler = CollectionChanged;
+ if (handler == null)
+ return;
+
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex == -1 || e.NewItems.Count > 1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ var newItem = e.NewItems[0] as TRestrict;
+ if (newItem == null || !newItem.Owned)
+ break;
+
+ int outerIndex = ToOuterIndex(e.NewStartingIndex);
+ handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.NewItems, outerIndex));
+ break;
+ case NotifyCollectionChangedAction.Move:
+ if (e.NewStartingIndex == -1 || e.OldStartingIndex == -1 || e.NewItems.Count > 1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ var movedItem = e.NewItems[0] as TRestrict;
+ if (movedItem == null || !movedItem.Owned)
+ break;
+
+ int outerOldIndex = ToOuterIndex(e.OldStartingIndex);
+ int outerNewIndex = ToOuterIndex(e.NewStartingIndex);
+ handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, e.NewItems, outerNewIndex, outerOldIndex));
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex == -1 || e.OldItems.Count > 1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ var removedItem = e.OldItems[0] as TRestrict;
+ if (removedItem == null || !removedItem.Owned)
+ break;
+
+ int outerRemovedIndex = ToOuterIndex(e.OldStartingIndex);
+ var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItem, outerRemovedIndex);
+ handler(this, args);
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ if (e.NewStartingIndex == -1 || e.OldStartingIndex == -1 || e.NewItems.Count > 1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ var newReplaceItem = e.NewItems[0] as TRestrict;
+ var oldReplaceItem = e.OldItems[0] as TRestrict;
+
+ if ((newReplaceItem == null || !newReplaceItem.Owned) && (oldReplaceItem == null || !oldReplaceItem.Owned))
+ {
+ break;
+ }
+ if (newReplaceItem == null || !newReplaceItem.Owned || oldReplaceItem == null || !oldReplaceItem.Owned)
+ {
+ goto case NotifyCollectionChangedAction.Reset;
+ }
+
+ int index = ToOuterIndex(e.NewStartingIndex);
+
+ var replaceArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newReplaceItem, oldReplaceItem, index);
+ handler(this, replaceArgs);
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ int ToInnerIndex(int outterIndex)
+ {
+ var outerIndex = 0;
+ int innerIndex;
+ for (innerIndex = 0; innerIndex < _list.Count; innerIndex++)
+ {
+ TTrack item = _list[innerIndex];
+ if (item is TRestrict && item.Owned)
+ {
+ if (outerIndex == outterIndex)
+ return innerIndex;
+ outerIndex++;
+ }
+ }
+
+ return innerIndex;
+ }
+
+ int ToOuterIndex(int innerIndex)
+ {
+ var outerIndex = 0;
+ for (var index = 0; index < innerIndex; index++)
+ {
+ TTrack item = _list[index];
+ if (item is TRestrict && item.Owned)
+ {
+ outerIndex++;
+ }
+ }
+
+ return outerIndex;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/OnIdiom.cs b/Xamarin.Forms.Core/OnIdiom.cs
new file mode 100644
index 00000000..9376d5b9
--- /dev/null
+++ b/Xamarin.Forms.Core/OnIdiom.cs
@@ -0,0 +1,21 @@
+namespace Xamarin.Forms
+{
+ public class OnIdiom<T>
+ {
+ public T Phone { get; set; }
+
+ public T Tablet { get; set; }
+
+ public static implicit operator T(OnIdiom<T> onIdiom)
+ {
+ switch (Device.Idiom)
+ {
+ default:
+ case TargetIdiom.Phone:
+ return onIdiom.Phone;
+ case TargetIdiom.Tablet:
+ return onIdiom.Tablet;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/OnPlatform.cs b/Xamarin.Forms.Core/OnPlatform.cs
new file mode 100644
index 00000000..b66167fa
--- /dev/null
+++ b/Xamarin.Forms.Core/OnPlatform.cs
@@ -0,0 +1,27 @@
+namespace Xamarin.Forms
+{
+ public class OnPlatform<T>
+ {
+ public T Android { get; set; }
+
+ public T iOS { get; set; }
+
+ public T WinPhone { get; set; }
+
+ public static implicit operator T(OnPlatform<T> onPlatform)
+ {
+ switch (Device.OS)
+ {
+ case TargetPlatform.iOS:
+ return onPlatform.iOS;
+ case TargetPlatform.Android:
+ return onPlatform.Android;
+ case TargetPlatform.Windows:
+ case TargetPlatform.WinPhone:
+ return onPlatform.WinPhone;
+ }
+
+ return onPlatform.iOS;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/OpenGLView.cs b/Xamarin.Forms.Core/OpenGLView.cs
new file mode 100644
index 00000000..530a2f0d
--- /dev/null
+++ b/Xamarin.Forms.Core/OpenGLView.cs
@@ -0,0 +1,38 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_OpenGLViewRenderer))]
+ public sealed class OpenGLView : View, IOpenGlViewController
+ {
+ #region Statics
+
+ public static readonly BindableProperty HasRenderLoopProperty = BindableProperty.Create("HasRenderLoop", typeof(bool), typeof(OpenGLView), default(bool));
+
+ #endregion
+
+ public bool HasRenderLoop
+ {
+ get { return (bool)GetValue(HasRenderLoopProperty); }
+ set { SetValue(HasRenderLoopProperty, value); }
+ }
+
+ public Action<Rectangle> OnDisplay { get; set; }
+
+ event EventHandler IOpenGlViewController.DisplayRequested
+ {
+ add { DisplayRequested += value; }
+ remove { DisplayRequested -= value; }
+ }
+
+ public void Display()
+ {
+ EventHandler handler = DisplayRequested;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ event EventHandler DisplayRequested;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/OrderedDictionary.cs b/Xamarin.Forms.Core/OrderedDictionary.cs
new file mode 100644
index 00000000..028979c5
--- /dev/null
+++ b/Xamarin.Forms.Core/OrderedDictionary.cs
@@ -0,0 +1,451 @@
+//
+// OrderedDictionary.cs
+//
+// Author:
+// Eric Maupin <me@ermau.com>
+//
+// Copyright (c) 2009 Eric Maupin (http://www.ermau.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Xamarin.Forms;
+
+namespace Cadenza.Collections
+{
+ internal sealed class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IList<KeyValuePair<TKey, TValue>>
+ {
+ readonly Dictionary<TKey, TValue> _dict;
+ readonly List<TKey> _keyOrder;
+ readonly ICollection<KeyValuePair<TKey, TValue>> _kvpCollection;
+ readonly ReadOnlyCollection<TKey> _roKeys;
+
+ readonly ReadOnlyValueCollection _roValues;
+
+ public OrderedDictionary() : this(0)
+ {
+ }
+
+ public OrderedDictionary(int capacity) : this(capacity, EqualityComparer<TKey>.Default)
+ {
+ }
+
+ public OrderedDictionary(IEqualityComparer<TKey> equalityComparer) : this(0, equalityComparer)
+ {
+ }
+
+ public OrderedDictionary(int capacity, IEqualityComparer<TKey> equalityComparer)
+ {
+ _dict = new Dictionary<TKey, TValue>(capacity, equalityComparer);
+ _kvpCollection = _dict;
+ _keyOrder = new List<TKey>(capacity);
+ _roKeys = new ReadOnlyCollection<TKey>(_keyOrder);
+ _roValues = new ReadOnlyValueCollection(this);
+ }
+
+ public OrderedDictionary(ICollection<KeyValuePair<TKey, TValue>> dictionary) : this(dictionary, EqualityComparer<TKey>.Default)
+ {
+ }
+
+ public OrderedDictionary(ICollection<KeyValuePair<TKey, TValue>> dictionary, IEqualityComparer<TKey> equalityComparer) : this(dictionary != null ? dictionary.Count : 0, equalityComparer)
+ {
+ if (dictionary == null)
+ throw new ArgumentNullException("dictionary");
+
+ foreach (KeyValuePair<TKey, TValue> kvp in dictionary)
+ Add(kvp.Key, kvp.Value);
+ }
+
+ /// <summary>
+ /// Gets the equality comparer being used for
+ /// <typeparam name="TKey" />
+ /// .
+ /// </summary>
+ public IEqualityComparer<TKey> Comparer
+ {
+ get { return _dict.Comparer; }
+ }
+
+ /// <summary>
+ /// Gets the value at the specified index.
+ /// </summary>
+ /// <param name="index">The index to get the value at.</param>
+ /// <returns>The value at the specified index.</returns>
+ /// <exception cref="IndexOutOfRangeException">
+ /// <paramref name="index" /> is less than 0 or greater than
+ /// <see cref="Count" />.
+ /// </exception>
+ public TValue this[int index]
+ {
+ get { return _dict[_keyOrder[index]]; }
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ /// <summary>
+ /// Clears the dictionary.
+ /// </summary>
+ public void Clear()
+ {
+ _dict.Clear();
+ _keyOrder.Clear();
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
+ {
+ return _kvpCollection.Contains(item);
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException("array");
+ if (Count > array.Length - arrayIndex)
+ throw new ArgumentException("Not enough space in array to copy");
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException("arrayIndex");
+
+ for (var i = 0; i < _keyOrder.Count; ++i)
+ {
+ TKey key = _keyOrder[i];
+ array[arrayIndex++] = new KeyValuePair<TKey, TValue>(key, _dict[key]);
+ }
+ }
+
+ /// <summary>
+ /// Gets the number of items in the dictionary.
+ /// </summary>
+ public int Count
+ {
+ get { return _dict.Count; }
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
+ {
+ return _kvpCollection.Remove(item) && _keyOrder.Remove(item.Key);
+ }
+
+ /// <summary>
+ /// Adds the <paramref name="key" /> and <paramref name="value" /> to the dictionary.
+ /// </summary>
+ /// <param name="key">The key to associate with the <paramref name="value" />.</param>
+ /// <param name="value">The value to add.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentException"><paramref name="key" /> already exists in the dictionary.</exception>
+ public void Add(TKey key, TValue value)
+ {
+ _dict.Add(key, value);
+ _keyOrder.Add(key);
+ }
+
+ /// <summary>
+ /// Gets whether or not <paramref name="key" /> is in the dictionary.
+ /// </summary>
+ /// <param name="key">The key to look for.</param>
+ /// <returns><c>true</c> if the key was found, <c>false</c> if not.</returns>
+ /// <exception cref="ArgumentNullException">If <paramref name="key" /> is <c>null</c>.</exception>
+ public bool ContainsKey(TKey key)
+ {
+ return _dict.ContainsKey(key);
+ }
+
+ /// <summary>
+ /// Gets or sets the value associated with <paramref name="key" />.
+ /// </summary>
+ /// <param name="key">The key to get or set the value for.</param>
+ /// <returns>The value associated with the key.</returns>
+ /// <exception cref="KeyNotFoundException"><paramref name="key" /> was not found attempting to get.</exception>
+ public TValue this[TKey key]
+ {
+ get { return _dict[key]; }
+ set
+ {
+ if (!_dict.ContainsKey(key))
+ _keyOrder.Add(key);
+
+ _dict[key] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets a read only collection of keys in the dictionary.
+ /// </summary>
+ public ICollection<TKey> Keys
+ {
+ get { return _roKeys; }
+ }
+
+ /// <summary>
+ /// Removes the key and associated value from the dictionary if found.
+ /// </summary>
+ /// <param name="key">The key to remove.</param>
+ /// <returns><c>true</c> if the key was found, <c>false</c> if not.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
+ public bool Remove(TKey key)
+ {
+ return _dict.Remove(key) && _keyOrder.Remove(key);
+ }
+
+ /// <summary>
+ /// Attempts to get the <paramref name="value" /> for the <paramref name="key" />.
+ /// </summary>
+ /// <param name="key">The key to search for.</param>
+ /// <param name="value">The value, if found.</param>
+ /// <returns><c>true</c> if the key was found, <c>false</c> otherwise.</returns>
+ /// <exception cref="ArgumentNullException">If <paramref name="key" /> is <c>null</c>.</exception>
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return _dict.TryGetValue(key, out value);
+ }
+
+ /// <summary>
+ /// Gets a read only collection of values in the dictionary.
+ /// </summary>
+ public ICollection<TValue> Values
+ {
+ get { return _roValues; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+ {
+ foreach (TKey key in _keyOrder)
+ yield return new KeyValuePair<TKey, TValue>(key, this[key]);
+ }
+
+ int IList<KeyValuePair<TKey, TValue>>.IndexOf(KeyValuePair<TKey, TValue> item)
+ {
+ return _keyOrder.IndexOf(item.Key);
+ }
+
+ void IList<KeyValuePair<TKey, TValue>>.Insert(int index, KeyValuePair<TKey, TValue> item)
+ {
+ Insert(index, item.Key, item.Value);
+ }
+
+ KeyValuePair<TKey, TValue> IList<KeyValuePair<TKey, TValue>>.this[int index]
+ {
+ get { return new KeyValuePair<TKey, TValue>(_keyOrder[index], this[index]); }
+ set
+ {
+ _keyOrder[index] = value.Key;
+ _dict[value.Key] = value.Value;
+ }
+ }
+
+ /// <summary>
+ /// Removes they key and associated value from the dictionary located at <paramref name="index" />.
+ /// </summary>
+ /// <param name="index">The index at which to remove an item.</param>
+ public void RemoveAt(int index)
+ {
+ TKey key = _keyOrder[index];
+ Remove(key);
+ }
+
+ /// <summary>
+ /// Gets whether or not <paramref name="value" /> is in the dictionary.
+ /// </summary>
+ /// <param name="value">The value to look for.</param>
+ /// <returns><c>true</c> if the value was found, <c>false</c> if not.</returns>
+ public bool ContainsValue(TValue value)
+ {
+ return _dict.ContainsValue(value);
+ }
+
+ /// <summary>
+ /// Gets the index of <paramref name="key" />.
+ /// </summary>
+ /// <param name="key">The key to find the index of.</param>
+ /// <returns>-1 if the key was not found, the index otherwise.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
+ public int IndexOf(TKey key)
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ return _keyOrder.IndexOf(key);
+ }
+
+ /// <summary>
+ /// Gets the index of <paramref name="key" /> starting with <paramref name="startIndex" />.
+ /// </summary>
+ /// <param name="key">The key to find the index of.</param>
+ /// <param name="startIndex">The index to start the search at.</param>
+ /// <returns>-1 if the key was not found, the index otherwise.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex" /> is not within the valid range of indexes.</exception>
+ public int IndexOf(TKey key, int startIndex)
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ return _keyOrder.IndexOf(key, startIndex);
+ }
+
+ /// <summary>
+ /// Gets the index of <paramref name="key" /> between the range given by <paramref name="startIndex" /> and
+ /// <paramref name="count" />.
+ /// </summary>
+ /// <param name="key">The key to find the index of.</param>
+ /// <param name="startIndex">The index to start the search at.</param>
+ /// <param name="count">How many items to search, including the <paramref name="startIndex" />.</param>
+ /// <returns>-1 if the key was not found, the index otherwise.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex" /> is not within the valid range of indexes.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="startIndex" /> and <paramref name="count" /> are not a
+ /// valid range.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="count" /> is less than 0.</exception>
+ public int IndexOf(TKey key, int startIndex, int count)
+ {
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ return _keyOrder.IndexOf(key, startIndex, count);
+ }
+
+ /// <summary>
+ /// Inserts the <paramref name="key" /> and <paramref name="value" /> at the specified index.
+ /// </summary>
+ /// <param name="index">The index to insert the key and value at.</param>
+ /// <param name="key">The key to assicate with the <paramref name="value" />.</param>
+ /// <param name="value">The value to insert.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="index" /> is less than 0 or greater than
+ /// <see cref="Count" />
+ /// </exception>
+ public void Insert(int index, TKey key, TValue value)
+ {
+ _keyOrder.Insert(index, key);
+ _dict.Add(key, value);
+ }
+
+ public void Move(int oldIndex, int newIndex)
+ {
+ TKey key = _keyOrder[oldIndex];
+ _keyOrder.RemoveAt(oldIndex);
+ _keyOrder.Insert(newIndex, key);
+ }
+
+ class ReadOnlyValueCollection : IList<TValue>
+ {
+ readonly OrderedDictionary<TKey, TValue> _odict;
+
+ public ReadOnlyValueCollection(OrderedDictionary<TKey, TValue> dict)
+ {
+ _odict = dict;
+ }
+
+ public void Add(TValue item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool Contains(TValue item)
+ {
+ return _odict.ContainsValue(item);
+ }
+
+ public void CopyTo(TValue[] array, int arrayIndex)
+ {
+ if (array == null)
+ throw new ArgumentNullException("array");
+ if (Count > array.Length - arrayIndex)
+ throw new ArgumentException("Not enough space in array to copy");
+ if (arrayIndex < 0 || arrayIndex > array.Length)
+ throw new ArgumentOutOfRangeException("arrayIndex");
+
+ for (var i = 0; i < _odict.Count; ++i)
+ array[arrayIndex++] = _odict[i];
+ }
+
+ public int Count
+ {
+ get { return _odict.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return true; }
+ }
+
+ public bool Remove(TValue item)
+ {
+ throw new NotSupportedException();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<TValue> GetEnumerator()
+ {
+ for (var i = 0; i < _odict._keyOrder.Count; ++i)
+ yield return _odict[i];
+ }
+
+ public int IndexOf(TValue item)
+ {
+ return _odict._dict.Values.IndexOf(item);
+ }
+
+ public void Insert(int index, TValue item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public TValue this[int index]
+ {
+ get { return _odict[index]; }
+ set { throw new NotSupportedException(); }
+ }
+
+ public void RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Page.cs b/Xamarin.Forms.Core/Page.cs
new file mode 100644
index 00000000..903f4b53
--- /dev/null
+++ b/Xamarin.Forms.Core/Page.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_PageRenderer))]
+ public class Page : VisualElement, ILayout
+ {
+ internal const string BusySetSignalName = "Xamarin.BusySet";
+
+ internal const string AlertSignalName = "Xamarin.SendAlert";
+
+ internal const string ActionSheetSignalName = "Xamarin.ShowActionSheet";
+
+ internal static readonly BindableProperty IgnoresContainerAreaProperty = BindableProperty.Create("IgnoresContainerArea", typeof(bool), typeof(Page), false);
+
+ public static readonly BindableProperty BackgroundImageProperty = BindableProperty.Create("BackgroundImage", typeof(string), typeof(Page), default(string));
+
+ public static readonly BindableProperty IsBusyProperty = BindableProperty.Create("IsBusy", typeof(bool), typeof(Page), false, propertyChanged: (bo, o, n) => ((Page)bo).OnPageBusyChanged());
+
+ public static readonly BindableProperty PaddingProperty = BindableProperty.Create("Padding", typeof(Thickness), typeof(Page), default(Thickness), propertyChanged: (bindable, old, newValue) =>
+ {
+ var layout = (Page)bindable;
+ layout.UpdateChildrenLayout();
+ });
+
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(Page), null);
+
+ public static readonly BindableProperty IconProperty = BindableProperty.Create("Icon", typeof(FileImageSource), typeof(Page), default(FileImageSource));
+
+ bool _allocatedFlag;
+ Rectangle _containerArea;
+
+ bool _containerAreaSet;
+
+ bool _hasAppeared;
+
+ ReadOnlyCollection<Element> _logicalChildren;
+
+ public Page()
+ {
+ var toolbarItems = new ObservableCollection<ToolbarItem>();
+ toolbarItems.CollectionChanged += OnToolbarItemsCollectionChanged;
+ ToolbarItems = toolbarItems;
+ InternalChildren.CollectionChanged += InternalChildrenOnCollectionChanged;
+ }
+
+ public string BackgroundImage
+ {
+ get { return (string)GetValue(BackgroundImageProperty); }
+ set { SetValue(BackgroundImageProperty, value); }
+ }
+
+ public FileImageSource Icon
+ {
+ get { return (FileImageSource)GetValue(IconProperty); }
+ set { SetValue(IconProperty, value); }
+ }
+
+ public bool IsBusy
+ {
+ get { return (bool)GetValue(IsBusyProperty); }
+ set { SetValue(IsBusyProperty, value); }
+ }
+
+ public Thickness Padding
+ {
+ get { return (Thickness)GetValue(PaddingProperty); }
+ set { SetValue(PaddingProperty, value); }
+ }
+
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set { SetValue(TitleProperty, value); }
+ }
+
+ public IList<ToolbarItem> ToolbarItems { get; internal set; }
+
+ internal Rectangle ContainerArea
+ {
+ get { return _containerArea; }
+ set
+ {
+ if (_containerArea == value)
+ return;
+ _containerAreaSet = true;
+ _containerArea = value;
+ ForceLayout();
+ }
+ }
+
+ internal bool IgnoresContainerArea
+ {
+ get { return (bool)GetValue(IgnoresContainerAreaProperty); }
+ set { SetValue(IgnoresContainerAreaProperty, value); }
+ }
+
+ internal ObservableCollection<Element> InternalChildren { get; } = new ObservableCollection<Element>();
+
+ internal override ReadOnlyCollection<Element> LogicalChildren
+ {
+ get { return _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection<Element>(InternalChildren)); }
+ }
+
+ public event EventHandler LayoutChanged;
+
+ public event EventHandler Appearing;
+
+ public event EventHandler Disappearing;
+
+ public Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons)
+ {
+ var args = new ActionSheetArguments(title, cancel, destruction, buttons);
+ MessagingCenter.Send(this, ActionSheetSignalName, args);
+ return args.Result.Task;
+ }
+
+ public Task DisplayAlert(string title, string message, string cancel)
+ {
+ return DisplayAlert(title, message, null, cancel);
+ }
+
+ public Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
+ {
+ if (string.IsNullOrEmpty(cancel))
+ throw new ArgumentNullException("cancel");
+
+ var args = new AlertArguments(title, message, accept, cancel);
+ MessagingCenter.Send(this, AlertSignalName, args);
+ return args.Result.Task;
+ }
+
+ public void ForceLayout()
+ {
+ SizeAllocated(Width, Height);
+ }
+
+ public bool SendBackButtonPressed()
+ {
+ return OnBackButtonPressed();
+ }
+
+ protected virtual void LayoutChildren(double x, double y, double width, double height)
+ {
+ var area = new Rectangle(x, y, width, height);
+ Rectangle originalArea = area;
+ if (_containerAreaSet)
+ {
+ area = ContainerArea;
+ area.X += Padding.Left;
+ area.Y += Padding.Right;
+ area.Width -= Padding.HorizontalThickness;
+ area.Height -= Padding.VerticalThickness;
+ area.Width = Math.Max(0, area.Width);
+ area.Height = Math.Max(0, area.Height);
+ }
+
+ foreach (Element element in LogicalChildren)
+ {
+ var child = element as VisualElement;
+ if (child == null)
+ continue;
+ var page = child as Page;
+ if (page != null && page.IgnoresContainerArea)
+ {
+ Forms.Layout.LayoutChildIntoBoundingRegion(child, originalArea);
+ }
+ else
+ {
+ Forms.Layout.LayoutChildIntoBoundingRegion(child, area);
+ }
+ }
+ }
+
+ protected virtual void OnAppearing()
+ {
+ }
+
+ protected virtual bool OnBackButtonPressed()
+ {
+ var application = RealParent as Application;
+ if (application == null || this == application.MainPage)
+ return false;
+
+ var canceled = false;
+ EventHandler handler = (sender, args) => { canceled = true; };
+ application.PopCanceled += handler;
+ Navigation.PopModalAsync().ContinueWith(t => { throw t.Exception; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
+
+ application.PopCanceled -= handler;
+ return !canceled;
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ foreach (ToolbarItem toolbarItem in ToolbarItems)
+ {
+ SetInheritedBindingContext(toolbarItem, BindingContext);
+ }
+ }
+
+ protected virtual void OnChildMeasureInvalidated(object sender, EventArgs e)
+ {
+ InvalidationTrigger trigger = (e as InvalidationEventArgs)?.Trigger ?? InvalidationTrigger.Undefined;
+ OnChildMeasureInvalidated((VisualElement)sender, trigger);
+ }
+
+ protected virtual void OnDisappearing()
+ {
+ }
+
+ protected override void OnParentSet()
+ {
+ if (!Application.IsApplicationOrNull(RealParent) && !(RealParent is Page))
+ throw new InvalidOperationException("Parent of a Page must also be a Page");
+ base.OnParentSet();
+ }
+
+ protected override void OnSizeAllocated(double width, double height)
+ {
+ _allocatedFlag = true;
+ base.OnSizeAllocated(width, height);
+ UpdateChildrenLayout();
+ }
+
+ protected void UpdateChildrenLayout()
+ {
+ if (!ShouldLayoutChildren())
+ return;
+
+ var startingLayout = new List<Rectangle>(LogicalChildren.Count);
+ foreach (VisualElement c in LogicalChildren)
+ {
+ startingLayout.Add(c.Bounds);
+ }
+
+ double x = Padding.Left;
+ double y = Padding.Top;
+ double w = Math.Max(0, Width - Padding.HorizontalThickness);
+ double h = Math.Max(0, Height - Padding.VerticalThickness);
+
+ LayoutChildren(x, y, w, h);
+
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var c = (VisualElement)LogicalChildren[i];
+
+ if (c.Bounds != startingLayout[i])
+ {
+ EventHandler handler = LayoutChanged;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ return;
+ }
+ }
+ }
+
+ internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger)
+ {
+ var container = this as IPageContainer<Page>;
+ if (container != null)
+ {
+ Page page = container.CurrentPage;
+ if (page != null && page.IsVisible && (!page.IsPlatformEnabled || !page.IsNativeStateConsistent))
+ return;
+ }
+ else
+ {
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var v = LogicalChildren[i] as VisualElement;
+ if (v != null && v.IsVisible && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent))
+ return;
+ }
+ }
+
+ _allocatedFlag = false;
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ if (!_allocatedFlag && Width >= 0 && Height >= 0)
+ {
+ SizeAllocated(Width, Height);
+ }
+ }
+
+ internal void SendAppearing()
+ {
+ if (_hasAppeared)
+ return;
+
+ _hasAppeared = true;
+
+ if (IsBusy)
+ MessagingCenter.Send(this, BusySetSignalName, true);
+
+ OnAppearing();
+ EventHandler handler = Appearing;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+
+ var pageContainer = this as IPageContainer<Page>;
+ pageContainer?.CurrentPage?.SendAppearing();
+ }
+
+ internal void SendDisappearing()
+ {
+ if (!_hasAppeared)
+ return;
+
+ _hasAppeared = false;
+
+ if (IsBusy)
+ MessagingCenter.Send(this, BusySetSignalName, false);
+
+ var pageContainer = this as IPageContainer<Page>;
+ pageContainer?.CurrentPage?.SendDisappearing();
+
+ OnDisappearing();
+ EventHandler handler = Disappearing;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.OldItems != null)
+ {
+ foreach (VisualElement item in e.OldItems.OfType<VisualElement>())
+ OnInternalRemoved(item);
+ }
+
+ if (e.NewItems != null)
+ {
+ foreach (VisualElement item in e.NewItems.OfType<VisualElement>())
+ OnInternalAdded(item);
+ }
+ }
+
+ void OnInternalAdded(VisualElement view)
+ {
+ view.MeasureInvalidated += OnChildMeasureInvalidated;
+
+ OnChildAdded(view);
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ void OnInternalRemoved(VisualElement view)
+ {
+ view.MeasureInvalidated -= OnChildMeasureInvalidated;
+
+ OnChildRemoved(view);
+ }
+
+ void OnPageBusyChanged()
+ {
+ if (!_hasAppeared)
+ return;
+
+ MessagingCenter.Send(this, BusySetSignalName, IsBusy);
+ }
+
+ void OnToolbarItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ if (args.Action != NotifyCollectionChangedAction.Add)
+ return;
+ foreach (IElement item in args.NewItems)
+ item.Parent = this;
+ }
+
+ bool ShouldLayoutChildren()
+ {
+ if (!LogicalChildren.Any() || Width <= 0 || Height <= 0 || !IsNativeStateConsistent)
+ return false;
+
+ var container = this as IPageContainer<Page>;
+ if (container != null && container.CurrentPage != null)
+ {
+ if (InternalChildren.Contains(container.CurrentPage))
+ return container.CurrentPage.IsPlatformEnabled && container.CurrentPage.IsNativeStateConsistent;
+ return true;
+ }
+
+ var any = false;
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var v = LogicalChildren[i] as VisualElement;
+ if (v != null && (!v.IsPlatformEnabled || !v.IsNativeStateConsistent))
+ {
+ any = true;
+ break;
+ }
+ }
+ return !any;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PanGestureRecognizer.cs b/Xamarin.Forms.Core/PanGestureRecognizer.cs
new file mode 100644
index 00000000..3fbf2f62
--- /dev/null
+++ b/Xamarin.Forms.Core/PanGestureRecognizer.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class PanGestureRecognizer : GestureRecognizer, IPanGestureController
+ {
+ public static readonly BindableProperty TouchPointsProperty = BindableProperty.Create("TouchPoints", typeof(int), typeof(PanGestureRecognizer), 1);
+
+ public int TouchPoints
+ {
+ get { return (int)GetValue(TouchPointsProperty); }
+ set { SetValue(TouchPointsProperty, value); }
+ }
+
+ void IPanGestureController.SendPan(Element sender, double totalX, double totalY, int gestureId)
+ {
+ PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Running, gestureId, totalX, totalY));
+ }
+
+ void IPanGestureController.SendPanCanceled(Element sender, int gestureId)
+ {
+ PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Canceled, gestureId));
+ }
+
+ void IPanGestureController.SendPanCompleted(Element sender, int gestureId)
+ {
+ PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Completed, gestureId));
+ }
+
+ void IPanGestureController.SendPanStarted(Element sender, int gestureId)
+ {
+ PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Started, gestureId));
+ }
+
+ public event EventHandler<PanUpdatedEventArgs> PanUpdated;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PanUpdatedEventArgs.cs b/Xamarin.Forms.Core/PanUpdatedEventArgs.cs
new file mode 100644
index 00000000..4c9e9481
--- /dev/null
+++ b/Xamarin.Forms.Core/PanUpdatedEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class PanUpdatedEventArgs : EventArgs
+ {
+ public PanUpdatedEventArgs(GestureStatus type, int gestureId, double totalx, double totaly) : this(type, gestureId)
+ {
+ TotalX = totalx;
+ TotalY = totaly;
+ }
+
+ public PanUpdatedEventArgs(GestureStatus type, int gestureId)
+ {
+ StatusType = type;
+ GestureId = gestureId;
+ }
+
+ public int GestureId { get; }
+
+ public GestureStatus StatusType { get; }
+
+ public double TotalX { get; }
+
+ public double TotalY { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ParameterAttribute.cs b/Xamarin.Forms.Core/ParameterAttribute.cs
new file mode 100644
index 00000000..d8a07267
--- /dev/null
+++ b/Xamarin.Forms.Core/ParameterAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Parameter)]
+ internal sealed class ParameterAttribute : Attribute
+ {
+ public ParameterAttribute(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Performance.cs b/Xamarin.Forms.Core/Performance.cs
new file mode 100644
index 00000000..e627817b
--- /dev/null
+++ b/Xamarin.Forms.Core/Performance.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Xamarin.Forms
+{
+ internal static class Performance
+ {
+ static readonly Dictionary<string, Stats> Statistics = new Dictionary<string, Stats>();
+
+ [Conditional("PERF")]
+ public static void Clear()
+ {
+ Statistics.Clear();
+ }
+
+ public static void Count(string tag = null, [CallerFilePath] string path = null, [CallerMemberName] string member = null)
+ {
+ string id = path + ":" + member + (tag != null ? "-" + tag : string.Empty);
+
+ Stats stats;
+ if (!Statistics.TryGetValue(id, out stats))
+ Statistics[id] = stats = new Stats();
+
+ stats.CallCount++;
+ }
+
+ [Conditional("PERF")]
+ public static void DumpStats()
+ {
+ Debug.WriteLine(GetStats());
+ }
+
+ public static string GetStats()
+ {
+ var b = new StringBuilder();
+ b.AppendLine("ID | Call Count | Total Time | Avg Time");
+ foreach (KeyValuePair<string, Stats> kvp in Statistics.OrderBy(kvp => kvp.Key))
+ {
+ string key = ShortenPath(kvp.Key);
+ double total = TimeSpan.FromTicks(kvp.Value.TotalTime).TotalMilliseconds;
+ double avg = total / kvp.Value.CallCount;
+ b.AppendFormat("{0,-80} | {1,-10} | {2,-10}ms | {3,-8}ms", key, kvp.Value.CallCount, total, avg);
+ b.AppendLine();
+ }
+ return b.ToString();
+ }
+
+ [Conditional("PERF")]
+ public static void Start(string tag = null, [CallerFilePath] string path = null, [CallerMemberName] string member = null)
+ {
+ string id = path + ":" + member + (tag != null ? "-" + tag : string.Empty);
+
+ Stats stats;
+ if (!Statistics.TryGetValue(id, out stats))
+ Statistics[id] = stats = new Stats();
+
+ stats.CallCount++;
+ stats.StartTimes.Push(Stopwatch.GetTimestamp());
+ }
+
+ [Conditional("PERF")]
+ public static void Stop(string tag = null, [CallerFilePath] string path = null, [CallerMemberName] string member = null)
+ {
+ string id = path + ":" + member + (tag != null ? "-" + tag : string.Empty);
+ long stop = Stopwatch.GetTimestamp();
+
+ Stats stats = Statistics[id];
+ long start = stats.StartTimes.Pop();
+ if (!stats.StartTimes.Any())
+ stats.TotalTime += stop - start;
+ }
+
+ static string ShortenPath(string path)
+ {
+ int index = path.IndexOf("Xamarin.Forms.");
+ if (index > -1)
+ path = path.Substring(index + 14);
+
+ return path;
+ }
+
+ class Stats
+ {
+ public readonly Stack<long> StartTimes = new Stack<long>();
+ public int CallCount;
+ public long TotalTime;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Picker.cs b/Xamarin.Forms.Core/Picker.cs
new file mode 100644
index 00000000..733f2079
--- /dev/null
+++ b/Xamarin.Forms.Core/Picker.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_PickerRenderer))]
+ public class Picker : View
+ {
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(Picker), default(string));
+
+ public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create("SelectedIndex", typeof(int), typeof(Picker), -1, BindingMode.TwoWay,
+ propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ EventHandler eh = ((Picker)bindable).SelectedIndexChanged;
+ if (eh != null)
+ eh(bindable, EventArgs.Empty);
+ }, coerceValue: CoerceSelectedIndex);
+
+ public Picker()
+ {
+ Items = new ObservableList<string>();
+ ((ObservableList<string>)Items).CollectionChanged += OnItemsCollectionChanged;
+ }
+
+ public IList<string> Items { get; }
+
+ public int SelectedIndex
+ {
+ get { return (int)GetValue(SelectedIndexProperty); }
+ set { SetValue(SelectedIndexProperty, value); }
+ }
+
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set { SetValue(TitleProperty, value); }
+ }
+
+ public event EventHandler SelectedIndexChanged;
+
+ static object CoerceSelectedIndex(BindableObject bindable, object value)
+ {
+ var picker = (Picker)bindable;
+ return picker.Items == null ? -1 : ((int)value).Clamp(-1, picker.Items.Count - 1);
+ }
+
+ void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ SelectedIndex = SelectedIndex.Clamp(-1, Items.Count - 1);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PinchGestureRecognizer.cs b/Xamarin.Forms.Core/PinchGestureRecognizer.cs
new file mode 100644
index 00000000..aef07fbb
--- /dev/null
+++ b/Xamarin.Forms.Core/PinchGestureRecognizer.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class PinchGestureRecognizer : GestureRecognizer, IPinchGestureController
+ {
+ bool IPinchGestureController.IsPinching { get; set; }
+
+ void IPinchGestureController.SendPinch(Element sender, double delta, Point currentScalePoint)
+ {
+ EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated;
+ if (handler != null)
+ {
+ handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Running, delta, currentScalePoint));
+ }
+ ((IPinchGestureController)this).IsPinching = true;
+ }
+
+ void IPinchGestureController.SendPinchCanceled(Element sender)
+ {
+ EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated;
+ if (handler != null)
+ {
+ handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Canceled));
+ }
+ ((IPinchGestureController)this).IsPinching = false;
+ }
+
+ void IPinchGestureController.SendPinchEnded(Element sender)
+ {
+ EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated;
+ if (handler != null)
+ {
+ handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Completed));
+ }
+ ((IPinchGestureController)this).IsPinching = false;
+ }
+
+ void IPinchGestureController.SendPinchStarted(Element sender, Point initialScalePoint)
+ {
+ EventHandler<PinchGestureUpdatedEventArgs> handler = PinchUpdated;
+ if (handler != null)
+ {
+ handler(sender, new PinchGestureUpdatedEventArgs(GestureStatus.Started, 1, initialScalePoint));
+ }
+ ((IPinchGestureController)this).IsPinching = true;
+ }
+
+ public event EventHandler<PinchGestureUpdatedEventArgs> PinchUpdated;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs b/Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs
new file mode 100644
index 00000000..a5c0eacb
--- /dev/null
+++ b/Xamarin.Forms.Core/PinchGestureUpdatedEventArgs.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class PinchGestureUpdatedEventArgs : EventArgs
+ {
+ public PinchGestureUpdatedEventArgs(GestureStatus status, double scale, Point origin) : this(status)
+ {
+ ScaleOrigin = origin;
+ Scale = scale;
+ }
+
+ public PinchGestureUpdatedEventArgs(GestureStatus status)
+ {
+ Status = status;
+ }
+
+ public double Scale { get; } = 1;
+
+ public Point ScaleOrigin { get; }
+
+ public GestureStatus Status { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PlatformEffect.cs b/Xamarin.Forms.Core/PlatformEffect.cs
new file mode 100644
index 00000000..e92231ce
--- /dev/null
+++ b/Xamarin.Forms.Core/PlatformEffect.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel;
+
+namespace Xamarin.Forms
+{
+ public abstract class PlatformEffect<TContainer, TControl> : Effect where TContainer : class where TControl : class
+ {
+ public TContainer Container { get; internal set; }
+
+ public TControl Control { get; internal set; }
+
+ protected virtual void OnElementPropertyChanged(PropertyChangedEventArgs args)
+ {
+ }
+
+ internal override void SendDetached()
+ {
+ base.SendDetached();
+ Container = null;
+ Control = null;
+ }
+
+ internal override void SendOnElementPropertyChanged(PropertyChangedEventArgs args)
+ {
+ if (IsAttached)
+ OnElementPropertyChanged(args);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Point.cs b/Xamarin.Forms.Core/Point.cs
new file mode 100644
index 00000000..5c6f5471
--- /dev/null
+++ b/Xamarin.Forms.Core/Point.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("X={X}, Y={Y}")]
+ [TypeConverter(typeof(PointTypeConverter))]
+ public struct Point
+ {
+ public double X { get; set; }
+
+ public double Y { get; set; }
+
+ public static Point Zero = new Point();
+
+ public override string ToString()
+ {
+ return string.Format("{{X={0} Y={1}}}", X.ToString(CultureInfo.InvariantCulture), Y.ToString(CultureInfo.InvariantCulture));
+ }
+
+ public Point(double x, double y) : this()
+ {
+ X = x;
+ Y = y;
+ }
+
+ public Point(Size sz) : this()
+ {
+ X = sz.Width;
+ Y = sz.Height;
+ }
+
+ public override bool Equals(object o)
+ {
+ if (!(o is Point))
+ return false;
+
+ return this == (Point)o;
+ }
+
+ public override int GetHashCode()
+ {
+ return X.GetHashCode() ^ (Y.GetHashCode() * 397);
+ }
+
+ public Point Offset(double dx, double dy)
+ {
+ Point p = this;
+ p.X += dx;
+ p.Y += dy;
+ return p;
+ }
+
+ public Point Round()
+ {
+ return new Point(Math.Round(X), Math.Round(Y));
+ }
+
+ public bool IsEmpty
+ {
+ get { return (X == 0) && (Y == 0); }
+ }
+
+ public static explicit operator Size(Point pt)
+ {
+ return new Size(pt.X, pt.Y);
+ }
+
+ public static Point operator +(Point pt, Size sz)
+ {
+ return new Point(pt.X + sz.Width, pt.Y + sz.Height);
+ }
+
+ public static Point operator -(Point pt, Size sz)
+ {
+ return new Point(pt.X - sz.Width, pt.Y - sz.Height);
+ }
+
+ public static bool operator ==(Point ptA, Point ptB)
+ {
+ return (ptA.X == ptB.X) && (ptA.Y == ptB.Y);
+ }
+
+ public static bool operator !=(Point ptA, Point ptB)
+ {
+ return (ptA.X != ptB.X) || (ptA.Y != ptB.Y);
+ }
+
+ public double Distance(Point other)
+ {
+ return Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PointTypeConverter.cs b/Xamarin.Forms.Core/PointTypeConverter.cs
new file mode 100644
index 00000000..9b949988
--- /dev/null
+++ b/Xamarin.Forms.Core/PointTypeConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class PointTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ double x, y;
+ string[] xy = value.Split(',');
+ if (xy.Length == 2 && double.TryParse(xy[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x) && double.TryParse(xy[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y))
+ return new Point(x, y);
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Point)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PreserveAttribute.cs b/Xamarin.Forms.Core/PreserveAttribute.cs
new file mode 100644
index 00000000..41487c86
--- /dev/null
+++ b/Xamarin.Forms.Core/PreserveAttribute.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(
+ AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field |
+ AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate | AttributeTargets.All)]
+ internal class PreserveAttribute : Attribute
+ {
+ public bool AllMembers;
+ public bool Conditional;
+
+ public PreserveAttribute(bool allMembers, bool conditional)
+ {
+ AllMembers = allMembers;
+ Conditional = conditional;
+ }
+
+ public PreserveAttribute()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ProgressBar.cs b/Xamarin.Forms.Core/ProgressBar.cs
new file mode 100644
index 00000000..cd8addf6
--- /dev/null
+++ b/Xamarin.Forms.Core/ProgressBar.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_ProgressBarRenderer))]
+ public class ProgressBar : View
+ {
+ public static readonly BindableProperty ProgressProperty = BindableProperty.Create("Progress", typeof(double), typeof(ProgressBar), 0d, coerceValue: (bo, v) => ((double)v).Clamp(0, 1));
+
+ public double Progress
+ {
+ get { return (double)GetValue(ProgressProperty); }
+ set { SetValue(ProgressProperty, value); }
+ }
+
+ public Task<bool> ProgressTo(double value, uint length, Easing easing)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ this.Animate("Progress", d => Progress = d, Progress, value, length: length, easing: easing, finished: (d, finished) => tcs.SetResult(finished));
+
+ return tcs.Task;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..b64ecc4d
--- /dev/null
+++ b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,56 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms;
+
+[assembly: AssemblyTitle("Xamarin.Forms.Core")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The Page "{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("")]
+
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.iOS")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.iOS.Classic")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.Android")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.UAP")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT.Tablet")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT.Phone")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WP8")]
+[assembly: InternalsVisibleTo("iOSUnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Controls")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Core.Design")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Core.UnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Core.Android.UnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Core.WP8.UnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Maps")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS.Classic")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.Android")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.UnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.UITests")]
+//[assembly:InternalsVisibleTo("Xamarin.Forms.Core.UITests")]
+
+[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.iOS.UITests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Android.UITests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Loader")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.UITest.Validator")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Build.Tasks")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Platform")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Pages")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Pages.UnitTests")]
+[assembly: Preserve] \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs b/Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs
new file mode 100644
index 00000000..358bc29b
--- /dev/null
+++ b/Xamarin.Forms.Core/Properties/GlobalAssemblyInfo.cs
@@ -0,0 +1,8 @@
+using System.Reflection;
+
+[assembly: AssemblyCompany("Xamarin Inc.")]
+[assembly: AssemblyProduct("Xamarin.Forms")]
+[assembly: AssemblyCopyright("Copyright © Xamarin Inc. 2013-2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyVersion("2.0.0.0")]
+[assembly: AssemblyFileVersion("2.0.0.0")] \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PropertyChangingEventArgs.cs b/Xamarin.Forms.Core/PropertyChangingEventArgs.cs
new file mode 100644
index 00000000..75c78994
--- /dev/null
+++ b/Xamarin.Forms.Core/PropertyChangingEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class PropertyChangingEventArgs : EventArgs
+ {
+ public PropertyChangingEventArgs(string propertyName)
+ {
+ PropertyName = propertyName;
+ }
+
+ public virtual string PropertyName { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/PropertyChangingEventHandler.cs b/Xamarin.Forms.Core/PropertyChangingEventHandler.cs
new file mode 100644
index 00000000..06227c76
--- /dev/null
+++ b/Xamarin.Forms.Core/PropertyChangingEventHandler.cs
@@ -0,0 +1,4 @@
+namespace Xamarin.Forms
+{
+ public delegate void PropertyChangingEventHandler(object sender, PropertyChangingEventArgs e);
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ReadOnlyCastingList.cs b/Xamarin.Forms.Core/ReadOnlyCastingList.cs
new file mode 100644
index 00000000..a3f880c3
--- /dev/null
+++ b/Xamarin.Forms.Core/ReadOnlyCastingList.cs
@@ -0,0 +1,35 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal class ReadOnlyCastingList<T, TFrom> : IReadOnlyList<T> where T : class where TFrom : class
+ {
+ readonly IList<TFrom> _list;
+
+ public ReadOnlyCastingList(IList<TFrom> list)
+ {
+ _list = list;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_list).GetEnumerator();
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return new CastingEnumerator<T, TFrom>(_list.GetEnumerator());
+ }
+
+ public int Count
+ {
+ get { return _list.Count; }
+ }
+
+ public T this[int index]
+ {
+ get { return _list[index] as T; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ReadOnlyListAdapter.cs b/Xamarin.Forms.Core/ReadOnlyListAdapter.cs
new file mode 100644
index 00000000..0ffdaef5
--- /dev/null
+++ b/Xamarin.Forms.Core/ReadOnlyListAdapter.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal sealed class ReadOnlyListAdapter : IList
+ {
+ readonly IReadOnlyCollection<object> _collection;
+ readonly IReadOnlyList<object> _list;
+
+ public ReadOnlyListAdapter(IReadOnlyList<object> list)
+ {
+ _list = list;
+ _collection = list;
+ }
+
+ public ReadOnlyListAdapter(IReadOnlyCollection<object> collection)
+ {
+ _collection = collection;
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int Count
+ {
+ get { return _collection.Count; }
+ }
+
+ public bool IsSynchronized
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public object SyncRoot
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _collection.GetEnumerator();
+ }
+
+ public int Add(object value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool Contains(object value)
+ {
+ return _list.Contains(value);
+ }
+
+ public int IndexOf(object value)
+ {
+ return _list.IndexOf(value);
+ }
+
+ public void Insert(int index, object value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsFixedSize
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return true; }
+ }
+
+ public object this[int index]
+ {
+ get { return _list[index]; }
+ set { throw new NotImplementedException(); }
+ }
+
+ public void Remove(object value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void RemoveAt(int index)
+ {
+ throw new NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Rectangle.cs b/Xamarin.Forms.Core/Rectangle.cs
new file mode 100644
index 00000000..ac5460cb
--- /dev/null
+++ b/Xamarin.Forms.Core/Rectangle.cs
@@ -0,0 +1,244 @@
+//
+// Rectangle.cs
+//
+// Author:
+// Lluis Sanchez <lluis@xamarin.com>
+//
+// Copyright (c) 2011 Xamarin Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("X={X}, Y={Y}, Width={Width}, Height={Height}")]
+ [TypeConverter(typeof(RectangleTypeConverter))]
+ public struct Rectangle
+ {
+ public double X { get; set; }
+
+ public double Y { get; set; }
+
+ public double Width { get; set; }
+
+ public double Height { get; set; }
+
+ public static Rectangle Zero = new Rectangle();
+
+ public override string ToString()
+ {
+ return string.Format("{{X={0} Y={1} Width={2} Height={3}}}", X.ToString(CultureInfo.InvariantCulture), Y.ToString(CultureInfo.InvariantCulture), Width.ToString(CultureInfo.InvariantCulture),
+ Height.ToString(CultureInfo.InvariantCulture));
+ }
+
+ // constructors
+ public Rectangle(double x, double y, double width, double height) : this()
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+
+ public Rectangle(Point loc, Size sz) : this(loc.X, loc.Y, sz.Width, sz.Height)
+ {
+ }
+
+ public static Rectangle FromLTRB(double left, double top, double right, double bottom)
+ {
+ return new Rectangle(left, top, right - left, bottom - top);
+ }
+
+ public bool Equals(Rectangle other)
+ {
+ return X.Equals(other.X) && Y.Equals(other.Y) && Width.Equals(other.Width) && Height.Equals(other.Height);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ return false;
+ return obj is Rectangle && Equals((Rectangle)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = X.GetHashCode();
+ hashCode = (hashCode * 397) ^ Y.GetHashCode();
+ hashCode = (hashCode * 397) ^ Width.GetHashCode();
+ hashCode = (hashCode * 397) ^ Height.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(Rectangle r1, Rectangle r2)
+ {
+ return (r1.Location == r2.Location) && (r1.Size == r2.Size);
+ }
+
+ public static bool operator !=(Rectangle r1, Rectangle r2)
+ {
+ return !(r1 == r2);
+ }
+
+ // Hit Testing / Intersection / Union
+ public bool Contains(Rectangle rect)
+ {
+ return X <= rect.X && Right >= rect.Right && Y <= rect.Y && Bottom >= rect.Bottom;
+ }
+
+ public bool Contains(Point pt)
+ {
+ return Contains(pt.X, pt.Y);
+ }
+
+ public bool Contains(double x, double y)
+ {
+ return (x >= Left) && (x < Right) && (y >= Top) && (y < Bottom);
+ }
+
+ public bool IntersectsWith(Rectangle r)
+ {
+ return !((Left >= r.Right) || (Right <= r.Left) || (Top >= r.Bottom) || (Bottom <= r.Top));
+ }
+
+ public Rectangle Union(Rectangle r)
+ {
+ return Union(this, r);
+ }
+
+ public static Rectangle Union(Rectangle r1, Rectangle r2)
+ {
+ return FromLTRB(Math.Min(r1.Left, r2.Left), Math.Min(r1.Top, r2.Top), Math.Max(r1.Right, r2.Right), Math.Max(r1.Bottom, r2.Bottom));
+ }
+
+ public Rectangle Intersect(Rectangle r)
+ {
+ return Intersect(this, r);
+ }
+
+ public static Rectangle Intersect(Rectangle r1, Rectangle r2)
+ {
+ double x = Math.Max(r1.X, r2.X);
+ double y = Math.Max(r1.Y, r2.Y);
+ double width = Math.Min(r1.Right, r2.Right) - x;
+ double height = Math.Min(r1.Bottom, r2.Bottom) - y;
+
+ if (width < 0 || height < 0)
+ {
+ return Zero;
+ }
+ return new Rectangle(x, y, width, height);
+ }
+
+ // Position/Size
+ public double Top
+ {
+ get { return Y; }
+ set { Y = value; }
+ }
+
+ public double Bottom
+ {
+ get { return Y + Height; }
+ set { Height = value - Y; }
+ }
+
+ public double Right
+ {
+ get { return X + Width; }
+ set { Width = value - X; }
+ }
+
+ public double Left
+ {
+ get { return X; }
+ set { X = value; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return (Width <= 0) || (Height <= 0); }
+ }
+
+ public Size Size
+ {
+ get { return new Size(Width, Height); }
+ set
+ {
+ Width = value.Width;
+ Height = value.Height;
+ }
+ }
+
+ public Point Location
+ {
+ get { return new Point(X, Y); }
+ set
+ {
+ X = value.X;
+ Y = value.Y;
+ }
+ }
+
+ public Point Center
+ {
+ get { return new Point(X + Width / 2, Y + Height / 2); }
+ }
+
+ // Inflate and Offset
+ public Rectangle Inflate(Size sz)
+ {
+ return Inflate(sz.Width, sz.Height);
+ }
+
+ public Rectangle Inflate(double width, double height)
+ {
+ Rectangle r = this;
+ r.X -= width;
+ r.Y -= height;
+ r.Width += width * 2;
+ r.Height += height * 2;
+ return r;
+ }
+
+ public Rectangle Offset(double dx, double dy)
+ {
+ Rectangle r = this;
+ r.X += dx;
+ r.Y += dy;
+ return r;
+ }
+
+ public Rectangle Offset(Point dr)
+ {
+ return Offset(dr.X, dr.Y);
+ }
+
+ public Rectangle Round()
+ {
+ return new Rectangle(Math.Round(X), Math.Round(Y), Math.Round(Width), Math.Round(Height));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/RectangleTypeConverter.cs b/Xamarin.Forms.Core/RectangleTypeConverter.cs
new file mode 100644
index 00000000..b17a8c26
--- /dev/null
+++ b/Xamarin.Forms.Core/RectangleTypeConverter.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class RectangleTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ double x, y, w, h;
+ string[] xywh = value.Split(',');
+ if (xywh.Length == 4 && double.TryParse(xywh[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x) && double.TryParse(xywh[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y) &&
+ double.TryParse(xywh[2], NumberStyles.Number, CultureInfo.InvariantCulture, out w) && double.TryParse(xywh[3], NumberStyles.Number, CultureInfo.InvariantCulture, out h))
+ return new Rectangle(x, y, w, h);
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Rectangle)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ReflectionExtensions.cs b/Xamarin.Forms.Core/ReflectionExtensions.cs
new file mode 100644
index 00000000..ab26fde5
--- /dev/null
+++ b/Xamarin.Forms.Core/ReflectionExtensions.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ internal static class ReflectionExtensions
+ {
+ public static FieldInfo GetField(this Type type, Func<FieldInfo, bool> predicate)
+ {
+ return GetFields(type).SingleOrDefault(predicate);
+ }
+
+ public static FieldInfo GetField(this Type type, string name)
+ {
+ return type.GetField(fi => fi.Name == name);
+ }
+
+ public static IEnumerable<FieldInfo> GetFields(this Type type)
+ {
+ return GetParts(type, i => i.DeclaredFields);
+ }
+
+ public static IEnumerable<PropertyInfo> GetProperties(this Type type)
+ {
+ return GetParts(type, ti => ti.DeclaredProperties);
+ }
+
+ public static PropertyInfo GetProperty(this Type type, string name)
+ {
+ Type t = type;
+ while (t != null)
+ {
+ TypeInfo ti = t.GetTypeInfo();
+ PropertyInfo property = ti.GetDeclaredProperty(name);
+ if (property != null)
+ return property;
+
+ t = ti.BaseType;
+ }
+
+ return null;
+ }
+
+ public static bool IsAssignableFrom(this Type self, Type c)
+ {
+ return self.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo());
+ }
+
+ public static bool IsInstanceOfType(this Type self, object o)
+ {
+ return self.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
+ }
+
+ static IEnumerable<T> GetParts<T>(Type type, Func<TypeInfo, IEnumerable<T>> selector)
+ {
+ Type t = type;
+ while (t != null)
+ {
+ TypeInfo ti = t.GetTypeInfo();
+ foreach (T f in selector(ti))
+ yield return f;
+ t = ti.BaseType;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Registrar.cs b/Xamarin.Forms.Core/Registrar.cs
new file mode 100644
index 00000000..92c2e533
--- /dev/null
+++ b/Xamarin.Forms.Core/Registrar.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ internal class Registrar<TRegistrable> where TRegistrable : class
+ {
+ readonly Dictionary<Type, Type> _handlers = new Dictionary<Type, Type>();
+
+ public void Register(Type tview, Type trender)
+ {
+ _handlers[tview] = trender;
+ }
+
+ internal TRegistrable GetHandler(Type type)
+ {
+ Type handlerType = GetHandlerType(type);
+ if (handlerType == null)
+ return null;
+
+ object handler = Activator.CreateInstance(handlerType);
+ return (TRegistrable)handler;
+ }
+
+ internal TOut GetHandler<TOut>(Type type) where TOut : TRegistrable
+ {
+ return (TOut)GetHandler(type);
+ }
+
+ internal Type GetHandlerType(Type viewType)
+ {
+ Type type = LookupHandlerType(viewType);
+ if (type != null)
+ return type;
+
+ // lazy load render-view association with RenderWithAttribute (as opposed to using ExportRenderer)
+ // TODO: change Registrar to a LazyImmutableDictionary and pass this logic to ctor as a delegate.
+ var attribute = viewType.GetTypeInfo().GetCustomAttribute<RenderWithAttribute>();
+ if (attribute == null)
+ return null;
+ type = attribute.Type;
+
+ if (type.Name.StartsWith("_"))
+ {
+ // TODO: Remove attribute2 once renderer names have been unified across all platforms
+ var attribute2 = type.GetTypeInfo().GetCustomAttribute<RenderWithAttribute>();
+ if (attribute2 != null)
+ type = attribute2.Type;
+
+ if (type.Name.StartsWith("_"))
+ {
+ //var attrs = type.GetTypeInfo ().GetCustomAttributes ().ToArray ();
+ return null;
+ }
+ }
+
+ Register(viewType, type);
+ return LookupHandlerType(viewType);
+ }
+
+ Type LookupHandlerType(Type viewType)
+ {
+ Type type = viewType;
+
+ while (true)
+ {
+ if (_handlers.ContainsKey(type))
+ return _handlers[type];
+
+ type = type.GetTypeInfo().BaseType;
+ if (type == null)
+ break;
+ }
+
+ return null;
+ }
+ }
+
+ internal static class Registrar
+ {
+ static Registrar()
+ {
+ Registered = new Registrar<IRegisterable>();
+ }
+
+ internal static Dictionary<string, Type> Effects { get; } = new Dictionary<string, Type>();
+
+ internal static IEnumerable<Assembly> ExtraAssemblies { get; set; }
+
+ internal static Registrar<IRegisterable> Registered { get; }
+
+ internal static void RegisterAll(Type[] attrTypes)
+ {
+ Assembly[] assemblies = Device.GetAssemblies();
+ if (ExtraAssemblies != null)
+ {
+ assemblies = assemblies.Union(ExtraAssemblies).ToArray();
+ }
+
+ Assembly defaultRendererAssembly = Device.PlatformServices.GetType().GetTypeInfo().Assembly;
+ int indexOfExecuting = Array.IndexOf(assemblies, defaultRendererAssembly);
+
+ if (indexOfExecuting > 0)
+ {
+ assemblies[indexOfExecuting] = assemblies[0];
+ assemblies[0] = defaultRendererAssembly;
+ }
+
+ // Don't use LINQ for performance reasons
+ // Naive implementation can easily take over a second to run
+ foreach (Assembly assembly in assemblies)
+ {
+ foreach (Type attrType in attrTypes)
+ {
+ Attribute[] attributes = assembly.GetCustomAttributes(attrType).ToArray();
+ if (attributes.Length == 0)
+ continue;
+
+ foreach (HandlerAttribute attribute in attributes)
+ {
+ if (attribute.ShouldRegister())
+ Registered.Register(attribute.HandlerType, attribute.TargetType);
+ }
+ }
+
+ string resolutionName = assembly.FullName;
+ var resolutionNameAttribute = (ResolutionGroupNameAttribute)assembly.GetCustomAttribute(typeof(ResolutionGroupNameAttribute));
+ if (resolutionNameAttribute != null)
+ {
+ resolutionName = resolutionNameAttribute.ShortName;
+ }
+
+ Attribute[] effectAttributes = assembly.GetCustomAttributes(typeof(ExportEffectAttribute)).ToArray();
+ if (effectAttributes.Length > 0)
+ {
+ foreach (Attribute attribute in effectAttributes)
+ {
+ var effect = (ExportEffectAttribute)attribute;
+ Effects.Add(resolutionName + "." + effect.Id, effect.Type);
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/RelativeLayout.cs b/Xamarin.Forms.Core/RelativeLayout.cs
new file mode 100644
index 00000000..b3a1b615
--- /dev/null
+++ b/Xamarin.Forms.Core/RelativeLayout.cs
@@ -0,0 +1,317 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Xamarin.Forms
+{
+ public class RelativeLayout : Layout<View>
+ {
+ public static readonly BindableProperty XConstraintProperty = BindableProperty.CreateAttached("XConstraint", typeof(Constraint), typeof(RelativeLayout), null);
+
+ public static readonly BindableProperty YConstraintProperty = BindableProperty.CreateAttached("YConstraint", typeof(Constraint), typeof(RelativeLayout), null);
+
+ public static readonly BindableProperty WidthConstraintProperty = BindableProperty.CreateAttached("WidthConstraint", typeof(Constraint), typeof(RelativeLayout), null);
+
+ public static readonly BindableProperty HeightConstraintProperty = BindableProperty.CreateAttached("HeightConstraint", typeof(Constraint), typeof(RelativeLayout), null);
+
+ public static readonly BindableProperty BoundsConstraintProperty = BindableProperty.CreateAttached("BoundsConstraint", typeof(BoundsConstraint), typeof(RelativeLayout), null);
+
+ readonly RelativeElementCollection _children;
+
+ IEnumerable<View> _childrenInSolveOrder;
+
+ public RelativeLayout()
+ {
+ VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
+ _children = new RelativeElementCollection(InternalChildren, this);
+ _children.Parent = this;
+ }
+
+ public new IRelativeList<View> Children
+ {
+ get { return _children; }
+ }
+
+ IEnumerable<View> ChildrenInSolveOrder
+ {
+ get
+ {
+ if (_childrenInSolveOrder != null)
+ return _childrenInSolveOrder;
+
+ var result = new List<View>();
+ var solveTable = new Dictionary<View, bool>();
+ foreach (View child in Children.Cast<View>())
+ {
+ solveTable[child] = false;
+ }
+
+ List<View> unsolvedChildren = Children.Cast<View>().ToList();
+ while (unsolvedChildren.Any())
+ {
+ List<View> copy = unsolvedChildren.ToList();
+ var solvedChild = false;
+ foreach (View child in copy)
+ {
+ if (CanSolveView(child, solveTable))
+ {
+ result.Add(child);
+ solveTable[child] = true;
+ unsolvedChildren.Remove(child);
+ solvedChild = true;
+ }
+ }
+ if (!solvedChild)
+ throw new UnsolvableConstraintsException("Constraints as specified contain an unsolvable loop.");
+ }
+
+ _childrenInSolveOrder = result;
+ return _childrenInSolveOrder;
+ }
+ }
+
+ public static BoundsConstraint GetBoundsConstraint(BindableObject bindable)
+ {
+ return (BoundsConstraint)bindable.GetValue(BoundsConstraintProperty);
+ }
+
+ public static Constraint GetHeightConstraint(BindableObject bindable)
+ {
+ return (Constraint)bindable.GetValue(HeightConstraintProperty);
+ }
+
+ public static Constraint GetWidthConstraint(BindableObject bindable)
+ {
+ return (Constraint)bindable.GetValue(WidthConstraintProperty);
+ }
+
+ public static Constraint GetXConstraint(BindableObject bindable)
+ {
+ return (Constraint)bindable.GetValue(XConstraintProperty);
+ }
+
+ public static Constraint GetYConstraint(BindableObject bindable)
+ {
+ return (Constraint)bindable.GetValue(YConstraintProperty);
+ }
+
+ public static void SetBoundsConstraint(BindableObject bindable, BoundsConstraint value)
+ {
+ bindable.SetValue(BoundsConstraintProperty, value);
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ foreach (View child in ChildrenInSolveOrder)
+ {
+ LayoutChildIntoBoundingRegion(child, SolveView(child));
+ }
+ }
+
+ protected override void OnAdded(View view)
+ {
+ BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
+ if (boundsConstraint == null)
+ {
+ // user probably added the view through the strict Add method.
+ CreateBoundsFromConstraints(view, GetXConstraint(view), GetYConstraint(view), GetWidthConstraint(view), GetHeightConstraint(view));
+ }
+
+ _childrenInSolveOrder = null;
+ base.OnAdded(view);
+ }
+
+ protected override void OnRemoved(View view)
+ {
+ _childrenInSolveOrder = null;
+ base.OnRemoved(view);
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ double mockWidth = double.IsPositiveInfinity(widthConstraint) ? ParentView.Width : widthConstraint;
+ double mockHeight = double.IsPositiveInfinity(heightConstraint) ? ParentView.Height : heightConstraint;
+ MockBounds(new Rectangle(0, 0, mockWidth, mockHeight));
+
+ var boundsRectangle = new Rectangle();
+ var set = false;
+ foreach (View child in ChildrenInSolveOrder)
+ {
+ Rectangle bounds = SolveView(child);
+ child.MockBounds(bounds);
+ if (!set)
+ {
+ boundsRectangle = bounds;
+ set = true;
+ }
+ else
+ {
+ boundsRectangle.Left = Math.Min(boundsRectangle.Left, bounds.Left);
+ boundsRectangle.Top = Math.Min(boundsRectangle.Top, bounds.Top);
+ boundsRectangle.Right = Math.Max(boundsRectangle.Right, bounds.Right);
+ boundsRectangle.Bottom = Math.Max(boundsRectangle.Bottom, bounds.Bottom);
+ }
+ }
+
+ foreach (View child in ChildrenInSolveOrder)
+ child.UnmockBounds();
+
+ UnmockBounds();
+
+ return new SizeRequest(new Size(boundsRectangle.Right, boundsRectangle.Bottom));
+ }
+
+ bool CanSolveView(View view, Dictionary<View, bool> solveTable)
+ {
+ BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
+ var parents = new List<View>();
+ if (boundsConstraint == null)
+ {
+ throw new Exception("BoundsConstraint should not be null at this point");
+ }
+ parents.AddRange(boundsConstraint.RelativeTo);
+ // expressions probably referenced the base layout somewhere
+ while (parents.Remove(this)) // because winphone does not have RemoveAll...
+ ;
+
+ if (!parents.Any())
+ return true;
+
+ for (var i = 0; i < parents.Count; i++)
+ {
+ View p = parents[i];
+
+ bool solvable;
+ if (!solveTable.TryGetValue(p, out solvable))
+ {
+ throw new InvalidOperationException("Views that have relationships to or from them must be kept in the RelativeLayout.");
+ }
+
+ if (!solvable)
+ return false;
+ }
+
+ return true;
+ }
+
+ void CreateBoundsFromConstraints(View view, Constraint xConstraint, Constraint yConstraint, Constraint widthConstraint, Constraint heightConstraint)
+ {
+ var parents = new List<View>();
+
+ Func<double> x;
+ if (xConstraint != null)
+ {
+ x = () => xConstraint.Compute(this);
+ if (xConstraint.RelativeTo != null)
+ parents.AddRange(xConstraint.RelativeTo);
+ }
+ else
+ x = () => 0;
+
+ Func<double> y;
+ if (yConstraint != null)
+ {
+ y = () => yConstraint.Compute(this);
+ if (yConstraint.RelativeTo != null)
+ parents.AddRange(yConstraint.RelativeTo);
+ }
+ else
+ y = () => 0;
+
+ Func<double> width;
+ if (widthConstraint != null)
+ {
+ width = () => widthConstraint.Compute(this);
+ if (widthConstraint.RelativeTo != null)
+ parents.AddRange(widthConstraint.RelativeTo);
+ }
+ else
+ width = () => view.Measure(Width, Height, MeasureFlags.IncludeMargins).Request.Width;
+
+ Func<double> height;
+ if (heightConstraint != null)
+ {
+ height = () => heightConstraint.Compute(this);
+ if (heightConstraint.RelativeTo != null)
+ parents.AddRange(heightConstraint.RelativeTo);
+ }
+ else
+ height = () => view.Measure(Width, Height, MeasureFlags.IncludeMargins).Request.Height;
+
+ BoundsConstraint bounds = BoundsConstraint.FromExpression(() => new Rectangle(x(), y(), width(), height()), parents.Distinct().ToArray());
+ SetBoundsConstraint(view, bounds);
+ }
+
+ static Rectangle SolveView(View view)
+ {
+ BoundsConstraint boundsConstraint = GetBoundsConstraint(view);
+ var result = new Rectangle();
+
+ if (boundsConstraint == null)
+ {
+ throw new Exception("BoundsConstraint should not be null at this point");
+ }
+ result = boundsConstraint.Compute();
+
+ return result;
+ }
+
+ public interface IRelativeList<T> : IList<T> where T : View
+ {
+ void Add(T view, Expression<Func<Rectangle>> bounds);
+
+ void Add(T view, Expression<Func<double>> x = null, Expression<Func<double>> y = null, Expression<Func<double>> width = null, Expression<Func<double>> height = null);
+
+ void Add(T view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null);
+ }
+
+ class RelativeElementCollection : ElementCollection<View>, IRelativeList<View>
+ {
+ public RelativeElementCollection(ObservableCollection<Element> inner, RelativeLayout parent) : base(inner)
+ {
+ Parent = parent;
+ }
+
+ internal RelativeLayout Parent { get; set; }
+
+ public void Add(View view, Expression<Func<Rectangle>> bounds)
+ {
+ if (bounds == null)
+ throw new ArgumentNullException("bounds");
+ SetBoundsConstraint(view, BoundsConstraint.FromExpression(bounds));
+
+ base.Add(view);
+ }
+
+ public void Add(View view, Expression<Func<double>> x = null, Expression<Func<double>> y = null, Expression<Func<double>> width = null, Expression<Func<double>> height = null)
+ {
+ Func<double> xCompiled = x != null ? x.Compile() : () => 0;
+ Func<double> yCompiled = y != null ? y.Compile() : () => 0;
+ Func<double> widthCompiled = width != null ? width.Compile() : () => view.Measure(Parent.Width, Parent.Height, MeasureFlags.IncludeMargins).Request.Width;
+ Func<double> heightCompiled = height != null ? height.Compile() : () => view.Measure(Parent.Width, Parent.Height, MeasureFlags.IncludeMargins).Request.Height;
+
+ var parents = new List<View>();
+ parents.AddRange(ExpressionSearch.Default.FindObjects<View>(x));
+ parents.AddRange(ExpressionSearch.Default.FindObjects<View>(y));
+ parents.AddRange(ExpressionSearch.Default.FindObjects<View>(width));
+ parents.AddRange(ExpressionSearch.Default.FindObjects<View>(height));
+
+ BoundsConstraint bounds = BoundsConstraint.FromExpression(() => new Rectangle(xCompiled(), yCompiled(), widthCompiled(), heightCompiled()), parents.Distinct().ToArray());
+
+ SetBoundsConstraint(view, bounds);
+
+ base.Add(view);
+ }
+
+ public void Add(View view, Constraint xConstraint = null, Constraint yConstraint = null, Constraint widthConstraint = null, Constraint heightConstraint = null)
+ {
+ Parent.CreateBoundsFromConstraints(view, xConstraint, yConstraint, widthConstraint, heightConstraint);
+
+ base.Add(view);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/RenderWithAttribute.cs b/Xamarin.Forms.Core/RenderWithAttribute.cs
new file mode 100644
index 00000000..75175306
--- /dev/null
+++ b/Xamarin.Forms.Core/RenderWithAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class RenderWithAttribute : Attribute
+ {
+ public RenderWithAttribute(Type type)
+ {
+ Type = type;
+ }
+
+ public Type Type { get; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs b/Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs
new file mode 100644
index 00000000..eee92e76
--- /dev/null
+++ b/Xamarin.Forms.Core/ResolutionGroupNameAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.Assembly)]
+ public class ResolutionGroupNameAttribute : Attribute
+ {
+ public ResolutionGroupNameAttribute(string name)
+ {
+ ShortName = name;
+ }
+
+ internal string ShortName { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ResourceDictionary.cs b/Xamarin.Forms.Core/ResourceDictionary.cs
new file mode 100644
index 00000000..b19773ff
--- /dev/null
+++ b/Xamarin.Forms.Core/ResourceDictionary.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ public sealed class ResourceDictionary : IResourceDictionary, IDictionary<string, object>
+ {
+ readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>();
+
+ void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
+ {
+ ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Add(item);
+ OnValuesChanged(item);
+ }
+
+ public void Clear()
+ {
+ _innerDictionary.Clear();
+ }
+
+ bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
+ {
+ return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Contains(item);
+ }
+
+ void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
+ {
+ ((ICollection<KeyValuePair<string, object>>)_innerDictionary).CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return _innerDictionary.Count; }
+ }
+
+ bool ICollection<KeyValuePair<string, object>>.IsReadOnly
+ {
+ get { return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).IsReadOnly; }
+ }
+
+ bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
+ {
+ return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Remove(item);
+ }
+
+ public void Add(string key, object value)
+ {
+ _innerDictionary.Add(key, value);
+ OnValueChanged(key, value);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return _innerDictionary.ContainsKey(key);
+ }
+
+ [IndexerName("Item")]
+ public object this[string index]
+ {
+ get { return _innerDictionary[index]; }
+ set
+ {
+ _innerDictionary[index] = value;
+ OnValueChanged(index, value);
+ }
+ }
+
+ public ICollection<string> Keys
+ {
+ get { return _innerDictionary.Keys; }
+ }
+
+ public bool Remove(string key)
+ {
+ return _innerDictionary.Remove(key);
+ }
+
+ public ICollection<object> Values
+ {
+ get { return _innerDictionary.Values; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_innerDictionary).GetEnumerator();
+ }
+
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ return _innerDictionary.GetEnumerator();
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ return _innerDictionary.TryGetValue(key, out value);
+ }
+
+ event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged
+ {
+ add { ValuesChanged += value; }
+ remove { ValuesChanged -= value; }
+ }
+
+ public void Add(Style style)
+ {
+ if (string.IsNullOrEmpty(style.Class))
+ Add(style.TargetType.FullName, style);
+ else
+ {
+ IList<Style> classes;
+ object outclasses;
+ if (!TryGetValue(Style.StyleClassPrefix + style.Class, out outclasses) || (classes = outclasses as IList<Style>) == null)
+ classes = new List<Style>();
+ classes.Add(style);
+ this[Style.StyleClassPrefix + style.Class] = classes;
+ }
+ }
+
+ void OnValueChanged(string key, object value)
+ {
+ OnValuesChanged(new KeyValuePair<string, object>(key, value));
+ }
+
+ void OnValuesChanged(params KeyValuePair<string, object>[] values)
+ {
+ if (values == null || values.Length == 0)
+ return;
+ ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values));
+ }
+
+ event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ResourcesChangedEventArgs.cs b/Xamarin.Forms.Core/ResourcesChangedEventArgs.cs
new file mode 100644
index 00000000..2bab8565
--- /dev/null
+++ b/Xamarin.Forms.Core/ResourcesChangedEventArgs.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal class ResourcesChangedEventArgs : EventArgs
+ {
+ public ResourcesChangedEventArgs(IEnumerable<KeyValuePair<string, object>> values)
+ {
+ Values = values;
+ }
+
+ public IEnumerable<KeyValuePair<string, object>> Values { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ResourcesExtensions.cs b/Xamarin.Forms.Core/ResourcesExtensions.cs
new file mode 100644
index 00000000..a75e264a
--- /dev/null
+++ b/Xamarin.Forms.Core/ResourcesExtensions.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ internal static class ResourcesExtensions
+ {
+ public static IEnumerable<KeyValuePair<string, object>> GetMergedResources(this IElement element)
+ {
+ Dictionary<string, object> resources = null;
+ while (element != null)
+ {
+ var ve = element as IResourcesProvider;
+ if (ve != null && ve.Resources != null && ve.Resources.Count != 0)
+ {
+ resources = resources ?? new Dictionary<string, object>(ve.Resources.Count);
+ foreach (KeyValuePair<string, object> res in ve.Resources)
+ if (!resources.ContainsKey(res.Key))
+ resources.Add(res.Key, res.Value);
+ else if (res.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal))
+ {
+ var mergedClassStyles = new List<Style>(resources[res.Key] as List<Style>);
+ mergedClassStyles.AddRange(res.Value as List<Style>);
+ resources[res.Key] = mergedClassStyles;
+ }
+ }
+ var app = element as Application;
+ if (app != null && app.SystemResources != null)
+ {
+ resources = resources ?? new Dictionary<string, object>(8);
+ foreach (KeyValuePair<string, object> res in app.SystemResources)
+ if (!resources.ContainsKey(res.Key))
+ resources.Add(res.Key, res.Value);
+ else if (res.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal))
+ {
+ var mergedClassStyles = new List<Style>(resources[res.Key] as List<Style>);
+ mergedClassStyles.AddRange(res.Value as List<Style>);
+ resources[res.Key] = mergedClassStyles;
+ }
+ }
+ element = element.Parent;
+ }
+ return resources;
+ }
+
+ public static bool TryGetResource(this IElement element, string key, out object value)
+ {
+ while (element != null)
+ {
+ var ve = element as IResourcesProvider;
+ if (ve != null && ve.Resources != null && ve.Resources.TryGetValue(key, out value))
+ return true;
+ var app = element as Application;
+ if (app != null && app.SystemResources != null && app.SystemResources.TryGetValue(key, out value))
+ return true;
+ element = element.Parent;
+ }
+ value = null;
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/RoutingEffect.cs b/Xamarin.Forms.Core/RoutingEffect.cs
new file mode 100644
index 00000000..41f040b9
--- /dev/null
+++ b/Xamarin.Forms.Core/RoutingEffect.cs
@@ -0,0 +1,42 @@
+using System.ComponentModel;
+
+namespace Xamarin.Forms
+{
+ public class RoutingEffect : Effect
+ {
+ internal readonly Effect Inner;
+
+ protected RoutingEffect(string effectId)
+ {
+ Inner = Resolve(effectId);
+ }
+
+ protected override void OnAttached()
+ {
+ }
+
+ protected override void OnDetached()
+ {
+ }
+
+ internal override void ClearEffect()
+ {
+ Inner?.ClearEffect();
+ }
+
+ internal override void SendAttached()
+ {
+ Inner?.SendAttached();
+ }
+
+ internal override void SendDetached()
+ {
+ Inner?.SendDetached();
+ }
+
+ internal override void SendOnElementPropertyChanged(PropertyChangedEventArgs args)
+ {
+ Inner?.SendOnElementPropertyChanged(args);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/RowDefinition.cs b/Xamarin.Forms.Core/RowDefinition.cs
new file mode 100644
index 00000000..746332ea
--- /dev/null
+++ b/Xamarin.Forms.Core/RowDefinition.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public sealed class RowDefinition : BindableObject, IDefinition
+ {
+ public static readonly BindableProperty HeightProperty = BindableProperty.Create("Height", typeof(GridLength), typeof(RowDefinition), new GridLength(1, GridUnitType.Star),
+ propertyChanged: (bindable, oldValue, newValue) => ((RowDefinition)bindable).OnSizeChanged());
+
+ public RowDefinition()
+ {
+ MinimumHeight = -1;
+ }
+
+ public GridLength Height
+ {
+ get { return (GridLength)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ internal double ActualHeight { get; set; }
+
+ internal double MinimumHeight { get; set; }
+
+ public event EventHandler SizeChanged;
+
+ void OnSizeChanged()
+ {
+ EventHandler eh = SizeChanged;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/RowDefinitionCollection.cs b/Xamarin.Forms.Core/RowDefinitionCollection.cs
new file mode 100644
index 00000000..4b979be5
--- /dev/null
+++ b/Xamarin.Forms.Core/RowDefinitionCollection.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ public sealed class RowDefinitionCollection : DefinitionCollection<RowDefinition>
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ScrollOrientation.cs b/Xamarin.Forms.Core/ScrollOrientation.cs
new file mode 100644
index 00000000..71cb335f
--- /dev/null
+++ b/Xamarin.Forms.Core/ScrollOrientation.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public enum ScrollOrientation
+ {
+ Vertical,
+ Horizontal,
+ Both
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ScrollToMode.cs b/Xamarin.Forms.Core/ScrollToMode.cs
new file mode 100644
index 00000000..741f5df0
--- /dev/null
+++ b/Xamarin.Forms.Core/ScrollToMode.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum ScrollToMode
+ {
+ Element = 0,
+ Position = 1
+ // Item = 2,
+ // GroupAndItem = 3,
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ScrollToPosition.cs b/Xamarin.Forms.Core/ScrollToPosition.cs
new file mode 100644
index 00000000..adc13771
--- /dev/null
+++ b/Xamarin.Forms.Core/ScrollToPosition.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum ScrollToPosition
+ {
+ MakeVisible = 0,
+ Start = 1,
+ Center = 2,
+ End = 3
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs b/Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs
new file mode 100644
index 00000000..28231df7
--- /dev/null
+++ b/Xamarin.Forms.Core/ScrollToRequestedEventArgs.cs
@@ -0,0 +1,56 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ScrollToRequestedEventArgs : EventArgs
+ {
+ internal ScrollToRequestedEventArgs(double scrollX, double scrollY, bool shouldAnimate)
+ {
+ ScrollX = scrollX;
+ ScrollY = scrollY;
+ ShouldAnimate = shouldAnimate;
+ Mode = ScrollToMode.Position;
+ }
+
+ internal ScrollToRequestedEventArgs(Element element, ScrollToPosition position, bool shouldAnimate)
+ {
+ Element = element;
+ Position = position;
+ ShouldAnimate = shouldAnimate;
+ Mode = ScrollToMode.Element;
+ }
+
+ internal ScrollToRequestedEventArgs(object item, ScrollToPosition position, bool shouldAnimate)
+ {
+ Item = item;
+ Position = position;
+ ShouldAnimate = shouldAnimate;
+ //Mode = ScrollToMode.Item;
+ }
+
+ internal ScrollToRequestedEventArgs(object item, object group, ScrollToPosition position, bool shouldAnimate)
+ {
+ Item = item;
+ Group = group;
+ Position = position;
+ ShouldAnimate = shouldAnimate;
+ //Mode = ScrollToMode.GroupAndIem;
+ }
+
+ public Element Element { get; private set; }
+
+ public ScrollToMode Mode { get; private set; }
+
+ public ScrollToPosition Position { get; private set; }
+
+ public double ScrollX { get; private set; }
+
+ public double ScrollY { get; private set; }
+
+ public bool ShouldAnimate { get; private set; }
+
+ internal object Group { get; private set; }
+
+ internal object Item { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ScrollView.cs b/Xamarin.Forms.Core/ScrollView.cs
new file mode 100644
index 00000000..143d0a61
--- /dev/null
+++ b/Xamarin.Forms.Core/ScrollView.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Threading.Tasks;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Content")]
+ [RenderWith(typeof(_ScrollViewRenderer))]
+ public class ScrollView : Layout, IScrollViewController
+ {
+ public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(ScrollOrientation), typeof(ScrollView), ScrollOrientation.Vertical);
+
+ static readonly BindablePropertyKey ScrollXPropertyKey = BindableProperty.CreateReadOnly("ScrollX", typeof(double), typeof(ScrollView), 0d);
+
+ public static readonly BindableProperty ScrollXProperty = ScrollXPropertyKey.BindableProperty;
+
+ static readonly BindablePropertyKey ScrollYPropertyKey = BindableProperty.CreateReadOnly("ScrollY", typeof(double), typeof(ScrollView), 0d);
+
+ public static readonly BindableProperty ScrollYProperty = ScrollYPropertyKey.BindableProperty;
+
+ static readonly BindablePropertyKey ContentSizePropertyKey = BindableProperty.CreateReadOnly("ContentSize", typeof(Size), typeof(ScrollView), default(Size));
+
+ public static readonly BindableProperty ContentSizeProperty = ContentSizePropertyKey.BindableProperty;
+
+ View _content;
+
+ TaskCompletionSource<bool> _scrollCompletionSource;
+
+ public View Content
+ {
+ get { return _content; }
+ set
+ {
+ if (_content == value)
+ return;
+
+ OnPropertyChanging();
+ if (_content != null)
+ InternalChildren.Remove(_content);
+ _content = value;
+ if (_content != null)
+ InternalChildren.Add(_content);
+ OnPropertyChanged();
+ }
+ }
+
+ public Size ContentSize
+ {
+ get { return (Size)GetValue(ContentSizeProperty); }
+ private set { SetValue(ContentSizePropertyKey, value); }
+ }
+
+ public ScrollOrientation Orientation
+ {
+ get { return (ScrollOrientation)GetValue(OrientationProperty); }
+ set { SetValue(OrientationProperty, value); }
+ }
+
+ public double ScrollX
+ {
+ get { return (double)GetValue(ScrollXProperty); }
+ private set { SetValue(ScrollXPropertyKey, value); }
+ }
+
+ public double ScrollY
+ {
+ get { return (double)GetValue(ScrollYProperty); }
+ private set { SetValue(ScrollYPropertyKey, value); }
+ }
+
+ Point IScrollViewController.GetScrollPositionForElement(VisualElement item, ScrollToPosition pos)
+ {
+ ScrollToPosition position = pos;
+ double y = GetCoordinate(item, "Y", 0);
+ double x = GetCoordinate(item, "X", 0);
+
+ if (position == ScrollToPosition.MakeVisible)
+ {
+ bool isItemVisible = ScrollX < y && ScrollY + Height > y;
+ if (isItemVisible)
+ return new Point(ScrollX, ScrollY);
+ switch (Orientation)
+ {
+ case ScrollOrientation.Vertical:
+ position = y > ScrollY ? ScrollToPosition.End : ScrollToPosition.Start;
+ break;
+ case ScrollOrientation.Horizontal:
+ position = x > ScrollX ? ScrollToPosition.End : ScrollToPosition.Start;
+ break;
+ case ScrollOrientation.Both:
+ position = x > ScrollX || y > ScrollY ? ScrollToPosition.End : ScrollToPosition.Start;
+ break;
+ }
+ }
+ switch (position)
+ {
+ case ScrollToPosition.Center:
+ y = y - Height / 2 + item.Height / 2;
+ x = x - Width / 2 + item.Width / 2;
+ break;
+ case ScrollToPosition.End:
+ y = y - Height + item.Height;
+ x = x - Width + item.Width;
+ break;
+ }
+ return new Point(x, y);
+ }
+
+ event EventHandler<ScrollToRequestedEventArgs> IScrollViewController.ScrollToRequested
+ {
+ add { ScrollToRequested += value; }
+ remove { ScrollToRequested -= value; }
+ }
+
+ void IScrollViewController.SendScrollFinished()
+ {
+ if (_scrollCompletionSource != null)
+ _scrollCompletionSource.TrySetResult(true);
+ }
+
+ void IScrollViewController.SetScrolledPosition(double x, double y)
+ {
+ if (ScrollX == x && ScrollY == y)
+ return;
+
+ ScrollX = x;
+ ScrollY = y;
+
+ EventHandler<ScrolledEventArgs> handler = Scrolled;
+ if (handler != null)
+ handler(this, new ScrolledEventArgs(x, y));
+ }
+
+ public event EventHandler<ScrolledEventArgs> Scrolled;
+
+ public Task ScrollToAsync(double x, double y, bool animated)
+ {
+ var args = new ScrollToRequestedEventArgs(x, y, animated);
+ OnScrollToRequested(args);
+ return _scrollCompletionSource.Task;
+ }
+
+ public Task ScrollToAsync(Element element, ScrollToPosition position, bool animated)
+ {
+ if (!Enum.IsDefined(typeof(ScrollToPosition), position))
+ throw new ArgumentException("position is not a valid ScrollToPosition", "position");
+
+ if (element == null)
+ throw new ArgumentNullException("element");
+
+ if (!CheckElementBelongsToScrollViewer(element))
+ throw new ArgumentException("element does not belong to this ScrollVIew", "element");
+
+ var args = new ScrollToRequestedEventArgs(element, position, animated);
+ OnScrollToRequested(args);
+ return _scrollCompletionSource.Task;
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ if (_content != null)
+ {
+ SizeRequest size;
+ switch (Orientation)
+ {
+ case ScrollOrientation.Horizontal:
+ size = _content.Measure(double.PositiveInfinity, height, MeasureFlags.IncludeMargins);
+ LayoutChildIntoBoundingRegion(_content, new Rectangle(x, y, GetMaxWidth(width, size), height));
+ ContentSize = new Size(GetMaxWidth(width), height);
+ break;
+ case ScrollOrientation.Vertical:
+ size = _content.Measure(width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ LayoutChildIntoBoundingRegion(_content, new Rectangle(x, y, width, GetMaxHeight(height, size)));
+ ContentSize = new Size(width, GetMaxHeight(height));
+ break;
+ case ScrollOrientation.Both:
+ size = _content.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ LayoutChildIntoBoundingRegion(_content, new Rectangle(x, y, GetMaxWidth(width, size), GetMaxHeight(height, size)));
+ ContentSize = new Size(GetMaxWidth(width), GetMaxHeight(height));
+ break;
+ }
+ }
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ if (Content == null)
+ return new SizeRequest();
+
+ switch (Orientation)
+ {
+ case ScrollOrientation.Horizontal:
+ widthConstraint = double.PositiveInfinity;
+ break;
+ case ScrollOrientation.Vertical:
+ heightConstraint = double.PositiveInfinity;
+ break;
+ case ScrollOrientation.Both:
+ widthConstraint = double.PositiveInfinity;
+ heightConstraint = double.PositiveInfinity;
+ break;
+ }
+
+ SizeRequest contentRequest = Content.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
+ contentRequest.Minimum = new Size(Math.Min(40, contentRequest.Minimum.Width), Math.Min(40, contentRequest.Minimum.Height));
+ return contentRequest;
+ }
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ switch (Orientation)
+ {
+ case ScrollOrientation.Horizontal:
+ LayoutOptions vOptions = view.VerticalOptions;
+ if (vOptions.Alignment == LayoutAlignment.Fill && (Constraint & LayoutConstraint.VerticallyFixed) != 0)
+ {
+ view.ComputedConstraint = LayoutConstraint.VerticallyFixed;
+ }
+ break;
+ case ScrollOrientation.Vertical:
+ LayoutOptions hOptions = view.HorizontalOptions;
+ if (hOptions.Alignment == LayoutAlignment.Fill && (Constraint & LayoutConstraint.HorizontallyFixed) != 0)
+ {
+ view.ComputedConstraint = LayoutConstraint.HorizontallyFixed;
+ }
+ break;
+ case ScrollOrientation.Both:
+ view.ComputedConstraint = LayoutConstraint.None;
+ break;
+ }
+ }
+
+ bool CheckElementBelongsToScrollViewer(Element element)
+ {
+ return Equals(element, this) || element.RealParent != null && CheckElementBelongsToScrollViewer(element.RealParent);
+ }
+
+ void CheckTaskCompletionSource()
+ {
+ if (_scrollCompletionSource != null && _scrollCompletionSource.Task.Status == TaskStatus.Running)
+ {
+ _scrollCompletionSource.TrySetCanceled();
+ }
+ _scrollCompletionSource = new TaskCompletionSource<bool>();
+ }
+
+ double GetCoordinate(Element item, string coordinateName, double coordinate)
+ {
+ if (item == this)
+ return coordinate;
+ coordinate += (double)typeof(VisualElement).GetProperty(coordinateName).GetValue(item, null);
+ var visualParentElement = item.RealParent as VisualElement;
+ return visualParentElement != null ? GetCoordinate(visualParentElement, coordinateName, coordinate) : coordinate;
+ }
+
+ double GetMaxHeight(double height)
+ {
+ return Math.Max(height, _content.Bounds.Bottom + Padding.Bottom);
+ }
+
+ static double GetMaxHeight(double height, SizeRequest size)
+ {
+ return Math.Max(size.Request.Height, height);
+ }
+
+ double GetMaxWidth(double width)
+ {
+ return Math.Max(width, _content.Bounds.Right + Padding.Right);
+ }
+
+ static double GetMaxWidth(double width, SizeRequest size)
+ {
+ return Math.Max(size.Request.Width, width);
+ }
+
+ void OnScrollToRequested(ScrollToRequestedEventArgs e)
+ {
+ CheckTaskCompletionSource();
+ EventHandler<ScrollToRequestedEventArgs> handler = ScrollToRequested;
+ if (handler != null)
+ handler(this, e);
+ }
+
+ event EventHandler<ScrollToRequestedEventArgs> ScrollToRequested;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ScrolledEventArgs.cs b/Xamarin.Forms.Core/ScrolledEventArgs.cs
new file mode 100644
index 00000000..bc1d1f36
--- /dev/null
+++ b/Xamarin.Forms.Core/ScrolledEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ScrolledEventArgs : EventArgs
+ {
+ public ScrolledEventArgs(double x, double y)
+ {
+ ScrollX = x;
+ ScrollY = y;
+ }
+
+ public double ScrollX { get; private set; }
+
+ public double ScrollY { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SearchBar.cs b/Xamarin.Forms.Core/SearchBar.cs
new file mode 100644
index 00000000..089d9427
--- /dev/null
+++ b/Xamarin.Forms.Core/SearchBar.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Windows.Input;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_SearchBarRenderer))]
+ public class SearchBar : View, IFontElement
+ {
+ public static readonly BindableProperty SearchCommandProperty = BindableProperty.Create("SearchCommand", typeof(ICommand), typeof(SearchBar), null, propertyChanged: OnCommandChanged);
+
+ public static readonly BindableProperty SearchCommandParameterProperty = BindableProperty.Create("SearchCommandParameter", typeof(object), typeof(SearchBar), null);
+
+ public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(SearchBar), default(string), BindingMode.TwoWay,
+ propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var searchBar = (SearchBar)bindable;
+ EventHandler<TextChangedEventArgs> eh = searchBar.TextChanged;
+ if (eh != null)
+ eh(searchBar, new TextChangedEventArgs((string)oldValue, (string)newValue));
+ });
+
+ public static readonly BindableProperty CancelButtonColorProperty = BindableProperty.Create("CancelButtonColor", typeof(Color), typeof(SearchBar), default(Color));
+
+ public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(SearchBar), null);
+
+ public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create("FontFamily", typeof(string), typeof(SearchBar), default(string));
+
+ public static readonly BindableProperty FontSizeProperty = BindableProperty.Create("FontSize", typeof(double), typeof(SearchBar), -1.0,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (SearchBar)bindable));
+
+ public static readonly BindableProperty FontAttributesProperty = BindableProperty.Create("FontAttributes", typeof(FontAttributes), typeof(SearchBar), FontAttributes.None);
+
+ public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(SearchBar), TextAlignment.Start);
+
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create("TextColor", typeof(Color), typeof(SearchBar), Color.Default);
+
+ public static readonly BindableProperty PlaceholderColorProperty = BindableProperty.Create("PlaceholderColor", typeof(Color), typeof(SearchBar), Color.Default);
+
+ public Color CancelButtonColor
+ {
+ get { return (Color)GetValue(CancelButtonColorProperty); }
+ set { SetValue(CancelButtonColorProperty, value); }
+ }
+
+ public TextAlignment HorizontalTextAlignment
+ {
+ get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
+ set { SetValue(HorizontalTextAlignmentProperty, value); }
+ }
+
+ public string Placeholder
+ {
+ get { return (string)GetValue(PlaceholderProperty); }
+ set { SetValue(PlaceholderProperty, value); }
+ }
+
+ public Color PlaceholderColor
+ {
+ get { return (Color)GetValue(PlaceholderColorProperty); }
+ set { SetValue(PlaceholderColorProperty, value); }
+ }
+
+ public ICommand SearchCommand
+ {
+ get { return (ICommand)GetValue(SearchCommandProperty); }
+ set { SetValue(SearchCommandProperty, value); }
+ }
+
+ public object SearchCommandParameter
+ {
+ get { return GetValue(SearchCommandParameterProperty); }
+ set { SetValue(SearchCommandParameterProperty, value); }
+ }
+
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ bool IsEnabledCore
+ {
+ set { SetValueCore(IsEnabledProperty, value); }
+ }
+
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ public event EventHandler SearchButtonPressed;
+
+ public event EventHandler<TextChangedEventArgs> TextChanged;
+
+ internal void OnSearchButtonPressed()
+ {
+ ICommand cmd = SearchCommand;
+
+ if (cmd != null && !cmd.CanExecute(SearchCommandParameter))
+ return;
+
+ cmd?.Execute(SearchCommandParameter);
+ SearchButtonPressed?.Invoke(this, EventArgs.Empty);
+ }
+
+ void CommandCanExecuteChanged(object sender, EventArgs eventArgs)
+ {
+ ICommand cmd = SearchCommand;
+ if (cmd != null)
+ IsEnabledCore = cmd.CanExecute(SearchCommandParameter);
+ }
+
+ static void OnCommandChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var self = (SearchBar)bindable;
+ var newCommand = (Command)newValue;
+ var oldCommand = (Command)oldValue;
+
+ if (oldCommand != null)
+ {
+ oldCommand.CanExecuteChanged -= self.CommandCanExecuteChanged;
+ }
+
+ if (newCommand != null)
+ {
+ newCommand.CanExecuteChanged += self.CommandCanExecuteChanged;
+ self.CommandCanExecuteChanged(self, EventArgs.Empty);
+ }
+ else
+ {
+ self.IsEnabledCore = true;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs b/Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs
new file mode 100644
index 00000000..c37881f5
--- /dev/null
+++ b/Xamarin.Forms.Core/SelectedItemChangedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class SelectedItemChangedEventArgs : EventArgs
+ {
+ public SelectedItemChangedEventArgs(object selectedItem)
+ {
+ SelectedItem = selectedItem;
+ }
+
+ public object SelectedItem { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs b/Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs
new file mode 100644
index 00000000..5f696b28
--- /dev/null
+++ b/Xamarin.Forms.Core/SelectedPositionChangedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class SelectedPositionChangedEventArgs : EventArgs
+ {
+ public SelectedPositionChangedEventArgs(int selectedPosition)
+ {
+ SelectedPosition = selectedPosition;
+ }
+
+ public object SelectedPosition { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SeparatorMenuItem.cs b/Xamarin.Forms.Core/SeparatorMenuItem.cs
new file mode 100644
index 00000000..5ddf70ae
--- /dev/null
+++ b/Xamarin.Forms.Core/SeparatorMenuItem.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal class SeparatorMenuItem : BaseMenuItem
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SeparatorVisibility.cs b/Xamarin.Forms.Core/SeparatorVisibility.cs
new file mode 100644
index 00000000..6d2fdfc1
--- /dev/null
+++ b/Xamarin.Forms.Core/SeparatorVisibility.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ public enum SeparatorVisibility
+ {
+ Default = 0,
+ None = 1
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Setter.cs b/Xamarin.Forms.Core/Setter.cs
new file mode 100644
index 00000000..b260e8f0
--- /dev/null
+++ b/Xamarin.Forms.Core/Setter.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Xml;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Value")]
+ public sealed class Setter : IValueProvider
+ {
+ readonly ConditionalWeakTable<BindableObject, object> _originalValues = new ConditionalWeakTable<BindableObject, object>();
+
+ public BindableProperty Property { get; set; }
+
+ public object Value { get; set; }
+
+ object IValueProvider.ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (Property == null)
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ IXmlLineInfo lineInfo = lineInfoProvider != null ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException("Property not set", lineInfo);
+ }
+ var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider;
+
+ Func<MemberInfo> minforetriever =
+ () =>
+ (MemberInfo)Property.DeclaringType.GetRuntimeProperty(Property.PropertyName) ?? (MemberInfo)Property.DeclaringType.GetRuntimeMethod("Get" + Property.PropertyName, new[] { typeof(BindableObject) });
+
+ object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider);
+ Value = value;
+ return this;
+ }
+
+ internal void Apply(BindableObject target, bool fromStyle = false)
+ {
+ if (target == null)
+ throw new ArgumentNullException("target");
+ if (Property == null)
+ return;
+
+ object originalValue = target.GetValue(Property);
+ if (!Equals(originalValue, Property.DefaultValue))
+ {
+ _originalValues.Remove(target);
+ _originalValues.Add(target, originalValue);
+ }
+
+ var dynamicResource = Value as DynamicResource;
+ var binding = Value as BindingBase;
+ if (binding != null)
+ target.SetBinding(Property, binding.Clone(), fromStyle);
+ else if (dynamicResource != null)
+ target.SetDynamicResource(Property, dynamicResource.Key, fromStyle);
+ else
+ target.SetValue(Property, Value, fromStyle);
+ }
+
+ internal void UnApply(BindableObject target, bool fromStyle = false)
+ {
+ if (target == null)
+ throw new ArgumentNullException("target");
+ if (Property == null)
+ return;
+
+ object actual = target.GetValue(Property);
+ if (!fromStyle && !Equals(actual, Value))
+ {
+ //Do not reset default value if the value has been changed
+ _originalValues.Remove(target);
+ return;
+ }
+
+ object defaultValue;
+ if (_originalValues.TryGetValue(target, out defaultValue))
+ {
+ //reset default value, unapply bindings and dynamicResource
+ target.SetValue(Property, defaultValue, fromStyle);
+ _originalValues.Remove(target);
+ }
+ else
+ target.ClearValue(Property);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SettersExtensions.cs b/Xamarin.Forms.Core/SettersExtensions.cs
new file mode 100644
index 00000000..8f94e6d8
--- /dev/null
+++ b/Xamarin.Forms.Core/SettersExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+ public static class SettersExtensions
+ {
+ public static void Add(this IList<Setter> setters, BindableProperty property, object value)
+ {
+ setters.Add(new Setter { Property = property, Value = value });
+ }
+
+ public static void AddBinding(this IList<Setter> setters, BindableProperty property, Binding binding)
+ {
+ if (binding == null)
+ throw new ArgumentNullException("binding");
+
+ setters.Add(new Setter { Property = property, Value = binding });
+ }
+
+ public static void AddDynamicResource(this IList<Setter> setters, BindableProperty property, string key)
+ {
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException("key");
+ setters.Add(new Setter { Property = property, Value = new DynamicResource(key) });
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Size.cs b/Xamarin.Forms.Core/Size.cs
new file mode 100644
index 00000000..4e709a10
--- /dev/null
+++ b/Xamarin.Forms.Core/Size.cs
@@ -0,0 +1,110 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("Width={Width}, Height={Height}")]
+ public struct Size
+ {
+ double _width;
+ double _height;
+
+ public static readonly Size Zero;
+
+ public Size(double width, double height)
+ {
+ if (double.IsNaN(width))
+ throw new ArgumentException("NaN is not a valid value for width");
+ if (double.IsNaN(height))
+ throw new ArgumentException("NaN is not a valid value for height");
+ _width = width;
+ _height = height;
+ }
+
+ public bool IsZero
+ {
+ get { return (_width == 0) && (_height == 0); }
+ }
+
+ [DefaultValue(0d)]
+ public double Width
+ {
+ get { return _width; }
+ set
+ {
+ if (double.IsNaN(value))
+ throw new ArgumentException("NaN is not a valid value for Width");
+ _width = value;
+ }
+ }
+
+ [DefaultValue(0d)]
+ public double Height
+ {
+ get { return _height; }
+ set
+ {
+ if (double.IsNaN(value))
+ throw new ArgumentException("NaN is not a valid value for Height");
+ _height = value;
+ }
+ }
+
+ public static Size operator +(Size s1, Size s2)
+ {
+ return new Size(s1._width + s2._width, s1._height + s2._height);
+ }
+
+ public static Size operator -(Size s1, Size s2)
+ {
+ return new Size(s1._width - s2._width, s1._height - s2._height);
+ }
+
+ public static Size operator *(Size s1, double value)
+ {
+ return new Size(s1._width * value, s1._height * value);
+ }
+
+ public static bool operator ==(Size s1, Size s2)
+ {
+ return (s1._width == s2._width) && (s1._height == s2._height);
+ }
+
+ public static bool operator !=(Size s1, Size s2)
+ {
+ return (s1._width != s2._width) || (s1._height != s2._height);
+ }
+
+ public static explicit operator Point(Size size)
+ {
+ return new Point(size.Width, size.Height);
+ }
+
+ public bool Equals(Size other)
+ {
+ return _width.Equals(other._width) && _height.Equals(other._height);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ return false;
+ return obj is Size && Equals((Size)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (_width.GetHashCode() * 397) ^ _height.GetHashCode();
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{{Width={0} Height={1}}}", _width.ToString(CultureInfo.InvariantCulture), _height.ToString(CultureInfo.InvariantCulture));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SizeRequest.cs b/Xamarin.Forms.Core/SizeRequest.cs
new file mode 100644
index 00000000..3a54aca2
--- /dev/null
+++ b/Xamarin.Forms.Core/SizeRequest.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("Request={Request.Width}x{Request.Height}, Minimum={Minimum.Width}x{Minimum.Height}")]
+ public struct SizeRequest
+ {
+ public Size Request { get; set; }
+
+ public Size Minimum { get; set; }
+
+ public SizeRequest(Size request, Size minimum)
+ {
+ Request = request;
+ Minimum = minimum;
+ }
+
+ public SizeRequest(Size request)
+ {
+ Request = request;
+ Minimum = request;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{{Request={0} Minimum={1}}}", Request, Minimum);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Slider.cs b/Xamarin.Forms.Core/Slider.cs
new file mode 100644
index 00000000..6363c410
--- /dev/null
+++ b/Xamarin.Forms.Core/Slider.cs
@@ -0,0 +1,85 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_SliderRenderer))]
+ public class Slider : View
+ {
+ public static readonly BindableProperty MinimumProperty = BindableProperty.Create("Minimum", typeof(double), typeof(Slider), 0d, validateValue: (bindable, value) =>
+ {
+ var slider = (Slider)bindable;
+ return (double)value < slider.Maximum;
+ }, coerceValue: (bindable, value) =>
+ {
+ var slider = (Slider)bindable;
+ slider.Value = slider.Value.Clamp((double)value, slider.Maximum);
+ return value;
+ });
+
+ public static readonly BindableProperty MaximumProperty = BindableProperty.Create("Maximum", typeof(double), typeof(Slider), 1d, validateValue: (bindable, value) =>
+ {
+ var slider = (Slider)bindable;
+ return (double)value > slider.Minimum;
+ }, coerceValue: (bindable, value) =>
+ {
+ var slider = (Slider)bindable;
+ slider.Value = slider.Value.Clamp(slider.Minimum, (double)value);
+ return value;
+ });
+
+ public static readonly BindableProperty ValueProperty = BindableProperty.Create("Value", typeof(double), typeof(Slider), 0d, BindingMode.TwoWay, coerceValue: (bindable, value) =>
+ {
+ var slider = (Slider)bindable;
+ return ((double)value).Clamp(slider.Minimum, slider.Maximum);
+ }, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var slider = (Slider)bindable;
+ EventHandler<ValueChangedEventArgs> eh = slider.ValueChanged;
+ if (eh != null)
+ eh(slider, new ValueChangedEventArgs((double)oldValue, (double)newValue));
+ });
+
+ public Slider()
+ {
+ }
+
+ public Slider(double min, double max, double val)
+ {
+ if (min >= max)
+ throw new ArgumentOutOfRangeException("min");
+
+ if (max > Minimum)
+ {
+ Maximum = max;
+ Minimum = min;
+ }
+ else
+ {
+ Minimum = min;
+ Maximum = max;
+ }
+ Value = val.Clamp(min, max);
+ }
+
+ public double Maximum
+ {
+ get { return (double)GetValue(MaximumProperty); }
+ set { SetValue(MaximumProperty, value); }
+ }
+
+ public double Minimum
+ {
+ get { return (double)GetValue(MinimumProperty); }
+ set { SetValue(MinimumProperty, value); }
+ }
+
+ public double Value
+ {
+ get { return (double)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ public event EventHandler<ValueChangedEventArgs> ValueChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Span.cs b/Xamarin.Forms.Core/Span.cs
new file mode 100644
index 00000000..3789ddff
--- /dev/null
+++ b/Xamarin.Forms.Core/Span.cs
@@ -0,0 +1,169 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Text")]
+ public sealed class Span : INotifyPropertyChanged, IFontElement
+ {
+ Color _backgroundColor;
+
+ Font _font;
+ FontAttributes _fontAttributes;
+ string _fontFamily;
+ double _fontSize;
+
+ Color _foregroundColor;
+ bool _inUpdate; // if we ever make this thread safe we need to move to a mutex
+
+ string _text;
+
+ public Span()
+ {
+ _fontFamily = null;
+ _fontAttributes = FontAttributes.None;
+ _fontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label), true);
+ _font = Font.SystemFontOfSize(_fontSize);
+ }
+
+ public Color BackgroundColor
+ {
+ get { return _backgroundColor; }
+ set
+ {
+ if (_backgroundColor == value)
+ return;
+ _backgroundColor = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [Obsolete("Please use the Font properties directly. Obsolete in 1.3.0")]
+ public Font Font
+ {
+ get { return _font; }
+ set
+ {
+ if (_font == value)
+ return;
+ _font = value;
+ OnPropertyChanged();
+ UpdateFontPropertiesFromStruct();
+ }
+ }
+
+ public Color ForegroundColor
+ {
+ get { return _foregroundColor; }
+ set
+ {
+ if (_foregroundColor == value)
+ return;
+ _foregroundColor = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Text
+ {
+ get { return _text; }
+ set
+ {
+ if (_text == value)
+ return;
+ _text = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public FontAttributes FontAttributes
+ {
+ get { return _fontAttributes; }
+ set
+ {
+ if (_fontAttributes == value)
+ return;
+ _fontAttributes = value;
+ OnPropertyChanged();
+ UpdateStructFromFontProperties();
+ }
+ }
+
+ public string FontFamily
+ {
+ get { return _fontFamily; }
+ set
+ {
+ if (_fontFamily == value)
+ return;
+ _fontFamily = value;
+ OnPropertyChanged();
+ UpdateStructFromFontProperties();
+ }
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FontSize
+ {
+ get { return _fontSize; }
+ set
+ {
+ if (_fontSize == value)
+ return;
+ _fontSize = value;
+ OnPropertyChanged();
+ UpdateStructFromFontProperties();
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ void UpdateFontPropertiesFromStruct()
+ {
+ if (_inUpdate)
+ return;
+ _inUpdate = true;
+
+ if (Font == Font.Default)
+ {
+ FontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label), true);
+ FontFamily = null;
+ FontAttributes = FontAttributes.None;
+ }
+ else
+ {
+ FontSize = Font.UseNamedSize ? Device.GetNamedSize(Font.NamedSize, typeof(Label), true) : Font.FontSize;
+ FontFamily = Font.FontFamily;
+ FontAttributes = Font.FontAttributes;
+ }
+
+ _inUpdate = false;
+ }
+
+ void UpdateStructFromFontProperties()
+ {
+ if (_inUpdate)
+ return;
+ _inUpdate = true;
+
+ if (FontFamily != null)
+ {
+ Font = Font.OfSize(FontFamily, FontSize).WithAttributes(FontAttributes);
+ }
+ else
+ {
+ Font = Font.SystemFontOfSize(FontSize).WithAttributes(FontAttributes);
+ }
+
+ _inUpdate = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SplitOrderedList.cs b/Xamarin.Forms.Core/SplitOrderedList.cs
new file mode 100644
index 00000000..6f423210
--- /dev/null
+++ b/Xamarin.Forms.Core/SplitOrderedList.cs
@@ -0,0 +1,497 @@
+// SplitOrderedList.cs
+//
+// Copyright (c) 2010 Jérémie "Garuma" Laval
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+//
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Xamarin.Forms
+{
+ internal class SplitOrderedList<TKey, T>
+ {
+ const int MaxLoad = 5;
+ const uint BucketSize = 512;
+
+ static readonly byte[] ReverseTable =
+ {
+ 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, 4,
+ 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210,
+ 50, 178, 114, 242, 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, 14, 142, 78, 206, 46, 174, 110,
+ 238, 30, 158, 94, 222, 62, 190, 126, 254, 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, 133, 69,
+ 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179,
+ 115, 243, 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, 15, 143, 79, 207, 47, 175, 111, 239, 31,
+ 159, 95, 223, 63, 191, 127, 255
+ };
+
+ static readonly byte[] LogTable =
+ {
+ 0xFF, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7
+ };
+
+ readonly IEqualityComparer<TKey> _comparer;
+
+ readonly Node _head;
+ readonly Node _tail;
+
+ Node[] _buckets = new Node[BucketSize];
+ int _count;
+ int _size = 2;
+
+ SimpleRwLock _slim = new SimpleRwLock();
+
+ public SplitOrderedList(IEqualityComparer<TKey> comparer)
+ {
+ _comparer = comparer;
+ _head = new Node().Init(0);
+ _tail = new Node().Init(ulong.MaxValue);
+ _head.Next = _tail;
+ SetBucket(0, _head);
+ }
+
+ public int Count
+ {
+ get { return _count; }
+ }
+
+ public bool CompareExchange(uint key, TKey subKey, T data, Func<T, bool> check)
+ {
+ Node node;
+ uint b = key % (uint)_size;
+ Node bucket;
+
+ if ((bucket = GetBucket(b)) == null)
+ bucket = InitializeBucket(b);
+
+ if (!ListFind(ComputeRegularKey(key), subKey, bucket, out node))
+ return false;
+
+ if (!check(node.Data))
+ return false;
+
+ node.Data = data;
+
+ return true;
+ }
+
+ public bool Delete(uint key, TKey subKey, out T data)
+ {
+ uint b = key % (uint)_size;
+ Node bucket;
+
+ if ((bucket = GetBucket(b)) == null)
+ bucket = InitializeBucket(b);
+
+ if (!ListDelete(bucket, ComputeRegularKey(key), subKey, out data))
+ return false;
+
+ Interlocked.Decrement(ref _count);
+ return true;
+ }
+
+ public bool Find(uint key, TKey subKey, out T data)
+ {
+ Node node;
+ uint b = key % (uint)_size;
+ data = default(T);
+ Node bucket;
+
+ if ((bucket = GetBucket(b)) == null)
+ bucket = InitializeBucket(b);
+
+ if (!ListFind(ComputeRegularKey(key), subKey, bucket, out node))
+ return false;
+
+ data = node.Data;
+
+ return !node.Marked;
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ Node node = _head.Next;
+
+ while (node != _tail)
+ {
+ while (node.Marked || (node.Key & 1) == 0)
+ {
+ node = node.Next;
+ if (node == _tail)
+ yield break;
+ }
+ yield return node.Data;
+ node = node.Next;
+ }
+ }
+
+ public bool Insert(uint key, TKey subKey, T data)
+ {
+ Node current;
+ return InsertInternal(key, subKey, data, null, out current);
+ }
+
+ public T InsertOrGet(uint key, TKey subKey, T data, Func<T> dataCreator)
+ {
+ Node current;
+ InsertInternal(key, subKey, data, dataCreator, out current);
+ return current.Data;
+ }
+
+ public T InsertOrUpdate(uint key, TKey subKey, Func<T> addGetter, Func<T, T> updateGetter)
+ {
+ Node current;
+ bool result = InsertInternal(key, subKey, default(T), addGetter, out current);
+
+ if (result)
+ return current.Data;
+
+ // FIXME: this should have a CAS-like behavior
+ return current.Data = updateGetter(current.Data);
+ }
+
+ public T InsertOrUpdate(uint key, TKey subKey, T addValue, T updateValue)
+ {
+ Node current;
+ if (InsertInternal(key, subKey, addValue, null, out current))
+ return current.Data;
+
+ // FIXME: this should have a CAS-like behavior
+ return current.Data = updateValue;
+ }
+
+ // When we run out of space for bucket storage, we use a lock-based array resize
+ void CheckSegment(uint segment, bool readLockTaken)
+ {
+ if (segment < _buckets.Length)
+ return;
+
+ if (readLockTaken)
+ _slim.ExitReadLock();
+ try
+ {
+ _slim.EnterWriteLock();
+ while (segment >= _buckets.Length)
+ Array.Resize(ref _buckets, _buckets.Length * 2);
+ }
+ finally
+ {
+ _slim.ExitWriteLock();
+ }
+ if (readLockTaken)
+ _slim.EnterReadLock();
+ }
+
+ // Reverse integer bits
+ static ulong ComputeDummyKey(uint key)
+ {
+ return (ulong)(((uint)ReverseTable[key & 0xff] << 24) | ((uint)ReverseTable[(key >> 8) & 0xff] << 16) | ((uint)ReverseTable[(key >> 16) & 0xff] << 8) | ReverseTable[(key >> 24) & 0xff]) << 1;
+ }
+
+ // Reverse integer bits and make sure LSB is set
+ static ulong ComputeRegularKey(uint key)
+ {
+ return ComputeDummyKey(key) | 1;
+ }
+
+ // Bucket storage is abstracted in a simple two-layer tree to avoid too much memory resize
+ Node GetBucket(uint index)
+ {
+ if (index >= _buckets.Length)
+ return null;
+ return _buckets[index];
+ }
+
+ // Turn v's MSB off
+ static uint GetParent(uint v)
+ {
+ uint t, tt;
+
+ // Find MSB position in v
+ int pos = (tt = v >> 16) > 0 ? (t = tt >> 8) > 0 ? 24 + LogTable[t] : 16 + LogTable[tt] : (t = v >> 8) > 0 ? 8 + LogTable[t] : LogTable[v];
+
+ return (uint)(v & ~(1 << pos));
+ }
+
+ Node InitializeBucket(uint b)
+ {
+ Node current;
+ uint parent = GetParent(b);
+ Node bucket;
+
+ if ((bucket = GetBucket(parent)) == null)
+ bucket = InitializeBucket(parent);
+
+ Node dummy = new Node().Init(ComputeDummyKey(b));
+ if (!ListInsert(dummy, bucket, out current, null))
+ return current;
+
+ return SetBucket(b, dummy);
+ }
+
+ bool InsertInternal(uint key, TKey subKey, T data, Func<T> dataCreator, out Node current)
+ {
+ Node node = new Node().Init(ComputeRegularKey(key), subKey, data);
+
+ uint b = key % (uint)_size;
+ Node bucket;
+
+ if ((bucket = GetBucket(b)) == null)
+ bucket = InitializeBucket(b);
+
+ if (!ListInsert(node, bucket, out current, dataCreator))
+ return false;
+
+ int csize = _size;
+ if (Interlocked.Increment(ref _count) / csize > MaxLoad && (csize & 0x40000000) == 0)
+ Interlocked.CompareExchange(ref _size, 2 * csize, csize);
+
+ current = node;
+
+ return true;
+ }
+
+ bool ListDelete(Node startPoint, ulong key, TKey subKey, out T data)
+ {
+ Node rightNode = null, rightNodeNext = null, leftNode = null;
+ data = default(T);
+ Node markedNode = null;
+
+ do
+ {
+ rightNode = ListSearch(key, subKey, ref leftNode, startPoint);
+ if (rightNode == _tail || rightNode.Key != key || !_comparer.Equals(subKey, rightNode.SubKey))
+ return false;
+
+ data = rightNode.Data;
+ rightNodeNext = rightNode.Next;
+
+ if (!rightNodeNext.Marked)
+ {
+ if (markedNode == null)
+ markedNode = new Node();
+ markedNode.Init(rightNodeNext);
+
+ if (Interlocked.CompareExchange(ref rightNode.Next, markedNode, rightNodeNext) == rightNodeNext)
+ break;
+ }
+ } while (true);
+
+ if (Interlocked.CompareExchange(ref leftNode.Next, rightNodeNext, rightNode) != rightNode)
+ ListSearch(rightNode.Key, subKey, ref leftNode, startPoint);
+
+ return true;
+ }
+
+ bool ListFind(ulong key, TKey subKey, Node startPoint, out Node data)
+ {
+ Node rightNode = null, leftNode = null;
+ data = null;
+
+ rightNode = ListSearch(key, subKey, ref leftNode, startPoint);
+ data = rightNode;
+
+ return rightNode != _tail && rightNode.Key == key && _comparer.Equals(subKey, rightNode.SubKey);
+ }
+
+ bool ListInsert(Node newNode, Node startPoint, out Node current, Func<T> dataCreator)
+ {
+ ulong key = newNode.Key;
+ Node rightNode = null, leftNode = null;
+
+ do
+ {
+ rightNode = current = ListSearch(key, newNode.SubKey, ref leftNode, startPoint);
+ if (rightNode != _tail && rightNode.Key == key && _comparer.Equals(newNode.SubKey, rightNode.SubKey))
+ return false;
+
+ newNode.Next = rightNode;
+ if (dataCreator != null)
+ newNode.Data = dataCreator();
+ if (Interlocked.CompareExchange(ref leftNode.Next, newNode, rightNode) == rightNode)
+ return true;
+ } while (true);
+ }
+
+ Node ListSearch(ulong key, TKey subKey, ref Node left, Node h)
+ {
+ Node leftNodeNext = null, rightNode = null;
+
+ do
+ {
+ Node t = h;
+ Node tNext = t.Next;
+ do
+ {
+ if (!tNext.Marked)
+ {
+ left = t;
+ leftNodeNext = tNext;
+ }
+ t = tNext.Marked ? tNext.Next : tNext;
+ if (t == _tail)
+ break;
+
+ tNext = t.Next;
+ } while (tNext.Marked || t.Key < key || (tNext.Key == key && !_comparer.Equals(subKey, t.SubKey)));
+
+ rightNode = t;
+
+ if (leftNodeNext == rightNode)
+ {
+ if (rightNode != _tail && rightNode.Next.Marked)
+ continue;
+ return rightNode;
+ }
+
+ if (Interlocked.CompareExchange(ref left.Next, rightNode, leftNodeNext) == leftNodeNext)
+ {
+ if (rightNode != _tail && rightNode.Next.Marked)
+ continue;
+ return rightNode;
+ }
+ } while (true);
+ }
+
+ Node SetBucket(uint index, Node node)
+ {
+ try
+ {
+ _slim.EnterReadLock();
+ CheckSegment(index, true);
+
+ Interlocked.CompareExchange(ref _buckets[index], node, null);
+ return _buckets[index];
+ }
+ finally
+ {
+ _slim.ExitReadLock();
+ }
+ }
+
+ class Node
+ {
+ public T Data;
+ public ulong Key;
+ public bool Marked;
+ public Node Next;
+ public TKey SubKey;
+
+ public Node Init(ulong key, TKey subKey, T data)
+ {
+ Key = key;
+ SubKey = subKey;
+ Data = data;
+
+ Marked = false;
+ Next = null;
+
+ return this;
+ }
+
+ // Used to create dummy node
+ public Node Init(ulong key)
+ {
+ Key = key;
+ Data = default(T);
+
+ Next = null;
+ Marked = false;
+ SubKey = default(TKey);
+
+ return this;
+ }
+
+ // Used to create marked node
+ public Node Init(Node wrapped)
+ {
+ Marked = true;
+ Next = wrapped;
+
+ Key = 0;
+ Data = default(T);
+ SubKey = default(TKey);
+
+ return this;
+ }
+ }
+
+ struct SimpleRwLock
+ {
+ const int RwWait = 1;
+ const int RwWrite = 2;
+ const int RwRead = 4;
+
+ int _rwlock;
+
+ public void EnterReadLock()
+ {
+ var sw = new SpinWait();
+ do
+ {
+ while ((_rwlock & (RwWrite | RwWait)) > 0)
+ sw.SpinOnce();
+
+ if ((Interlocked.Add(ref _rwlock, RwRead) & (RwWait | RwWait)) == 0)
+ return;
+
+ Interlocked.Add(ref _rwlock, -RwRead);
+ } while (true);
+ }
+
+ public void ExitReadLock()
+ {
+ Interlocked.Add(ref _rwlock, -RwRead);
+ }
+
+ public void EnterWriteLock()
+ {
+ var sw = new SpinWait();
+ do
+ {
+ int state = _rwlock;
+ if (state < RwWrite)
+ {
+ if (Interlocked.CompareExchange(ref _rwlock, RwWrite, state) == state)
+ return;
+ state = _rwlock;
+ }
+ // We register our interest in taking the Write lock (if upgradeable it's already done)
+ while ((state & RwWait) == 0 && Interlocked.CompareExchange(ref _rwlock, state | RwWait, state) != state)
+ state = _rwlock;
+ // Before falling to sleep
+ while (_rwlock > RwWait)
+ sw.SpinOnce();
+ } while (true);
+ }
+
+ public void ExitWriteLock()
+ {
+ Interlocked.Add(ref _rwlock, -RwWrite);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/StackLayout.cs b/Xamarin.Forms.Core/StackLayout.cs
new file mode 100644
index 00000000..4dc7bfa3
--- /dev/null
+++ b/Xamarin.Forms.Core/StackLayout.cs
@@ -0,0 +1,460 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class StackLayout : Layout<View>
+ {
+ public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(StackOrientation), typeof(StackLayout), StackOrientation.Vertical,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout());
+
+ public static readonly BindableProperty SpacingProperty = BindableProperty.Create("Spacing", typeof(double), typeof(StackLayout), 6d,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout());
+
+ LayoutInformation _layoutInformation = new LayoutInformation();
+
+ public StackOrientation Orientation
+ {
+ get { return (StackOrientation)GetValue(OrientationProperty); }
+ set { SetValue(OrientationProperty, value); }
+ }
+
+ public double Spacing
+ {
+ get { return (double)GetValue(SpacingProperty); }
+ set { SetValue(SpacingProperty, value); }
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ if (!HasVisibileChildren())
+ {
+ return;
+ }
+
+ if (width == _layoutInformation.Constraint.Width && height == _layoutInformation.Constraint.Height)
+ {
+ StackOrientation orientation = Orientation;
+
+ AlignOffAxis(_layoutInformation, orientation, width, height);
+ ProcessExpanders(_layoutInformation, orientation, x, y, width, height);
+ }
+ else
+ {
+ CalculateLayout(_layoutInformation, x, y, width, height, true);
+ }
+
+ LayoutInformation layoutInformationCopy = _layoutInformation;
+
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (child.IsVisible)
+ LayoutChildIntoBoundingRegion(child, layoutInformationCopy.Plots[i], layoutInformationCopy.Requests[i]);
+ }
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ if (!HasVisibileChildren())
+ {
+ return new SizeRequest();
+ }
+
+ // calculate with padding inset for X,Y so we can hopefully re-use this in the layout pass
+ Thickness padding = Padding;
+ CalculateLayout(_layoutInformation, padding.Left, padding.Top, widthConstraint, heightConstraint, false);
+ var result = new SizeRequest(_layoutInformation.Bounds.Size, _layoutInformation.MinimumSize);
+ return result;
+ }
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ ComputeConstraintForView(view, false);
+ }
+
+ internal override void InvalidateMeasure(InvalidationTrigger trigger)
+ {
+ _layoutInformation = new LayoutInformation();
+ base.InvalidateMeasure(trigger);
+ }
+
+ void AlignOffAxis(LayoutInformation layout, StackOrientation orientation, double widthConstraint, double heightConstraint)
+ {
+ for (var i = 0; i < layout.Plots.Length; i++)
+ {
+ if (!((View)LogicalChildren[i]).IsVisible)
+ continue;
+ if (orientation == StackOrientation.Vertical)
+ {
+ layout.Plots[i].Width = widthConstraint;
+ }
+ else
+ {
+ layout.Plots[i].Height = heightConstraint;
+ }
+ }
+ }
+
+ void CalculateLayout(LayoutInformation layout, double x, double y, double widthConstraint, double heightConstraint, bool processExpanders)
+ {
+ layout.Constraint = new Size(widthConstraint, heightConstraint);
+ layout.Expanders = 0;
+ layout.CompressionSpace = 0;
+ layout.Plots = new Rectangle[Children.Count];
+ layout.Requests = new SizeRequest[Children.Count];
+
+ StackOrientation orientation = Orientation;
+
+ CalculateNaiveLayout(layout, orientation, x, y, widthConstraint, heightConstraint);
+ CompressNaiveLayout(layout, orientation, widthConstraint, heightConstraint);
+
+ if (processExpanders)
+ {
+ AlignOffAxis(layout, orientation, widthConstraint, heightConstraint);
+ ProcessExpanders(layout, orientation, x, y, widthConstraint, heightConstraint);
+ }
+ }
+
+ void CalculateNaiveLayout(LayoutInformation layout, StackOrientation orientation, double x, double y, double widthConstraint, double heightConstraint)
+ {
+ layout.CompressionSpace = 0;
+
+ double xOffset = x;
+ double yOffset = y;
+ double boundsWidth = 0;
+ double boundsHeight = 0;
+ double minimumWidth = 0;
+ double minimumHeight = 0;
+ double spacing = Spacing;
+ if (orientation == StackOrientation.Vertical)
+ {
+ View expander = null;
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (!child.IsVisible)
+ continue;
+
+ if (child.VerticalOptions.Expands)
+ {
+ layout.Expanders++;
+ if (expander != null)
+ {
+ // we have multiple expanders, make sure previous expanders are reset to not be fixed because they no logner are
+ ComputeConstraintForView(child, false);
+ }
+ expander = child;
+ }
+ SizeRequest request = child.Measure(widthConstraint, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+
+ var bounds = new Rectangle(x, yOffset, request.Request.Width, request.Request.Height);
+ layout.Plots[i] = bounds;
+ layout.Requests[i] = request;
+ layout.CompressionSpace += Math.Max(0, request.Request.Height - request.Minimum.Height);
+ yOffset = bounds.Bottom + spacing;
+
+ boundsWidth = Math.Max(boundsWidth, request.Request.Width);
+ boundsHeight = bounds.Bottom - y;
+ minimumHeight += request.Minimum.Height + spacing;
+ minimumWidth = Math.Max(minimumWidth, request.Minimum.Width);
+ }
+ minimumHeight -= spacing;
+ if (expander != null)
+ ComputeConstraintForView(expander, layout.Expanders == 1); // warning : slightly obtuse, but we either need to setup the expander or clear the last one
+ }
+ else
+ {
+ View expander = null;
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (!child.IsVisible)
+ continue;
+
+ if (child.HorizontalOptions.Expands)
+ {
+ layout.Expanders++;
+ if (expander != null)
+ {
+ ComputeConstraintForView(child, false);
+ }
+ expander = child;
+ }
+ SizeRequest request = child.Measure(double.PositiveInfinity, heightConstraint, MeasureFlags.IncludeMargins);
+
+ var bounds = new Rectangle(xOffset, y, request.Request.Width, request.Request.Height);
+ layout.Plots[i] = bounds;
+ layout.Requests[i] = request;
+ layout.CompressionSpace += Math.Max(0, request.Request.Width - request.Minimum.Width);
+ xOffset = bounds.Right + spacing;
+
+ boundsWidth = bounds.Right - x;
+ boundsHeight = Math.Max(boundsHeight, request.Request.Height);
+ minimumWidth += request.Minimum.Width + spacing;
+ minimumHeight = Math.Max(minimumHeight, request.Minimum.Height);
+ }
+ minimumWidth -= spacing;
+ if (expander != null)
+ ComputeConstraintForView(expander, layout.Expanders == 1);
+ }
+
+ layout.Bounds = new Rectangle(x, y, boundsWidth, boundsHeight);
+ layout.MinimumSize = new Size(minimumWidth, minimumHeight);
+ }
+
+ void CompressHorizontalLayout(LayoutInformation layout, double widthConstraint, double heightConstraint)
+ {
+ double xOffset = 0;
+
+ if (widthConstraint >= layout.Bounds.Width)
+ {
+ // no need to compress
+ return;
+ }
+
+ double requiredCompression = layout.Bounds.Width - widthConstraint;
+ double compressionSpace = layout.CompressionSpace;
+ double compressionPressure = (requiredCompression / layout.CompressionSpace).Clamp(0, 1);
+
+ for (var i = 0; i < layout.Plots.Length; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (!child.IsVisible)
+ continue;
+
+ Size minimum = layout.Requests[i].Minimum;
+
+ layout.Plots[i].X -= xOffset;
+
+ Rectangle plot = layout.Plots[i];
+ double availableSpace = plot.Width - minimum.Width;
+ if (availableSpace <= 0)
+ continue;
+
+ compressionSpace -= availableSpace;
+
+ double compression = availableSpace * compressionPressure;
+ xOffset += compression;
+
+ double newWidth = plot.Width - compression;
+ SizeRequest newRequest = child.Measure(newWidth, heightConstraint, MeasureFlags.IncludeMargins);
+
+ layout.Requests[i] = newRequest;
+
+ plot.Height = newRequest.Request.Height;
+
+ if (newRequest.Request.Width < newWidth)
+ {
+ double delta = newWidth - newRequest.Request.Width;
+ newWidth = newRequest.Request.Width;
+ xOffset += delta;
+ requiredCompression = requiredCompression - xOffset;
+ compressionPressure = (requiredCompression / compressionSpace).Clamp(0, 1);
+ }
+ plot.Width = newWidth;
+
+ layout.Bounds.Height = Math.Max(layout.Bounds.Height, plot.Height);
+
+ layout.Plots[i] = plot;
+ }
+ }
+
+ void CompressNaiveLayout(LayoutInformation layout, StackOrientation orientation, double widthConstraint, double heightConstraint)
+ {
+ if (layout.CompressionSpace <= 0)
+ return;
+
+ if (orientation == StackOrientation.Vertical)
+ {
+ CompressVerticalLayout(layout, widthConstraint, heightConstraint);
+ }
+ else
+ {
+ CompressHorizontalLayout(layout, widthConstraint, heightConstraint);
+ }
+ }
+
+ void CompressVerticalLayout(LayoutInformation layout, double widthConstraint, double heightConstraint)
+ {
+ double yOffset = 0;
+
+ if (heightConstraint >= layout.Bounds.Height)
+ {
+ // no need to compress
+ return;
+ }
+
+ double requiredCompression = layout.Bounds.Height - heightConstraint;
+ double compressionSpace = layout.CompressionSpace;
+ double compressionPressure = (requiredCompression / layout.CompressionSpace).Clamp(0, 1);
+
+ for (var i = 0; i < layout.Plots.Length; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (!child.IsVisible)
+ continue;
+
+ Size minimum = layout.Requests[i].Minimum;
+
+ layout.Plots[i].Y -= yOffset;
+
+ Rectangle plot = layout.Plots[i];
+ double availableSpace = plot.Height - minimum.Height;
+ if (availableSpace <= 0)
+ continue;
+
+ compressionSpace -= availableSpace;
+
+ double compression = availableSpace * compressionPressure;
+ yOffset += compression;
+
+ double newHeight = plot.Height - compression;
+ SizeRequest newRequest = child.Measure(widthConstraint, newHeight, MeasureFlags.IncludeMargins);
+
+ layout.Requests[i] = newRequest;
+
+ plot.Width = newRequest.Request.Width;
+
+ if (newRequest.Request.Height < newHeight)
+ {
+ double delta = newHeight - newRequest.Request.Height;
+ newHeight = newRequest.Request.Height;
+ yOffset += delta;
+ requiredCompression = requiredCompression - yOffset;
+ compressionPressure = (requiredCompression / compressionSpace).Clamp(0, 1);
+ }
+ plot.Height = newHeight;
+
+ layout.Bounds.Width = Math.Max(layout.Bounds.Width, plot.Width);
+
+ layout.Plots[i] = plot;
+ }
+ }
+
+ void ComputeConstraintForView(View view, bool isOnlyExpander)
+ {
+ if (Orientation == StackOrientation.Horizontal)
+ {
+ if ((Constraint & LayoutConstraint.VerticallyFixed) != 0 && view.VerticalOptions.Alignment == LayoutAlignment.Fill)
+ {
+ if (isOnlyExpander && view.HorizontalOptions.Alignment == LayoutAlignment.Fill && Constraint == LayoutConstraint.Fixed)
+ {
+ view.ComputedConstraint = LayoutConstraint.Fixed;
+ }
+ else
+ {
+ view.ComputedConstraint = LayoutConstraint.VerticallyFixed;
+ }
+ }
+ else
+ {
+ view.ComputedConstraint = LayoutConstraint.None;
+ }
+ }
+ else
+ {
+ if ((Constraint & LayoutConstraint.HorizontallyFixed) != 0 && view.HorizontalOptions.Alignment == LayoutAlignment.Fill)
+ {
+ if (isOnlyExpander && view.VerticalOptions.Alignment == LayoutAlignment.Fill && Constraint == LayoutConstraint.Fixed)
+ {
+ view.ComputedConstraint = LayoutConstraint.Fixed;
+ }
+ else
+ {
+ view.ComputedConstraint = LayoutConstraint.HorizontallyFixed;
+ }
+ }
+ else
+ {
+ view.ComputedConstraint = LayoutConstraint.None;
+ }
+ }
+ }
+
+ bool HasVisibileChildren()
+ {
+ for (var index = 0; index < InternalChildren.Count; index++)
+ {
+ var child = (VisualElement)InternalChildren[index];
+ if (child.IsVisible)
+ return true;
+ }
+ return false;
+ }
+
+ void ProcessExpanders(LayoutInformation layout, StackOrientation orientation, double x, double y, double widthConstraint, double heightConstraint)
+ {
+ if (layout.Expanders <= 0)
+ return;
+
+ if (orientation == StackOrientation.Vertical)
+ {
+ double extraSpace = heightConstraint - layout.Bounds.Height;
+ if (extraSpace <= 0)
+ return;
+
+ double spacePerExpander = extraSpace / layout.Expanders;
+ double yOffset = 0;
+
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (!child.IsVisible)
+ continue;
+ Rectangle plot = layout.Plots[i];
+ plot.Y += yOffset;
+
+ if (child.VerticalOptions.Expands)
+ {
+ plot.Height += spacePerExpander;
+ yOffset += spacePerExpander;
+ }
+
+ layout.Plots[i] = plot;
+ }
+
+ layout.Bounds.Height = heightConstraint;
+ }
+ else
+ {
+ double extraSpace = widthConstraint - layout.Bounds.Width;
+ if (extraSpace <= 0)
+ return;
+
+ double spacePerExpander = extraSpace / layout.Expanders;
+ double xOffset = 0;
+
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var child = (View)LogicalChildren[i];
+ if (!child.IsVisible)
+ continue;
+ Rectangle plot = layout.Plots[i];
+ plot.X += xOffset;
+
+ if (child.HorizontalOptions.Expands)
+ {
+ plot.Width += spacePerExpander;
+ xOffset += spacePerExpander;
+ }
+
+ layout.Plots[i] = plot;
+ }
+
+ layout.Bounds.Width = widthConstraint;
+ }
+ }
+
+ class LayoutInformation
+ {
+ public Rectangle Bounds;
+ public double CompressionSpace;
+ public Size Constraint;
+ public int Expanders;
+ public Size MinimumSize;
+ public Rectangle[] Plots;
+ public SizeRequest[] Requests;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/StackOrientation.cs b/Xamarin.Forms.Core/StackOrientation.cs
new file mode 100644
index 00000000..4ab00fd2
--- /dev/null
+++ b/Xamarin.Forms.Core/StackOrientation.cs
@@ -0,0 +1,8 @@
+namespace Xamarin.Forms
+{
+ public enum StackOrientation
+ {
+ Vertical,
+ Horizontal
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Stepper.cs b/Xamarin.Forms.Core/Stepper.cs
new file mode 100644
index 00000000..2a3de841
--- /dev/null
+++ b/Xamarin.Forms.Core/Stepper.cs
@@ -0,0 +1,94 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_StepperRenderer))]
+ public class Stepper : View
+ {
+ public static readonly BindableProperty MaximumProperty = BindableProperty.Create("Maximum", typeof(double), typeof(Stepper), 100.0, validateValue: (bindable, value) =>
+ {
+ var stepper = (Stepper)bindable;
+ return (double)value > stepper.Minimum;
+ }, coerceValue: (bindable, value) =>
+ {
+ var stepper = (Stepper)bindable;
+ stepper.Value = stepper.Value.Clamp(stepper.Minimum, (double)value);
+ return value;
+ });
+
+ public static readonly BindableProperty MinimumProperty = BindableProperty.Create("Minimum", typeof(double), typeof(Stepper), 0.0, validateValue: (bindable, value) =>
+ {
+ var stepper = (Stepper)bindable;
+ return (double)value < stepper.Maximum;
+ }, coerceValue: (bindable, value) =>
+ {
+ var stepper = (Stepper)bindable;
+ stepper.Value = stepper.Value.Clamp((double)value, stepper.Maximum);
+ return value;
+ });
+
+ public static readonly BindableProperty ValueProperty = BindableProperty.Create("Value", typeof(double), typeof(Stepper), 0.0, BindingMode.TwoWay, coerceValue: (bindable, value) =>
+ {
+ var stepper = (Stepper)bindable;
+ return ((double)value).Clamp(stepper.Minimum, stepper.Maximum);
+ }, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var stepper = (Stepper)bindable;
+ EventHandler<ValueChangedEventArgs> eh = stepper.ValueChanged;
+ if (eh != null)
+ eh(stepper, new ValueChangedEventArgs((double)oldValue, (double)newValue));
+ });
+
+ public static readonly BindableProperty IncrementProperty = BindableProperty.Create("Increment", typeof(double), typeof(Stepper), 1.0);
+
+ public Stepper()
+ {
+ }
+
+ public Stepper(double min, double max, double val, double increment)
+ {
+ if (min >= max)
+ throw new ArgumentOutOfRangeException("min");
+ if (max > Minimum)
+ {
+ Maximum = max;
+ Minimum = min;
+ }
+ else
+ {
+ Minimum = min;
+ Maximum = max;
+ }
+
+ Value = val.Clamp(min, max);
+ Increment = increment;
+ }
+
+ public double Increment
+ {
+ get { return (double)GetValue(IncrementProperty); }
+ set { SetValue(IncrementProperty, value); }
+ }
+
+ public double Maximum
+ {
+ get { return (double)GetValue(MaximumProperty); }
+ set { SetValue(MaximumProperty, value); }
+ }
+
+ public double Minimum
+ {
+ get { return (double)GetValue(MinimumProperty); }
+ set { SetValue(MinimumProperty, value); }
+ }
+
+ public double Value
+ {
+ get { return (double)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ public event EventHandler<ValueChangedEventArgs> ValueChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/StreamImageSource.cs b/Xamarin.Forms.Core/StreamImageSource.cs
new file mode 100644
index 00000000..ae59ea3c
--- /dev/null
+++ b/Xamarin.Forms.Core/StreamImageSource.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ public class StreamImageSource : ImageSource
+ {
+ public static readonly BindableProperty StreamProperty = BindableProperty.Create("Stream", typeof(Func<CancellationToken, Task<Stream>>), typeof(StreamImageSource),
+ default(Func<CancellationToken, Task<Stream>>));
+
+ public virtual Func<CancellationToken, Task<Stream>> Stream
+ {
+ get { return (Func<CancellationToken, Task<Stream>>)GetValue(StreamProperty); }
+ set { SetValue(StreamProperty, value); }
+ }
+
+ protected override void OnPropertyChanged(string propertyName)
+ {
+ if (propertyName == StreamProperty.PropertyName)
+ OnSourceChanged();
+ base.OnPropertyChanged(propertyName);
+ }
+
+ internal async Task<Stream> GetStreamAsync(CancellationToken userToken = default(CancellationToken))
+ {
+ if (Stream == null)
+ return null;
+
+ OnLoadingStarted();
+ userToken.Register(CancellationTokenSource.Cancel);
+ Stream stream = null;
+ try
+ {
+ stream = await Stream(CancellationTokenSource.Token);
+ OnLoadingCompleted(false);
+ }
+ catch (OperationCanceledException)
+ {
+ OnLoadingCompleted(true);
+ throw;
+ }
+ return stream;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/StreamWrapper.cs b/Xamarin.Forms.Core/StreamWrapper.cs
new file mode 100644
index 00000000..5d4e2cae
--- /dev/null
+++ b/Xamarin.Forms.Core/StreamWrapper.cs
@@ -0,0 +1,81 @@
+using System;
+using System.IO;
+
+namespace Xamarin.Forms
+{
+ internal class StreamWrapper : Stream
+ {
+ readonly Stream _wrapped;
+
+ public StreamWrapper(Stream wrapped)
+ {
+ if (wrapped == null)
+ throw new ArgumentNullException("wrapped");
+
+ _wrapped = wrapped;
+ }
+
+ public override bool CanRead
+ {
+ get { return _wrapped.CanRead; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return _wrapped.CanSeek; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return _wrapped.CanWrite; }
+ }
+
+ public override long Length
+ {
+ get { return _wrapped.Length; }
+ }
+
+ public override long Position
+ {
+ get { return _wrapped.Position; }
+ set { _wrapped.Position = value; }
+ }
+
+ public event EventHandler Disposed;
+
+ public override void Flush()
+ {
+ _wrapped.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return _wrapped.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return _wrapped.Seek(offset, origin);
+ }
+
+ public override void SetLength(long value)
+ {
+ _wrapped.SetLength(value);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ _wrapped.Write(buffer, offset, count);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _wrapped.Dispose();
+ EventHandler eh = Disposed;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+
+ base.Dispose(disposing);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Style.cs b/Xamarin.Forms.Core/Style.cs
new file mode 100644
index 00000000..8d27f010
--- /dev/null
+++ b/Xamarin.Forms.Core/Style.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Setters")]
+ public sealed class Style : IStyle
+ {
+ internal const string StyleClassPrefix = "Xamarin.Forms.StyleClass.";
+
+ readonly BindableProperty _basedOnResourceProperty = BindableProperty.CreateAttached("BasedOnResource", typeof(Style), typeof(Style), default(Style),
+ propertyChanged: OnBasedOnResourceChanged);
+
+ readonly List<WeakReference<BindableObject>> _targets = new List<WeakReference<BindableObject>>(4);
+
+ Style _basedOnStyle;
+
+ string _baseResourceKey;
+
+ IList<Behavior> _behaviors;
+
+ IList<TriggerBase> _triggers;
+
+ public Style([TypeConverter(typeof(TypeTypeConverter))] [Parameter("TargetType")] Type targetType)
+ {
+ if (targetType == null)
+ throw new ArgumentNullException("targetType");
+
+ TargetType = targetType;
+ Setters = new List<Setter>();
+ }
+
+ public bool ApplyToDerivedTypes { get; set; }
+
+ public Style BasedOn
+ {
+ get { return _basedOnStyle; }
+ set
+ {
+ if (_basedOnStyle == value)
+ return;
+ if (!ValidateBasedOn(value))
+ throw new ArgumentException("BasedOn.TargetType is not compatible with TargetType");
+ Style oldValue = _basedOnStyle;
+ _basedOnStyle = value;
+ BasedOnChanged(oldValue, value);
+ if (value != null)
+ BaseResourceKey = null;
+ }
+ }
+
+ public string BaseResourceKey
+ {
+ get { return _baseResourceKey; }
+ set
+ {
+ if (_baseResourceKey == value)
+ return;
+ _baseResourceKey = value;
+ //update all DynamicResources
+ foreach (WeakReference<BindableObject> bindableWr in _targets)
+ {
+ BindableObject target;
+ if (!bindableWr.TryGetTarget(out target))
+ continue;
+ target.RemoveDynamicResource(_basedOnResourceProperty);
+ if (value != null)
+ target.SetDynamicResource(_basedOnResourceProperty, value);
+ }
+ if (value != null)
+ BasedOn = null;
+ }
+ }
+
+ public IList<Behavior> Behaviors
+ {
+ get { return _behaviors ?? (_behaviors = new AttachedCollection<Behavior>()); }
+ }
+
+ public bool CanCascade { get; set; }
+
+ public string Class { get; set; }
+
+ public IList<Setter> Setters { get; }
+
+ public IList<TriggerBase> Triggers
+ {
+ get { return _triggers ?? (_triggers = new AttachedCollection<TriggerBase>()); }
+ }
+
+ void IStyle.Apply(BindableObject bindable)
+ {
+ _targets.Add(new WeakReference<BindableObject>(bindable));
+ if (BaseResourceKey != null)
+ bindable.SetDynamicResource(_basedOnResourceProperty, BaseResourceKey);
+ ApplyCore(bindable, BasedOn ?? GetBasedOnResource(bindable));
+ }
+
+ public Type TargetType { get; }
+
+ void IStyle.UnApply(BindableObject bindable)
+ {
+ UnApplyCore(bindable, BasedOn ?? GetBasedOnResource(bindable));
+ bindable.RemoveDynamicResource(_basedOnResourceProperty);
+ _targets.RemoveAll(wr =>
+ {
+ BindableObject target;
+ return wr.TryGetTarget(out target) && target == bindable;
+ });
+ }
+
+ internal bool CanBeAppliedTo(Type targetType)
+ {
+ if (TargetType == targetType)
+ return true;
+ if (!ApplyToDerivedTypes)
+ return false;
+ do
+ {
+ targetType = targetType.GetTypeInfo().BaseType;
+ if (TargetType == targetType)
+ return true;
+ } while (targetType != typeof(Element));
+ return false;
+ }
+
+ void ApplyCore(BindableObject bindable, Style basedOn)
+ {
+ if (basedOn != null)
+ ((IStyle)basedOn).Apply(bindable);
+
+ foreach (Setter setter in Setters)
+ setter.Apply(bindable, true);
+ ((AttachedCollection<Behavior>)Behaviors).AttachTo(bindable);
+ ((AttachedCollection<TriggerBase>)Triggers).AttachTo(bindable);
+ }
+
+ void BasedOnChanged(Style oldValue, Style newValue)
+ {
+ foreach (WeakReference<BindableObject> bindableRef in _targets)
+ {
+ BindableObject bindable;
+ if (!bindableRef.TryGetTarget(out bindable))
+ continue;
+
+ UnApplyCore(bindable, oldValue);
+ ApplyCore(bindable, newValue);
+ }
+ }
+
+ Style GetBasedOnResource(BindableObject bindable)
+ {
+ return (Style)bindable.GetValue(_basedOnResourceProperty);
+ }
+
+ static void OnBasedOnResourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ Style style = (bindable as VisualElement).Style;
+ if (style == null)
+ return;
+ style.UnApplyCore(bindable, (Style)oldValue);
+ style.ApplyCore(bindable, (Style)newValue);
+ }
+
+ void UnApplyCore(BindableObject bindable, Style basedOn)
+ {
+ ((AttachedCollection<TriggerBase>)Triggers).DetachFrom(bindable);
+ ((AttachedCollection<Behavior>)Behaviors).DetachFrom(bindable);
+ foreach (Setter setter in Setters)
+ setter.UnApply(bindable, true);
+
+ if (basedOn != null)
+ ((IStyle)basedOn).UnApply(bindable);
+ }
+
+ bool ValidateBasedOn(Style value)
+ {
+ if (value == null)
+ return true;
+ return value.TargetType.IsAssignableFrom(TargetType);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Switch.cs b/Xamarin.Forms.Core/Switch.cs
new file mode 100644
index 00000000..394e050a
--- /dev/null
+++ b/Xamarin.Forms.Core/Switch.cs
@@ -0,0 +1,24 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_SwitchRenderer))]
+ public class Switch : View
+ {
+ public static readonly BindableProperty IsToggledProperty = BindableProperty.Create("IsToggled", typeof(bool), typeof(Switch), false, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ EventHandler<ToggledEventArgs> eh = ((Switch)bindable).Toggled;
+ if (eh != null)
+ eh(bindable, new ToggledEventArgs((bool)newValue));
+ }, defaultBindingMode: BindingMode.TwoWay);
+
+ public bool IsToggled
+ {
+ get { return (bool)GetValue(IsToggledProperty); }
+ set { SetValue(IsToggledProperty, value); }
+ }
+
+ public event EventHandler<ToggledEventArgs> Toggled;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/SynchronizedList.cs b/Xamarin.Forms.Core/SynchronizedList.cs
new file mode 100644
index 00000000..edc6f575
--- /dev/null
+++ b/Xamarin.Forms.Core/SynchronizedList.cs
@@ -0,0 +1,130 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal class SynchronizedList<T> : IList<T>, IReadOnlyList<T>
+ {
+ readonly List<T> _list = new List<T>();
+ ReadOnlyCollection<T> _snapshot;
+
+ public void Add(T item)
+ {
+ lock(_list)
+ {
+ _list.Add(item);
+ _snapshot = null;
+ }
+ }
+
+ public void Clear()
+ {
+ lock(_list)
+ {
+ _list.Clear();
+ _snapshot = null;
+ }
+ }
+
+ public bool Contains(T item)
+ {
+ lock(_list)
+ return _list.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ lock(_list)
+ _list.CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return _list.Count; }
+ }
+
+ bool ICollection<T>.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ public bool Remove(T item)
+ {
+ lock(_list)
+ {
+ if (_list.Remove(item))
+ {
+ _snapshot = null;
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ ReadOnlyCollection<T> snap = _snapshot;
+ if (snap == null)
+ {
+ lock(_list)
+ _snapshot = snap = new ReadOnlyCollection<T>(_list.ToList());
+ }
+
+ return snap.GetEnumerator();
+ }
+
+ public int IndexOf(T item)
+ {
+ lock(_list)
+ return _list.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ lock(_list)
+ {
+ _list.Insert(index, item);
+ _snapshot = null;
+ }
+ }
+
+ public T this[int index]
+ {
+ get
+ {
+ ReadOnlyCollection<T> snap = _snapshot;
+ if (snap != null)
+ return snap[index];
+
+ lock(_list)
+ return _list[index];
+ }
+
+ set
+ {
+ lock(_list)
+ {
+ _list[index] = value;
+ _snapshot = null;
+ }
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ lock(_list)
+ {
+ _list.RemoveAt(index);
+ _snapshot = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TabbedPage.cs b/Xamarin.Forms.Core/TabbedPage.cs
new file mode 100644
index 00000000..53b736cb
--- /dev/null
+++ b/Xamarin.Forms.Core/TabbedPage.cs
@@ -0,0 +1,17 @@
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_TabbedPageRenderer))]
+ public class TabbedPage : MultiPage<Page>
+ {
+ protected override Page CreateDefault(object item)
+ {
+ var page = new Page();
+ if (item != null)
+ page.Title = item.ToString();
+
+ return page;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TableIntent.cs b/Xamarin.Forms.Core/TableIntent.cs
new file mode 100644
index 00000000..7d4737ca
--- /dev/null
+++ b/Xamarin.Forms.Core/TableIntent.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum TableIntent
+ {
+ Menu,
+ Settings,
+ Form,
+ Data
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TableModel.cs b/Xamarin.Forms.Core/TableModel.cs
new file mode 100644
index 00000000..866b7a69
--- /dev/null
+++ b/Xamarin.Forms.Core/TableModel.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal abstract class TableModel
+ {
+ public virtual Cell GetCell(int section, int row)
+ {
+ object item = GetItem(section, row);
+ var cell = item as Cell;
+ if (cell != null)
+ return cell;
+
+ return new TextCell { Text = item.ToString() };
+ }
+
+ public virtual Cell GetHeaderCell(int section)
+ {
+ return null;
+ }
+
+ public abstract object GetItem(int section, int row);
+
+ public abstract int GetRowCount(int section);
+
+ public abstract int GetSectionCount();
+
+ public virtual string[] GetSectionIndexTitles()
+ {
+ return null;
+ }
+
+ public virtual string GetSectionTitle(int section)
+ {
+ return null;
+ }
+
+ public event EventHandler<EventArg<object>> ItemLongPressed;
+
+ public event EventHandler<EventArg<object>> ItemSelected;
+
+ public void RowLongPressed(int section, int row)
+ {
+ RowLongPressed(GetItem(section, row));
+ }
+
+ public void RowLongPressed(object item)
+ {
+ if (ItemLongPressed != null)
+ ItemLongPressed(this, new EventArg<object>(item));
+
+ OnRowLongPressed(item);
+ }
+
+ public void RowSelected(int section, int row)
+ {
+ RowSelected(GetItem(section, row));
+ }
+
+ public void RowSelected(object item)
+ {
+ if (ItemSelected != null)
+ ItemSelected(this, new EventArg<object>(item));
+
+ OnRowSelected(item);
+ }
+
+ protected virtual void OnRowLongPressed(object item)
+ {
+ }
+
+ protected virtual void OnRowSelected(object item)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TableRoot.cs b/Xamarin.Forms.Core/TableRoot.cs
new file mode 100644
index 00000000..60ef6d95
--- /dev/null
+++ b/Xamarin.Forms.Core/TableRoot.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+
+namespace Xamarin.Forms
+{
+ public sealed class TableRoot : TableSectionBase<TableSection>
+ {
+ public TableRoot()
+ {
+ SetupEvents();
+ }
+
+ public TableRoot(string title) : base(title)
+ {
+ SetupEvents();
+ }
+
+ internal event EventHandler<ChildCollectionChangedEventArgs> SectionCollectionChanged;
+
+ void ChildCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ EventHandler<ChildCollectionChangedEventArgs> handler = SectionCollectionChanged;
+ if (handler != null)
+ handler(this, new ChildCollectionChangedEventArgs(notifyCollectionChangedEventArgs));
+ }
+
+ void ChildPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
+ {
+ if (propertyChangedEventArgs.PropertyName == TitleProperty.PropertyName)
+ {
+ OnPropertyChanged(TitleProperty.PropertyName);
+ }
+ }
+
+ void SetupEvents()
+ {
+ CollectionChanged += (sender, args) =>
+ {
+ if (args.NewItems != null)
+ {
+ foreach (TableSection section in args.NewItems)
+ {
+ section.CollectionChanged += ChildCollectionChanged;
+ section.PropertyChanged += ChildPropertyChanged;
+ }
+ }
+
+ if (args.OldItems != null)
+ {
+ foreach (TableSection section in args.OldItems)
+ {
+ section.CollectionChanged -= ChildCollectionChanged;
+ section.PropertyChanged -= ChildPropertyChanged;
+ }
+ }
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TableSection.cs b/Xamarin.Forms.Core/TableSection.cs
new file mode 100644
index 00000000..9ae7caf5
--- /dev/null
+++ b/Xamarin.Forms.Core/TableSection.cs
@@ -0,0 +1,137 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace Xamarin.Forms
+{
+ public abstract class TableSectionBase<T> : TableSectionBase, IList<T>, INotifyCollectionChanged where T : BindableObject
+ {
+ readonly ObservableCollection<T> _children = new ObservableCollection<T>();
+
+ /// <summary>
+ /// Constructs a Section without an empty header.
+ /// </summary>
+ protected TableSectionBase()
+ {
+ _children.CollectionChanged += OnChildrenChanged;
+ }
+
+ /// <summary>
+ /// Constructs a Section with the specified header.
+ /// </summary>
+ protected TableSectionBase(string title) : base(title)
+ {
+ _children.CollectionChanged += OnChildrenChanged;
+ }
+
+ public void Add(T item)
+ {
+ _children.Add(item);
+ }
+
+ public void Clear()
+ {
+ _children.Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ return _children.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ _children.CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return _children.Count; }
+ }
+
+ bool ICollection<T>.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ public bool Remove(T item)
+ {
+ return _children.Remove(item);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ return _children.GetEnumerator();
+ }
+
+ public int IndexOf(T item)
+ {
+ return _children.IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ _children.Insert(index, item);
+ }
+
+ public T this[int index]
+ {
+ get { return _children[index]; }
+ set { _children[index] = value; }
+ }
+
+ public void RemoveAt(int index)
+ {
+ _children.RemoveAt(index);
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged
+ {
+ add { _children.CollectionChanged += value; }
+ remove { _children.CollectionChanged -= value; }
+ }
+
+ public void Add(IEnumerable<T> items)
+ {
+ items.ForEach(_children.Add);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ object bc = BindingContext;
+ foreach (T child in _children)
+ SetInheritedBindingContext(child, bc);
+ }
+
+ void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ // We need to hook up the binding context for new items.
+ if (notifyCollectionChangedEventArgs.NewItems == null)
+ return;
+ object bc = BindingContext;
+ foreach (BindableObject item in notifyCollectionChangedEventArgs.NewItems)
+ {
+ SetInheritedBindingContext(item, bc);
+ }
+ }
+ }
+
+ public sealed class TableSection : TableSectionBase<Cell>
+ {
+ public TableSection()
+ {
+ }
+
+ public TableSection(string title) : base(title)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TableSectionBase.cs b/Xamarin.Forms.Core/TableSectionBase.cs
new file mode 100644
index 00000000..9cfa5351
--- /dev/null
+++ b/Xamarin.Forms.Core/TableSectionBase.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class TableSectionBase : BindableObject
+ {
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(TableSectionBase), null);
+
+ /// <summary>
+ /// Constructs a Section without an empty header.
+ /// </summary>
+ protected TableSectionBase()
+ {
+ }
+
+ /// <summary>
+ /// Constructs a Section with the specified header.
+ /// </summary>
+ protected TableSectionBase(string title)
+ {
+ if (title == null)
+ throw new ArgumentNullException("title");
+
+ Title = title;
+ }
+
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set { SetValue(TitleProperty, value); }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TableView.cs b/Xamarin.Forms.Core/TableView.cs
new file mode 100644
index 00000000..a88999b7
--- /dev/null
+++ b/Xamarin.Forms.Core/TableView.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [ContentProperty("Root")]
+ [RenderWith(typeof(_TableViewRenderer))]
+ public class TableView : View
+ {
+ public static readonly BindableProperty RowHeightProperty = BindableProperty.Create("RowHeight", typeof(int), typeof(TableView), -1);
+
+ public static readonly BindableProperty HasUnevenRowsProperty = BindableProperty.Create("HasUnevenRows", typeof(bool), typeof(TableView), false);
+
+ readonly TableSectionModel _tableModel;
+
+ TableIntent _intent = TableIntent.Data;
+
+ TableModel _model;
+
+ public TableView() : this(null)
+ {
+ }
+
+ public TableView(TableRoot root)
+ {
+ VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
+ Model = _tableModel = new TableSectionModel(this, root);
+ }
+
+ public bool HasUnevenRows
+ {
+ get { return (bool)GetValue(HasUnevenRowsProperty); }
+ set { SetValue(HasUnevenRowsProperty, value); }
+ }
+
+ public TableIntent Intent
+ {
+ get { return _intent; }
+ set
+ {
+ if (_intent == value)
+ return;
+
+ OnPropertyChanging();
+ _intent = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public TableRoot Root
+ {
+ get { return _tableModel.Root; }
+ set
+ {
+ if (_tableModel.Root != null)
+ {
+ _tableModel.Root.SectionCollectionChanged -= OnSectionCollectionChanged;
+ _tableModel.Root.PropertyChanged -= OnTableModelRootPropertyChanged;
+ }
+ _tableModel.Root = value ?? new TableRoot();
+ SetInheritedBindingContext(_tableModel.Root, BindingContext);
+
+ Root.SelectMany(r => r).ForEach(cell => cell.Parent = this);
+ _tableModel.Root.SectionCollectionChanged += OnSectionCollectionChanged;
+ _tableModel.Root.PropertyChanged += OnTableModelRootPropertyChanged;
+ OnModelChanged();
+ }
+ }
+
+ public int RowHeight
+ {
+ get { return (int)GetValue(RowHeightProperty); }
+ set { SetValue(RowHeightProperty, value); }
+ }
+
+ internal TableModel Model
+ {
+ get { return _model; }
+ set
+ {
+ _model = value;
+ OnModelChanged();
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (Root != null)
+ SetInheritedBindingContext(Root, BindingContext);
+ }
+
+ protected virtual void OnModelChanged()
+ {
+ foreach (Cell cell in Root.SelectMany(r => r))
+ cell.Parent = this;
+
+ if (ModelChanged != null)
+ ModelChanged(this, EventArgs.Empty);
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ var minimumSize = new Size(40, 40);
+ double width = Math.Min(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height);
+ var request = new Size(width, Math.Max(Device.Info.ScaledScreenSize.Width, Device.Info.ScaledScreenSize.Height));
+
+ return new SizeRequest(request, minimumSize);
+ }
+
+ internal event EventHandler ModelChanged;
+
+ void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ OnModelChanged();
+ }
+
+ void OnSectionCollectionChanged(object sender, ChildCollectionChangedEventArgs childCollectionChangedEventArgs)
+ {
+ if (childCollectionChangedEventArgs.Args.NewItems != null)
+ childCollectionChangedEventArgs.Args.NewItems.Cast<Cell>().ForEach(cell => cell.Parent = this);
+ OnModelChanged();
+ }
+
+ void OnTableModelRootPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == TableSectionBase.TitleProperty.PropertyName)
+ OnModelChanged();
+ }
+
+ internal class TableSectionModel : TableModel
+ {
+ static readonly BindableProperty PathProperty = BindableProperty.Create("Path", typeof(Tuple<int, int>), typeof(Cell), null);
+
+ readonly TableView _parent;
+ TableRoot _root;
+
+ public TableSectionModel(TableView tableParent, TableRoot tableRoot)
+ {
+ _parent = tableParent;
+ Root = tableRoot ?? new TableRoot();
+ }
+
+ public TableRoot Root
+ {
+ get { return _root; }
+ set
+ {
+ if (_root == value)
+ return;
+
+ RemoveEvents(_root);
+ _root = value;
+ ApplyEvents(_root);
+ }
+ }
+
+ public override Cell GetCell(int section, int row)
+ {
+ var cell = (Cell)GetItem(section, row);
+ SetPath(cell, new Tuple<int, int>(section, row));
+ return cell;
+ }
+
+ public override object GetItem(int section, int row)
+ {
+ return _root[section][row];
+ }
+
+ public override int GetRowCount(int section)
+ {
+ return _root[section].Count;
+ }
+
+ public override int GetSectionCount()
+ {
+ return _root.Count;
+ }
+
+ public override string GetSectionTitle(int section)
+ {
+ return _root[section].Title;
+ }
+
+ protected override void OnRowSelected(object item)
+ {
+ base.OnRowSelected(item);
+
+ ((Cell)item).OnTapped();
+ }
+
+ internal static Tuple<int, int> GetPath(Cell item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+
+ return (Tuple<int, int>)item.GetValue(PathProperty);
+ }
+
+ void ApplyEvents(TableRoot tableRoot)
+ {
+ tableRoot.CollectionChanged += _parent.CollectionChanged;
+ tableRoot.SectionCollectionChanged += _parent.OnSectionCollectionChanged;
+ }
+
+ void RemoveEvents(TableRoot tableRoot)
+ {
+ if (tableRoot == null)
+ return;
+
+ tableRoot.CollectionChanged -= _parent.CollectionChanged;
+ tableRoot.SectionCollectionChanged -= _parent.OnSectionCollectionChanged;
+ }
+
+ static void SetPath(Cell item, Tuple<int, int> index)
+ {
+ if (item == null)
+ return;
+
+ item.SetValue(PathProperty, index);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TapGestureRecognizer.cs b/Xamarin.Forms.Core/TapGestureRecognizer.cs
new file mode 100644
index 00000000..0489985d
--- /dev/null
+++ b/Xamarin.Forms.Core/TapGestureRecognizer.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Windows.Input;
+
+namespace Xamarin.Forms
+{
+ public sealed class TapGestureRecognizer : GestureRecognizer
+ {
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(TapGestureRecognizer), null);
+
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(TapGestureRecognizer), null);
+
+ public static readonly BindableProperty NumberOfTapsRequiredProperty = BindableProperty.Create("NumberOfTapsRequired", typeof(int), typeof(TapGestureRecognizer), 1);
+
+ public TapGestureRecognizer()
+ {
+ }
+
+ public ICommand Command
+ {
+ get { return (ICommand)GetValue(CommandProperty); }
+ set { SetValue(CommandProperty, value); }
+ }
+
+ public object CommandParameter
+ {
+ get { return GetValue(CommandParameterProperty); }
+ set { SetValue(CommandParameterProperty, value); }
+ }
+
+ public int NumberOfTapsRequired
+ {
+ get { return (int)GetValue(NumberOfTapsRequiredProperty); }
+ set { SetValue(NumberOfTapsRequiredProperty, value); }
+ }
+
+ public event EventHandler Tapped;
+
+ internal void SendTapped(View sender)
+ {
+ ICommand cmd = Command;
+ if (cmd != null && cmd.CanExecute(CommandParameter))
+ cmd.Execute(CommandParameter);
+
+ EventHandler handler = Tapped;
+ if (handler != null)
+ handler(sender, new TappedEventArgs(CommandParameter));
+
+ Action<View, object> callback = TappedCallback;
+ if (callback != null)
+ callback(sender, TappedCallbackParameter);
+ }
+
+ #region obsolete cruft
+
+ // call empty constructor to hack around bug in mono where compiler generates invalid IL
+ [Obsolete("Obsolete in 1.0.2. Use Command instead")]
+ public TapGestureRecognizer(Action<View, object> tappedCallback) : this()
+ {
+ if (tappedCallback == null)
+ throw new ArgumentNullException("tappedCallback");
+ TappedCallback = tappedCallback;
+ }
+
+ // call empty constructor to hack around bug in mono where compiler generates invalid IL
+ [Obsolete("Obsolete in 1.0.2. Use Command instead")]
+ public TapGestureRecognizer(Action<View> tappedCallback) : this()
+ {
+ if (tappedCallback == null)
+ throw new ArgumentNullException("tappedCallback");
+ TappedCallback = (s, o) => tappedCallback(s);
+ }
+
+ [Obsolete("Obsolete in 1.0.2. Use Command instead")] public static readonly BindableProperty TappedCallbackProperty = BindableProperty.Create("TappedCallback", typeof(Action<View, object>),
+ typeof(TapGestureRecognizer), null);
+
+ [Obsolete("Obsolete in 1.0.2. Use Command instead")]
+ public Action<View, object> TappedCallback
+ {
+ get { return (Action<View, object>)GetValue(TappedCallbackProperty); }
+ set { SetValue(TappedCallbackProperty, value); }
+ }
+
+ [Obsolete("Obsolete in 1.0.2. Use Command instead")] public static readonly BindableProperty TappedCallbackParameterProperty = BindableProperty.Create("TappedCallbackParameter", typeof(object),
+ typeof(TapGestureRecognizer), null);
+
+ [Obsolete("Obsolete in 1.0.2. Use Command instead")]
+ public object TappedCallbackParameter
+ {
+ get { return GetValue(TappedCallbackParameterProperty); }
+ set { SetValue(TappedCallbackParameterProperty, value); }
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TappedEventArgs.cs b/Xamarin.Forms.Core/TappedEventArgs.cs
new file mode 100644
index 00000000..9f36b7a2
--- /dev/null
+++ b/Xamarin.Forms.Core/TappedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class TappedEventArgs : EventArgs
+ {
+ public TappedEventArgs(object parameter)
+ {
+ Parameter = parameter;
+ }
+
+ public object Parameter { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TargetIdiom.cs b/Xamarin.Forms.Core/TargetIdiom.cs
new file mode 100644
index 00000000..d19875db
--- /dev/null
+++ b/Xamarin.Forms.Core/TargetIdiom.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum TargetIdiom
+ {
+ Unsupported,
+ Phone,
+ Tablet,
+ Desktop
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TargetPlatform.cs b/Xamarin.Forms.Core/TargetPlatform.cs
new file mode 100644
index 00000000..d9fe6b76
--- /dev/null
+++ b/Xamarin.Forms.Core/TargetPlatform.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.Forms
+{
+ public enum TargetPlatform
+ {
+ Other,
+ iOS,
+ Android,
+ WinPhone,
+ Windows
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TelephoneKeyboard.cs b/Xamarin.Forms.Core/TelephoneKeyboard.cs
new file mode 100644
index 00000000..36f03d67
--- /dev/null
+++ b/Xamarin.Forms.Core/TelephoneKeyboard.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal sealed class TelephoneKeyboard : Keyboard
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TemplateBinding.cs b/Xamarin.Forms.Core/TemplateBinding.cs
new file mode 100644
index 00000000..b01f0671
--- /dev/null
+++ b/Xamarin.Forms.Core/TemplateBinding.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class TemplateBinding : BindingBase
+ {
+ internal const string SelfPath = ".";
+ IValueConverter _converter;
+ object _converterParameter;
+
+ BindingExpression _expression;
+ string _path;
+
+ public TemplateBinding()
+ {
+ }
+
+ public TemplateBinding(string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, string stringFormat = null)
+ {
+ if (path == null)
+ throw new ArgumentNullException("path");
+ if (string.IsNullOrWhiteSpace(path))
+ throw new ArgumentException("path can not be an empty string", "path");
+
+ AllowChaining = true;
+ Path = path;
+ Converter = converter;
+ ConverterParameter = converterParameter;
+ Mode = mode;
+ StringFormat = stringFormat;
+ }
+
+ public IValueConverter Converter
+ {
+ get { return _converter; }
+ set
+ {
+ ThrowIfApplied();
+
+ _converter = value;
+ }
+ }
+
+ public object ConverterParameter
+ {
+ get { return _converterParameter; }
+ set
+ {
+ ThrowIfApplied();
+
+ _converterParameter = value;
+ }
+ }
+
+ public string Path
+ {
+ get { return _path; }
+ set
+ {
+ ThrowIfApplied();
+
+ _path = value;
+ _expression = GetBindingExpression(value);
+ }
+ }
+
+ internal override void Apply(bool fromTarget)
+ {
+ base.Apply(fromTarget);
+
+ if (_expression == null)
+ _expression = new BindingExpression(this, SelfPath);
+
+ _expression.Apply(fromTarget);
+ }
+
+ internal override async void Apply(object newContext, BindableObject bindObj, BindableProperty targetProperty)
+ {
+ var view = bindObj as Element;
+ if (view == null)
+ throw new InvalidOperationException();
+
+ base.Apply(newContext, bindObj, targetProperty);
+
+ Element templatedParent = await TemplateUtilities.FindTemplatedParentAsync(view);
+ ApplyInner(templatedParent, bindObj, targetProperty);
+ }
+
+ internal override BindingBase Clone()
+ {
+ return new TemplateBinding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat };
+ }
+
+ internal override object GetSourceValue(object value, Type targetPropertyType)
+ {
+ if (Converter != null)
+ value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ return base.GetSourceValue(value, targetPropertyType);
+ }
+
+ internal override object GetTargetValue(object value, Type sourcePropertyType)
+ {
+ if (Converter != null)
+ value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ return base.GetTargetValue(value, sourcePropertyType);
+ }
+
+ internal override void Unapply()
+ {
+ base.Unapply();
+
+ if (_expression != null)
+ _expression.Unapply();
+ }
+
+ void ApplyInner(Element templatedParent, BindableObject bindableObject, BindableProperty targetProperty)
+ {
+ if (_expression == null && templatedParent != null)
+ _expression = new BindingExpression(this, SelfPath);
+
+ _expression?.Apply(templatedParent, bindableObject, targetProperty);
+ }
+
+ BindingExpression GetBindingExpression(string path)
+ {
+ return new BindingExpression(this, !string.IsNullOrWhiteSpace(path) ? path : SelfPath);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TemplateExtensions.cs b/Xamarin.Forms.Core/TemplateExtensions.cs
new file mode 100644
index 00000000..3e62c52d
--- /dev/null
+++ b/Xamarin.Forms.Core/TemplateExtensions.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public static class TemplateExtensions
+ {
+ public static void SetBinding(this DataTemplate self, BindableProperty targetProperty, string path)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+
+ self.SetBinding(targetProperty, new Binding(path));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TemplateUtilities.cs b/Xamarin.Forms.Core/TemplateUtilities.cs
new file mode 100644
index 00000000..0599e747
--- /dev/null
+++ b/Xamarin.Forms.Core/TemplateUtilities.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ internal static class TemplateUtilities
+ {
+ public static async Task<Element> FindTemplatedParentAsync(Element element)
+ {
+ if (element.RealParent is Application)
+ return null;
+
+ var skipCount = 0;
+ element = await GetRealParentAsync(element);
+ while (!Application.IsApplicationOrNull(element))
+ {
+ var controlTemplated = element as IControlTemplated;
+ if (controlTemplated?.ControlTemplate != null)
+ {
+ if (skipCount == 0)
+ return element;
+ skipCount--;
+ }
+ if (element is ContentPresenter)
+ skipCount++;
+ element = await GetRealParentAsync(element);
+ }
+
+ return null;
+ }
+
+ public static Task<Element> GetRealParentAsync(Element element)
+ {
+ Element parent = element.RealParent;
+ if (parent is Application)
+ return Task.FromResult<Element>(null);
+
+ if (parent != null)
+ return Task.FromResult(parent);
+
+ var tcs = new TaskCompletionSource<Element>();
+ EventHandler handler = null;
+ handler = (sender, args) =>
+ {
+ tcs.TrySetResult(element.RealParent);
+ element.ParentSet -= handler;
+ };
+ element.ParentSet += handler;
+
+ return tcs.Task;
+ }
+
+ public static void OnContentChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var self = (IControlTemplated)bindable;
+ var newElement = (Element)newValue;
+ if (self.ControlTemplate == null)
+ {
+ for (var i = 0; i < self.InternalChildren.Count; i++)
+ {
+ self.InternalChildren.Remove(self.InternalChildren[i]);
+ }
+
+ if (newValue != null)
+ self.InternalChildren.Add(newElement);
+ }
+ else
+ {
+ if (newElement != null)
+ {
+ BindableObject.SetInheritedBindingContext(newElement, bindable.BindingContext);
+ }
+ }
+ }
+
+ public static void OnControlTemplateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var self = (IControlTemplated)bindable;
+
+ // First make sure any old ContentPresenters are no longer bound up. This MUST be
+ // done before we attempt to make the new template.
+ if (oldValue != null)
+ {
+ var queue = new Queue<Element>(16);
+ queue.Enqueue((Element)self);
+
+ while (queue.Count > 0)
+ {
+ ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren;
+ for (var i = 0; i < children.Count; i++)
+ {
+ Element child = children[i];
+ var controlTemplated = child as IControlTemplated;
+
+ var presenter = child as ContentPresenter;
+ if (presenter != null)
+ presenter.Clear();
+ else if (controlTemplated == null || controlTemplated.ControlTemplate == null)
+ queue.Enqueue(child);
+ }
+ }
+ }
+
+ // Now remove all remnants of any other children just to be sure
+ for (var i = 0; i < self.InternalChildren.Count; i++)
+ {
+ self.InternalChildren.Remove(self.InternalChildren[i]);
+ }
+
+ ControlTemplate template = self.ControlTemplate;
+ var content = template.CreateContent() as View;
+
+ if (content == null)
+ {
+ throw new NotSupportedException("ControlTemplate must return a type derived from View.");
+ }
+
+ self.InternalChildren.Add(content);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TemplatedItemsList.cs b/Xamarin.Forms.Core/TemplatedItemsList.cs
new file mode 100644
index 00000000..814f5835
--- /dev/null
+++ b/Xamarin.Forms.Core/TemplatedItemsList.cs
@@ -0,0 +1,1325 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using Cadenza.Collections;
+
+namespace Xamarin.Forms
+{
+ internal sealed class TemplatedItemsList<TView, TItem> : BindableObject, IReadOnlyList<TItem>, IList, INotifyCollectionChanged, IDisposable where TView : BindableObject, IItemsView<TItem>
+ where TItem : BindableObject
+ {
+ public static readonly BindableProperty NameProperty = BindableProperty.Create("Name", typeof(string), typeof(TemplatedItemsList<TView, TItem>), null);
+
+ public static readonly BindableProperty ShortNameProperty = BindableProperty.Create("ShortName", typeof(string), typeof(TemplatedItemsList<TView, TItem>), null);
+
+ static readonly BindablePropertyKey HeaderContentPropertyKey = BindableProperty.CreateReadOnly("HeaderContent", typeof(TItem), typeof(TemplatedItemsList<TView, TItem>), null);
+
+ internal static readonly BindablePropertyKey ListProxyPropertyKey = BindableProperty.CreateReadOnly("ListProxy", typeof(ListProxy), typeof(TemplatedItemsList<TView, TItem>), null,
+ propertyChanged: OnListProxyChanged);
+
+ static readonly BindableProperty GroupProperty = BindableProperty.Create("Group", typeof(TemplatedItemsList<TView, TItem>), typeof(TItem), null);
+
+ static readonly BindableProperty IndexProperty = BindableProperty.Create("Index", typeof(int), typeof(TItem), -1);
+
+ static readonly BindablePropertyKey IsGroupHeaderPropertyKey = BindableProperty.CreateAttachedReadOnly("IsGroupHeader", typeof(bool), typeof(Cell), false);
+
+ readonly BindableProperty _itemSourceProperty;
+ readonly BindableProperty _itemTemplateProperty;
+
+ readonly TView _itemsView;
+
+ readonly List<TItem> _templatedObjects = new List<TItem>();
+
+ bool _disposed;
+ BindingBase _groupDisplayBinding;
+ OrderedDictionary<object, TemplatedItemsList<TView, TItem>> _groupedItems;
+ DataTemplate _groupHeaderTemplate;
+ BindingBase _groupShortNameBinding;
+ ShortNamesProxy _shortNames;
+
+ internal TemplatedItemsList(TView itemsView, BindableProperty itemSourceProperty, BindableProperty itemTemplateProperty)
+ {
+ if (itemsView == null)
+ throw new ArgumentNullException("itemsView");
+ if (itemSourceProperty == null)
+ throw new ArgumentNullException("itemSourceProperty");
+ if (itemTemplateProperty == null)
+ throw new ArgumentNullException("itemTemplateProperty");
+
+ _itemsView = itemsView;
+ _itemsView.PropertyChanged += BindableOnPropertyChanged;
+
+ _itemSourceProperty = itemSourceProperty;
+ _itemTemplateProperty = itemTemplateProperty;
+
+ IEnumerable source = GetItemsViewSource();
+ if (source != null)
+ ListProxy = new ListProxy(source);
+ else
+ ListProxy = new ListProxy(new object[0]);
+ }
+
+ internal TemplatedItemsList(TemplatedItemsList<TView, TItem> parent, IEnumerable itemSource, TView itemsView, BindableProperty itemTemplateProperty, int windowSize = int.MaxValue)
+ {
+ if (itemsView == null)
+ throw new ArgumentNullException("itemsView");
+ if (itemTemplateProperty == null)
+ throw new ArgumentNullException("itemTemplateProperty");
+
+ Parent = parent;
+
+ _itemsView = itemsView;
+ _itemsView.PropertyChanged += BindableOnPropertyChanged;
+ _itemTemplateProperty = itemTemplateProperty;
+
+ if (itemSource != null)
+ {
+ ListProxy = new ListProxy(itemSource, windowSize);
+ ListProxy.CollectionChanged += OnProxyCollectionChanged;
+ }
+ else
+ ListProxy = new ListProxy(new object[0]);
+ }
+
+ public BindingBase GroupDisplayBinding
+ {
+ get { return _groupDisplayBinding; }
+ set
+ {
+ _groupDisplayBinding = value;
+ OnHeaderTemplateChanged();
+ }
+ }
+
+ public DataTemplate GroupHeaderTemplate
+ {
+ get
+ {
+ DataTemplate groupHeader = null;
+ if (GroupHeaderTemplateProperty != null)
+ groupHeader = (DataTemplate)_itemsView.GetValue(GroupHeaderTemplateProperty);
+
+ return groupHeader ?? _groupHeaderTemplate;
+ }
+
+ set
+ {
+ if (_groupHeaderTemplate == value)
+ return;
+
+ _groupHeaderTemplate = value;
+ OnHeaderTemplateChanged();
+ }
+ }
+
+ public BindableProperty GroupHeaderTemplateProperty { get; set; }
+
+ public BindingBase GroupShortNameBinding
+ {
+ get { return _groupShortNameBinding; }
+ set
+ {
+ _groupShortNameBinding = value;
+ OnShortNameBindingChanged();
+ }
+ }
+
+ public TItem HeaderContent
+ {
+ get { return (TItem)GetValue(HeaderContentPropertyKey.BindableProperty); }
+ private set { SetValue(HeaderContentPropertyKey, value); }
+ }
+
+ public bool IsGroupingEnabled
+ {
+ get { return (IsGroupingEnabledProperty != null) && (bool)_itemsView.GetValue(IsGroupingEnabledProperty); }
+ }
+
+ public BindableProperty IsGroupingEnabledProperty { get; set; }
+
+ public IEnumerable ItemsSource
+ {
+ get { return ListProxy.ProxiedEnumerable; }
+ }
+
+ public string Name
+ {
+ get { return (string)GetValue(NameProperty); }
+ set { SetValue(NameProperty, value); }
+ }
+
+ public TemplatedItemsList<TView, TItem> Parent { get; }
+
+ public BindableProperty ProgressiveLoadingProperty { get; set; }
+
+ public string ShortName
+ {
+ get { return (string)GetValue(ShortNameProperty); }
+ set { SetValue(ShortNameProperty, value); }
+ }
+
+ public IReadOnlyList<string> ShortNames
+ {
+ get { return _shortNames; }
+ }
+
+ internal ListViewCachingStrategy CachingStrategy
+ {
+ get
+ {
+ var listView = _itemsView as ListView;
+ if (listView == null)
+ return ListViewCachingStrategy.RetainElement;
+
+ return listView.CachingStrategy;
+ }
+ }
+
+ internal ListProxy ListProxy
+ {
+ get { return (ListProxy)GetValue(ListProxyPropertyKey.BindableProperty); }
+ private set { SetValue(ListProxyPropertyKey, value); }
+ }
+
+ DataTemplate ItemTemplate
+ {
+ get { return (DataTemplate)_itemsView.GetValue(_itemTemplateProperty); }
+ }
+
+ bool ProgressiveLoading
+ {
+ get { return (ProgressiveLoadingProperty != null) && (bool)_itemsView.GetValue(ProgressiveLoadingProperty); }
+ }
+
+ void ICollection.CopyTo(Array array, int arrayIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get { return false; }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get { return this; }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _itemsView.PropertyChanged -= BindableOnPropertyChanged;
+
+ TItem header = HeaderContent;
+ if (header != null)
+ UnhookItem(header);
+
+ for (var i = 0; i < _templatedObjects.Count; i++)
+ {
+ TItem item = _templatedObjects[i];
+ if (item != null)
+ UnhookItem(item);
+ }
+
+ _disposed = true;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ if (IsGroupingEnabled)
+ return _groupedItems.Values.GetEnumerator();
+
+ return GetEnumerator();
+ }
+
+ public IEnumerator<TItem> GetEnumerator()
+ {
+ var i = 0;
+ foreach (object item in ListProxy)
+ yield return GetOrCreateContent(i++, item);
+ }
+
+ int IList.Add(object item)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ bool IList.Contains(object item)
+ {
+ throw new NotImplementedException();
+ }
+
+ int IList.IndexOf(object item)
+ {
+ if (IsGroupingEnabled)
+ {
+ var til = item as TemplatedItemsList<TView, TItem>;
+ if (til != null)
+ return _groupedItems.Values.IndexOf(til);
+ }
+
+ return IndexOf((TItem)item);
+ }
+
+ void IList.Insert(int index, object item)
+ {
+ throw new NotSupportedException();
+ }
+
+ bool IList.IsFixedSize
+ {
+ get { return false; }
+ }
+
+ bool IList.IsReadOnly
+ {
+ get { return true; }
+ }
+
+ object IList.this[int index]
+ {
+ get
+ {
+ if (IsGroupingEnabled)
+ return GetGroup(index);
+
+ return this[index];
+ }
+ set { throw new NotSupportedException(); }
+ }
+
+ void IList.Remove(object item)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ public int Count
+ {
+ get { return ListProxy.Count; }
+ }
+
+ public TItem this[int index]
+ {
+ get { return GetOrCreateContent(index, ListProxy[index]); }
+ }
+
+ public int GetDescendantCount()
+ {
+ if (!IsGroupingEnabled)
+ return Count;
+
+ if (_groupedItems == null)
+ return 0;
+
+ int count = Count;
+ foreach (TemplatedItemsList<TView, TItem> group in _groupedItems.Values)
+ count += group.GetDescendantCount();
+
+ return count;
+ }
+
+ public int GetGlobalIndexForGroup(TemplatedItemsList<TView, TItem> group)
+ {
+ if (group == null)
+ throw new ArgumentNullException("group");
+
+ int groupIndex = _groupedItems.Values.IndexOf(group);
+
+ var index = 0;
+ for (var i = 0; i < groupIndex; i++)
+ index += _groupedItems[i].GetDescendantCount() + 1;
+
+ return index;
+ }
+
+ public int GetGlobalIndexOfGroup(object item)
+ {
+ var count = 0;
+ if (IsGroupingEnabled && _groupedItems != null)
+ {
+ foreach (object group in _groupedItems.Keys)
+ {
+ if (group == item)
+ return count;
+ count++;
+ }
+ }
+
+ return -1;
+ }
+
+ public int GetGlobalIndexOfItem(object item)
+ {
+ if (!IsGroupingEnabled)
+ return ListProxy.IndexOf(item);
+
+ var count = 0;
+ if (_groupedItems != null)
+ {
+ foreach (TemplatedItemsList<TView, TItem> children in _groupedItems.Values)
+ {
+ count++;
+
+ int index = children.GetGlobalIndexOfItem(item);
+ if (index != -1)
+ return count + index;
+
+ count += children.GetDescendantCount();
+ }
+ }
+
+ return -1;
+ }
+
+ public int GetGlobalIndexOfItem(object group, object item)
+ {
+ if (!IsGroupingEnabled)
+ return ListProxy.IndexOf(item);
+
+ var count = 0;
+ if (_groupedItems != null)
+ {
+ foreach (KeyValuePair<object, TemplatedItemsList<TView, TItem>> kvp in _groupedItems)
+ {
+ count++;
+
+ if (ReferenceEquals(group, kvp.Key))
+ {
+ int index = kvp.Value.GetGlobalIndexOfItem(item);
+ if (index != -1)
+ return count + index;
+ }
+
+ count += kvp.Value.GetDescendantCount();
+ }
+ }
+
+ return -1;
+ }
+
+ public Tuple<int, int> GetGroupAndIndexOfItem(object item)
+ {
+ if (item == null)
+ return new Tuple<int, int>(-1, -1);
+ if (!IsGroupingEnabled)
+ return new Tuple<int, int>(0, GetGlobalIndexOfItem(item));
+
+ var group = 0;
+ if (_groupedItems != null)
+ {
+ foreach (TemplatedItemsList<TView, TItem> children in _groupedItems.Values)
+ {
+ int index = children.GetGlobalIndexOfItem(item);
+ if (index != -1)
+ return new Tuple<int, int>(group, index);
+
+ group++;
+ }
+ }
+
+ return new Tuple<int, int>(-1, -1);
+ }
+
+ public Tuple<int, int> GetGroupAndIndexOfItem(object group, object item)
+ {
+ if (!IsGroupingEnabled)
+ return new Tuple<int, int>(0, GetGlobalIndexOfItem(item));
+ if (_groupedItems == null)
+ return new Tuple<int, int>(-1, -1);
+
+ var groupIndex = 0;
+ foreach (TemplatedItemsList<TView, TItem> children in _groupedItems.Values)
+ {
+ if (ReferenceEquals(children.BindingContext, group) || group == null)
+ {
+ for (var i = 0; i < children.Count; i++)
+ {
+ if (ReferenceEquals(children[i].BindingContext, item))
+ return new Tuple<int, int>(groupIndex, i);
+ }
+
+ if (group != null)
+ return new Tuple<int, int>(groupIndex, -1);
+ }
+
+ groupIndex++;
+ }
+
+ return new Tuple<int, int>(-1, -1);
+ }
+
+ public int GetGroupIndexFromGlobal(int globalIndex, out int leftOver)
+ {
+ leftOver = 0;
+
+ var index = 0;
+ for (var i = 0; i < _groupedItems.Count; i++)
+ {
+ if (index == globalIndex)
+ return i;
+
+ TemplatedItemsList<TView, TItem> group = _groupedItems[i];
+ int count = group.GetDescendantCount();
+
+ if (index + count >= globalIndex)
+ {
+ leftOver = globalIndex - index;
+ return i;
+ }
+
+ index += count + 1;
+ }
+
+ return -1;
+ }
+
+ public event NotifyCollectionChangedEventHandler GroupedCollectionChanged;
+
+ public int IndexOf(TItem item)
+ {
+ TemplatedItemsList<TView, TItem> group = GetGroup(item);
+ if (group != null && group != this)
+ return -1;
+
+ return GetIndex(item);
+ }
+
+ internal TItem CreateContent(int index, object item, bool insert = false)
+ {
+ TItem content = ItemTemplate != null ? (TItem)ItemTemplate.CreateContent(item, _itemsView) : _itemsView.CreateDefault(item);
+
+ content = UpdateContent(content, index, item);
+
+ if (CachingStrategy == ListViewCachingStrategy.RecycleElement)
+ return content;
+
+ for (int i = _templatedObjects.Count; i <= index; i++)
+ _templatedObjects.Add(null);
+
+ if (!insert)
+ _templatedObjects[index] = content;
+ else
+ _templatedObjects.Insert(index, content);
+
+ return content;
+ }
+
+ internal void ForceUpdate()
+ {
+ ListProxy.Clear();
+ }
+
+ internal TemplatedItemsList<TView, TItem> GetGroup(int index)
+ {
+ if (!IsGroupingEnabled)
+ return this;
+
+ return _groupedItems[index];
+ }
+
+ internal static TemplatedItemsList<TView, TItem> GetGroup(TItem item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+
+ return (TemplatedItemsList<TView, TItem>)item.GetValue(GroupProperty);
+ }
+
+ internal static int GetIndex(TItem item)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+
+ return (int)item.GetValue(IndexProperty);
+ }
+
+ internal static bool GetIsGroupHeader(BindableObject bindable)
+ {
+ return (bool)bindable.GetValue(IsGroupHeaderPropertyKey.BindableProperty);
+ }
+
+ internal TItem GetOrCreateContent(int index, object item)
+ {
+ TItem content;
+ if (_templatedObjects.Count <= index || (content = _templatedObjects[index]) == null)
+ content = CreateContent(index, item);
+
+ return content;
+ }
+
+ internal static void SetIsGroupHeader(BindableObject bindable, bool value)
+ {
+ bindable.SetValue(IsGroupHeaderPropertyKey, value);
+ }
+
+ internal TItem UpdateContent(TItem content, int index, object item)
+ {
+ content.BindingContext = item;
+
+ if (Parent != null)
+ SetGroup(content, this);
+
+ SetIndex(content, index);
+
+ _itemsView.SetupContent(content, index);
+
+ return content;
+ }
+
+ internal TItem UpdateContent(TItem content, int index)
+ {
+ object item = ListProxy[index];
+ return UpdateContent(content, index, item);
+ }
+
+ internal TItem UpdateHeader(TItem content, int groupIndex)
+ {
+ if (Parent != null && Parent.GroupHeaderTemplate == null)
+ {
+ content.BindingContext = this;
+ }
+ else
+ {
+ content.BindingContext = ListProxy.ProxiedEnumerable;
+ }
+
+ SetIndex(content, groupIndex);
+
+ _itemsView.SetupContent(content, groupIndex);
+
+ return content;
+ }
+
+ void BindableOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (_itemSourceProperty != null && e.PropertyName == _itemSourceProperty.PropertyName)
+ OnItemsSourceChanged();
+ else if (e.PropertyName == _itemTemplateProperty.PropertyName)
+ OnItemTemplateChanged();
+ else if (ProgressiveLoadingProperty != null && e.PropertyName == ProgressiveLoadingProperty.PropertyName)
+ OnInfiniteScrollingChanged();
+ else if (GroupHeaderTemplateProperty != null && e.PropertyName == GroupHeaderTemplateProperty.PropertyName)
+ OnHeaderTemplateChanged();
+ else if (IsGroupingEnabledProperty != null && e.PropertyName == IsGroupingEnabledProperty.PropertyName)
+ OnGroupingEnabledChanged();
+ }
+
+ IList ConvertContent(int startingIndex, IList items, bool forceCreate = false, bool setIndex = false)
+ {
+ var contentItems = new List<TItem>(items.Count);
+ for (var i = 0; i < items.Count; i++)
+ {
+ int index = i + startingIndex;
+ TItem content = !forceCreate ? GetOrCreateContent(index, items[i]) : CreateContent(index, items[i]);
+ if (setIndex)
+ SetIndex(content, index);
+
+ contentItems.Add(content);
+ }
+
+ return contentItems;
+ }
+
+ IEnumerable GetItemsViewSource()
+ {
+ return (IEnumerable)_itemsView.GetValue(_itemSourceProperty);
+ }
+
+ void GroupedReset()
+ {
+ if (_groupedItems != null)
+ {
+ foreach (KeyValuePair<object, TemplatedItemsList<TView, TItem>> group in _groupedItems)
+ {
+ group.Value.CollectionChanged -= OnInnerCollectionChanged;
+ group.Value.Dispose();
+ }
+ _groupedItems.Clear();
+ }
+
+ _templatedObjects.Clear();
+
+ var i = 0;
+ foreach (object item in ListProxy)
+ InsertGrouped(item, i++);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ TemplatedItemsList<TView, TItem> InsertGrouped(object item, int index)
+ {
+ var children = item as IEnumerable;
+
+ var groupProxy = new TemplatedItemsList<TView, TItem>(this, children, _itemsView, _itemTemplateProperty);
+ if (GroupDisplayBinding != null)
+ groupProxy.SetBinding(NameProperty, GroupDisplayBinding.Clone());
+ else if (GroupHeaderTemplate == null && item != null)
+ groupProxy.Name = item.ToString();
+
+ if (GroupShortNameBinding != null)
+ groupProxy.SetBinding(ShortNameProperty, GroupShortNameBinding.Clone());
+
+ groupProxy.BindingContext = item;
+
+ if (GroupHeaderTemplate != null)
+ {
+ groupProxy.HeaderContent = (TItem)GroupHeaderTemplate.CreateContent(groupProxy.ItemsSource, _itemsView);
+ groupProxy.HeaderContent.BindingContext = groupProxy.ItemsSource;
+ //groupProxy.HeaderContent.BindingContext = groupProxy;
+ //groupProxy.HeaderContent.SetBinding (BindingContextProperty, "ItemsSource");
+ }
+ else
+ {
+ // HACK: TemplatedItemsList shouldn't assume what the default is, but it needs
+ // to be able to setup bindings. Needs some internal-API tweaking there isn't
+ // time for right now.
+ groupProxy.HeaderContent = _itemsView.CreateDefault(ListProxy.ProxiedEnumerable);
+ groupProxy.HeaderContent.BindingContext = groupProxy;
+ groupProxy.HeaderContent.SetBinding(TextCell.TextProperty, "Name");
+ }
+
+ SetIndex(groupProxy.HeaderContent, index);
+ SetIsGroupHeader(groupProxy.HeaderContent, true);
+
+ _itemsView.SetupContent(groupProxy.HeaderContent, index);
+
+ _templatedObjects.Insert(index, groupProxy.HeaderContent);
+ _groupedItems.Insert(index, item, groupProxy);
+
+ groupProxy.CollectionChanged += OnInnerCollectionChanged;
+
+ return groupProxy;
+ }
+
+ void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler changed = CollectionChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void OnCollectionChangedGrouped(NotifyCollectionChangedEventArgs e)
+ {
+ if (_groupedItems == null)
+ _groupedItems = new OrderedDictionary<object, TemplatedItemsList<TView, TItem>>();
+
+ List<TemplatedItemsList<TView, TItem>> newItems = null, oldItems = null;
+
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex == -1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ for (int i = e.NewStartingIndex; i < _templatedObjects.Count; i++)
+ SetIndex(_templatedObjects[i], i + e.NewItems.Count);
+
+ newItems = new List<TemplatedItemsList<TView, TItem>>(e.NewItems.Count);
+
+ for (var i = 0; i < e.NewItems.Count; i++)
+ {
+ TemplatedItemsList<TView, TItem> converted = InsertGrouped(e.NewItems[i], e.NewStartingIndex + i);
+ newItems.Add(converted);
+ }
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, e.NewStartingIndex));
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex == -1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ int removeIndex = e.OldStartingIndex;
+ for (int i = removeIndex + e.OldItems.Count; i < _templatedObjects.Count; i++)
+ SetIndex(_templatedObjects[i], removeIndex++);
+
+ oldItems = new List<TemplatedItemsList<TView, TItem>>(e.OldItems.Count);
+ for (var i = 0; i < e.OldItems.Count; i++)
+ {
+ int index = e.OldStartingIndex + i;
+ TemplatedItemsList<TView, TItem> til = _groupedItems[index];
+ til.CollectionChanged -= OnInnerCollectionChanged;
+ oldItems.Add(til);
+ _groupedItems.RemoveAt(index);
+ _templatedObjects.RemoveAt(index);
+ til.Dispose();
+ }
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, e.OldStartingIndex));
+
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ if (e.OldStartingIndex == -1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ oldItems = new List<TemplatedItemsList<TView, TItem>>(e.OldItems.Count);
+ newItems = new List<TemplatedItemsList<TView, TItem>>(e.NewItems.Count);
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ {
+ int index = e.OldStartingIndex + i;
+
+ TemplatedItemsList<TView, TItem> til = _groupedItems[index];
+ til.CollectionChanged -= OnInnerCollectionChanged;
+ oldItems.Add(til);
+
+ _groupedItems.RemoveAt(index);
+ _templatedObjects.RemoveAt(index);
+
+ newItems.Add(InsertGrouped(e.NewItems[i], index));
+ til.Dispose();
+ }
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, e.OldStartingIndex));
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ bool movingForward = e.OldStartingIndex < e.NewStartingIndex;
+
+ if (movingForward)
+ {
+ int moveIndex = e.OldStartingIndex;
+ for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++)
+ SetIndex(_templatedObjects[i], moveIndex++);
+ }
+ else
+ {
+ for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++)
+ {
+ TItem item = _templatedObjects[i + e.NewStartingIndex];
+ SetIndex(item, GetIndex(item) + e.OldItems.Count);
+ }
+ }
+
+ oldItems = new List<TemplatedItemsList<TView, TItem>>(e.OldItems.Count);
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ {
+ oldItems.Add(_groupedItems[e.OldStartingIndex]);
+
+ _templatedObjects.RemoveAt(e.OldStartingIndex);
+ _groupedItems.RemoveAt(e.OldStartingIndex);
+ }
+
+ int insertIndex = e.NewStartingIndex;
+ if (e.OldStartingIndex < e.NewStartingIndex)
+ insertIndex -= e.OldItems.Count - 1;
+
+ for (var i = 0; i < oldItems.Count; i++)
+ {
+ TemplatedItemsList<TView, TItem> til = oldItems[i];
+ _templatedObjects.Insert(insertIndex + i, til.HeaderContent);
+ _groupedItems.Insert(insertIndex + i, til.BindingContext, til);
+ SetIndex(til.HeaderContent, insertIndex + i);
+ }
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, oldItems, e.OldStartingIndex, e.NewStartingIndex));
+
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ GroupedReset();
+ break;
+ }
+ }
+
+ void OnGroupingEnabledChanged()
+ {
+ if (CachingStrategy == ListViewCachingStrategy.RecycleElement)
+ _templatedObjects.Clear();
+
+ OnItemsSourceChanged(true);
+
+ if (!IsGroupingEnabled && _shortNames != null)
+ {
+ _shortNames.Dispose();
+ _shortNames = null;
+ }
+ else
+ OnShortNameBindingChanged();
+ }
+
+ void OnHeaderTemplateChanged()
+ {
+ OnItemTemplateChanged();
+ }
+
+ void OnInfiniteScrollingChanged()
+ {
+ OnItemsSourceChanged();
+ }
+
+ void OnInnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler handler = GroupedCollectionChanged;
+ if (handler != null)
+ handler(sender, e);
+ }
+
+ void OnItemsSourceChanged(bool fromGrouping = false)
+ {
+ ListProxy.CollectionChanged -= OnProxyCollectionChanged;
+
+ IEnumerable itemSource = GetItemsViewSource();
+ if (itemSource == null)
+ ListProxy = new ListProxy(new object[0]);
+ else
+ ListProxy = new ListProxy(itemSource);
+
+ ListProxy.CollectionChanged += OnProxyCollectionChanged;
+ OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ void OnItemTemplateChanged()
+ {
+ if (ListProxy.Count == 0)
+ return;
+
+ OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ static void OnListProxyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var til = (TemplatedItemsList<TView, TItem>)bindable;
+ til.OnPropertyChanged("ItemsSource");
+ }
+
+ void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ OnProxyCollectionChanged(sender, e, true);
+ }
+
+ void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, bool fixWindows = true)
+ {
+ if (IsGroupingEnabled)
+ {
+ OnCollectionChangedGrouped(e);
+ return;
+ }
+
+ if (CachingStrategy == ListViewCachingStrategy.RecycleElement)
+ {
+ OnCollectionChanged(e);
+ return;
+ }
+
+ /* HACKAHACKHACK: LongListSelector on WP SL has a bug in that it completely fails to deal with
+ * INCC notifications that include more than 1 item. */
+ if (fixWindows && Device.OS == TargetPlatform.WinPhone)
+ {
+ SplitCollectionChangedItems(e);
+ return;
+ }
+
+ int count = Count;
+ var ex = e as NotifyCollectionChangedEventArgsEx;
+ if (ex != null)
+ count = ex.Count;
+
+ var maxindex = 0;
+ if (e.NewStartingIndex >= 0 && e.NewItems != null)
+ maxindex = Math.Max(maxindex, e.NewStartingIndex + e.NewItems.Count);
+ if (e.OldStartingIndex >= 0 && e.OldItems != null)
+ maxindex = Math.Max(maxindex, e.OldStartingIndex + e.OldItems.Count);
+ if (maxindex > _templatedObjects.Count)
+ _templatedObjects.InsertRange(_templatedObjects.Count, Enumerable.Repeat<TItem>(null, maxindex - _templatedObjects.Count));
+
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex >= 0)
+ {
+ for (int i = e.NewStartingIndex; i < _templatedObjects.Count; i++)
+ SetIndex(_templatedObjects[i], i + e.NewItems.Count);
+
+ _templatedObjects.InsertRange(e.NewStartingIndex, Enumerable.Repeat<TItem>(null, e.NewItems.Count));
+
+ IList items = ConvertContent(e.NewStartingIndex, e.NewItems, true, true);
+ e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Add, items, e.NewStartingIndex);
+ }
+ else
+ {
+ goto case NotifyCollectionChangedAction.Reset;
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ if (e.NewStartingIndex < 0 || e.OldStartingIndex < 0)
+ goto case NotifyCollectionChangedAction.Reset;
+
+ bool movingForward = e.OldStartingIndex < e.NewStartingIndex;
+
+ if (movingForward)
+ {
+ int moveIndex = e.OldStartingIndex;
+ for (int i = moveIndex + e.OldItems.Count; i <= e.NewStartingIndex; i++)
+ SetIndex(_templatedObjects[i], moveIndex++);
+ }
+ else
+ {
+ for (var i = 0; i < e.OldStartingIndex - e.NewStartingIndex; i++)
+ {
+ TItem item = _templatedObjects[i + e.NewStartingIndex];
+ if (item != null)
+ SetIndex(item, GetIndex(item) + e.OldItems.Count);
+ }
+ }
+
+ TItem[] itemsToMove = _templatedObjects.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToArray();
+
+ _templatedObjects.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
+ _templatedObjects.InsertRange(e.NewStartingIndex, itemsToMove);
+ for (var i = 0; i < itemsToMove.Length; i++)
+ SetIndex(itemsToMove[i], e.NewStartingIndex + i);
+
+ e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Move, itemsToMove, e.NewStartingIndex, e.OldStartingIndex);
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex >= 0)
+ {
+ int removeIndex = e.OldStartingIndex;
+ for (int i = removeIndex + e.OldItems.Count; i < _templatedObjects.Count; i++)
+ SetIndex(_templatedObjects[i], removeIndex++);
+
+ var items = new TItem[e.OldItems.Count];
+ for (var i = 0; i < items.Length; i++)
+ {
+ TItem item = _templatedObjects[e.OldStartingIndex + i];
+ if (item == null)
+ continue;
+
+ UnhookItem(item);
+ items[i] = item;
+ }
+
+ _templatedObjects.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
+ e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Remove, items, e.OldStartingIndex);
+ }
+ else
+ {
+ goto case NotifyCollectionChangedAction.Reset;
+ }
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ if (e.NewStartingIndex >= 0)
+ {
+ IList oldItems = ConvertContent(e.NewStartingIndex, e.OldItems);
+ IList newItems = ConvertContent(e.NewStartingIndex, e.NewItems, true, true);
+
+ for (var i = 0; i < oldItems.Count; i++)
+ {
+ UnhookItem((TItem)oldItems[i]);
+ }
+
+ e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Replace, newItems, oldItems, e.NewStartingIndex);
+ }
+ else
+ {
+ goto case NotifyCollectionChangedAction.Reset;
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ e = new NotifyCollectionChangedEventArgsEx(count, NotifyCollectionChangedAction.Reset);
+ UnhookAndClear();
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ OnCollectionChanged(e);
+ }
+
+ void OnShortNameBindingChanged()
+ {
+ if (!IsGroupingEnabled)
+ return;
+
+ if (GroupShortNameBinding != null && _shortNames == null)
+ _shortNames = new ShortNamesProxy(this);
+ else if (GroupShortNameBinding == null && _shortNames != null)
+ {
+ _shortNames.Dispose();
+ _shortNames = null;
+ }
+
+ if (_groupedItems != null)
+ {
+ if (GroupShortNameBinding == null)
+ {
+ foreach (TemplatedItemsList<TView, TItem> list in _groupedItems.Values)
+ list.SetValue(ShortNameProperty, null);
+
+ return;
+ }
+
+ foreach (TemplatedItemsList<TView, TItem> list in _groupedItems.Values)
+ list.SetBinding(ShortNameProperty, GroupShortNameBinding.Clone());
+ }
+
+ if (_shortNames != null)
+ _shortNames.Reset();
+ }
+
+ static void SetGroup(TItem item, TemplatedItemsList<TView, TItem> group)
+ {
+ if (item == null)
+ throw new ArgumentNullException("item");
+
+ item.SetValue(GroupProperty, group);
+ }
+
+ static void SetIndex(TItem item, int index)
+ {
+ if (item == null)
+ return;
+
+ item.SetValue(IndexProperty, index);
+ }
+
+ void SplitCollectionChangedItems(NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (e.NewStartingIndex < 0)
+ goto default;
+
+ for (var i = 0; i < e.NewItems.Count; i++)
+ OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.NewItems[i], e.NewStartingIndex + i), false);
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ if (e.OldStartingIndex < 0)
+ goto default;
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.OldItems[i], e.OldStartingIndex + i), false);
+
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ if (e.OldStartingIndex < 0)
+ goto default;
+
+ for (var i = 0; i < e.OldItems.Count; i++)
+ OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.NewItems[i], e.OldItems[i], e.OldStartingIndex + i), false);
+
+ break;
+
+ default:
+ OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), false);
+ break;
+ }
+ }
+
+ void UnhookAndClear()
+ {
+ for (var i = 0; i < _templatedObjects.Count; i++)
+ {
+ TItem item = _templatedObjects[i];
+ if (item == null)
+ continue;
+
+ UnhookItem(item);
+ }
+
+ _templatedObjects.Clear();
+ }
+
+ async void UnhookItem(TItem item)
+ {
+ SetIndex(item, -1);
+ _itemsView.UnhookContent(item);
+
+ //Hack: the cell could still be visible on iOS because the cells are reloaded after this unhook
+ //this causes some visual updates caused by a null datacontext and default values like IsVisible
+ if (Device.OS == TargetPlatform.iOS && CachingStrategy == ListViewCachingStrategy.RetainElement)
+ await Task.Delay(100);
+ item.BindingContext = null;
+ }
+
+ class ShortNamesProxy : IReadOnlyList<string>, INotifyCollectionChanged, IDisposable
+ {
+ readonly HashSet<TemplatedItemsList<TView, TItem>> _attachedItems = new HashSet<TemplatedItemsList<TView, TItem>>();
+ readonly TemplatedItemsList<TView, TItem> _itemsList;
+
+ readonly Dictionary<TemplatedItemsList<TView, TItem>, string> _oldNames = new Dictionary<TemplatedItemsList<TView, TItem>, string>();
+
+ bool _disposed;
+
+ internal ShortNamesProxy(TemplatedItemsList<TView, TItem> itemsList)
+ {
+ _itemsList = itemsList;
+ _itemsList.CollectionChanged += OnItemsListCollectionChanged;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ _itemsList.CollectionChanged -= OnItemsListCollectionChanged;
+
+ ResetCore(false);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<string> GetEnumerator()
+ {
+ if (_itemsList._groupedItems == null)
+ yield break;
+
+ foreach (TemplatedItemsList<TView, TItem> item in _itemsList._groupedItems.Values)
+ {
+ AttachList(item);
+ yield return item.ShortName;
+ }
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ public int Count
+ {
+ get { return _itemsList._groupedItems.Count; }
+ }
+
+ public string this[int index]
+ {
+ get
+ {
+ TemplatedItemsList<TView, TItem> list = _itemsList._groupedItems[index];
+ AttachList(list);
+
+ return list.ShortName;
+ }
+ }
+
+ public void Reset()
+ {
+ ResetCore(true);
+ }
+
+ void AttachList(TemplatedItemsList<TView, TItem> list)
+ {
+ if (_attachedItems.Contains(list))
+ return;
+
+ list.PropertyChanging += OnChildListPropertyChanging;
+ list.PropertyChanged += OnChildListPropertyChanged;
+ _attachedItems.Add(list);
+ }
+
+ List<string> ConvertItems(IList list)
+ {
+ var newList = new List<string>(list.Count);
+ newList.AddRange(list.Cast<TemplatedItemsList<TView, TItem>>().Select(tl => tl.ShortName));
+ return newList;
+ }
+
+ void OnChildListPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName != ShortNameProperty.PropertyName)
+ return;
+
+ var list = (TemplatedItemsList<TView, TItem>)sender;
+ string old = _oldNames[list];
+ _oldNames.Remove(list);
+
+ int index = _itemsList._groupedItems.Values.IndexOf(list);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, list.ShortName, old, index));
+ }
+
+ void OnChildListPropertyChanging(object sender, PropertyChangingEventArgs e)
+ {
+ if (e.PropertyName != ShortNameProperty.PropertyName)
+ return;
+
+ var list = (TemplatedItemsList<TView, TItem>)sender;
+ _oldNames[list] = list.ShortName;
+ }
+
+ void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ NotifyCollectionChangedEventHandler changed = CollectionChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ void OnItemsListCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, ConvertItems(e.NewItems), e.NewStartingIndex);
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, ConvertItems(e.OldItems), e.NewStartingIndex, e.OldStartingIndex);
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, ConvertItems(e.OldItems), e.OldStartingIndex);
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, ConvertItems(e.NewItems), ConvertItems(e.OldItems), e.OldStartingIndex);
+ break;
+ }
+
+ OnCollectionChanged(e);
+ }
+
+ void ResetCore(bool raiseReset)
+ {
+ foreach (TemplatedItemsList<TView, TItem> list in _attachedItems)
+ {
+ list.PropertyChanged -= OnChildListPropertyChanged;
+ list.PropertyChanging -= OnChildListPropertyChanging;
+ }
+
+ _attachedItems.Clear();
+
+ if (raiseReset)
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TemplatedPage.cs b/Xamarin.Forms.Core/TemplatedPage.cs
new file mode 100644
index 00000000..69c2d58b
--- /dev/null
+++ b/Xamarin.Forms.Core/TemplatedPage.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public class TemplatedPage : Page, IControlTemplated
+ {
+ public static readonly BindableProperty ControlTemplateProperty = BindableProperty.Create(nameof(ControlTemplate), typeof(ControlTemplate), typeof(TemplatedPage), null,
+ propertyChanged: TemplateUtilities.OnControlTemplateChanged);
+
+ public ControlTemplate ControlTemplate
+ {
+ get { return (ControlTemplate)GetValue(ControlTemplateProperty); }
+ set { SetValue(ControlTemplateProperty, value); }
+ }
+
+ IList<Element> IControlTemplated.InternalChildren => InternalChildren;
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ LayoutOptions vOptions = view.VerticalOptions;
+ LayoutOptions hOptions = view.HorizontalOptions;
+
+ var result = LayoutConstraint.None;
+ if (vOptions.Alignment == LayoutAlignment.Fill)
+ result |= LayoutConstraint.VerticallyFixed;
+ if (hOptions.Alignment == LayoutAlignment.Fill)
+ result |= LayoutConstraint.HorizontallyFixed;
+
+ view.ComputedConstraint = result;
+ }
+
+ internal override void SetChildInheritedBindingContext(Element child, object context)
+ {
+ if (ControlTemplate == null)
+ base.SetChildInheritedBindingContext(child, context);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TemplatedView.cs b/Xamarin.Forms.Core/TemplatedView.cs
new file mode 100644
index 00000000..30dadb92
--- /dev/null
+++ b/Xamarin.Forms.Core/TemplatedView.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public class TemplatedView : Layout, IControlTemplated
+ {
+ public static readonly BindableProperty ControlTemplateProperty = BindableProperty.Create(nameof(ControlTemplate), typeof(ControlTemplate), typeof(TemplatedView), null,
+ propertyChanged: TemplateUtilities.OnControlTemplateChanged);
+
+ public ControlTemplate ControlTemplate
+ {
+ get { return (ControlTemplate)GetValue(ControlTemplateProperty); }
+ set { SetValue(ControlTemplateProperty, value); }
+ }
+
+ IList<Element> IControlTemplated.InternalChildren => InternalChildren;
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ Element element = LogicalChildren[i];
+ var child = element as View;
+ if (child != null)
+ LayoutChildIntoBoundingRegion(child, new Rectangle(x, y, width, height));
+ }
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ double widthRequest = WidthRequest;
+ double heightRequest = HeightRequest;
+ var childRequest = new SizeRequest();
+
+ if ((widthRequest == -1 || heightRequest == -1) && InternalChildren.Count > 0)
+ {
+ childRequest = ((View)InternalChildren[0]).Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
+ }
+
+ return new SizeRequest
+ {
+ Request = new Size { Width = widthRequest != -1 ? widthRequest : childRequest.Request.Width, Height = heightRequest != -1 ? heightRequest : childRequest.Request.Height },
+ Minimum = childRequest.Minimum
+ };
+ }
+
+ internal override void ComputeConstraintForView(View view)
+ {
+ bool isFixedHorizontally = (Constraint & LayoutConstraint.HorizontallyFixed) != 0;
+ bool isFixedVertically = (Constraint & LayoutConstraint.VerticallyFixed) != 0;
+
+ var result = LayoutConstraint.None;
+ if (isFixedVertically && view.VerticalOptions.Alignment == LayoutAlignment.Fill)
+ result |= LayoutConstraint.VerticallyFixed;
+ if (isFixedHorizontally && view.HorizontalOptions.Alignment == LayoutAlignment.Fill)
+ result |= LayoutConstraint.HorizontallyFixed;
+ view.ComputedConstraint = result;
+ }
+
+ internal override void SetChildInheritedBindingContext(Element child, object context)
+ {
+ if (ControlTemplate == null)
+ base.SetChildInheritedBindingContext(child, context);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TextAlignment.cs b/Xamarin.Forms.Core/TextAlignment.cs
new file mode 100644
index 00000000..cee32c97
--- /dev/null
+++ b/Xamarin.Forms.Core/TextAlignment.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public enum TextAlignment
+ {
+ Start,
+ Center,
+ End
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TextChangedEventArgs.cs b/Xamarin.Forms.Core/TextChangedEventArgs.cs
new file mode 100644
index 00000000..dc4202ae
--- /dev/null
+++ b/Xamarin.Forms.Core/TextChangedEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class TextChangedEventArgs : EventArgs
+ {
+ public TextChangedEventArgs(string oldTextValue, string newTextValue)
+ {
+ OldTextValue = oldTextValue;
+ NewTextValue = newTextValue;
+ }
+
+ public string NewTextValue { get; private set; }
+
+ public string OldTextValue { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TextKeyboard.cs b/Xamarin.Forms.Core/TextKeyboard.cs
new file mode 100644
index 00000000..d2d1942f
--- /dev/null
+++ b/Xamarin.Forms.Core/TextKeyboard.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal sealed class TextKeyboard : Keyboard
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Thickness.cs b/Xamarin.Forms.Core/Thickness.cs
new file mode 100644
index 00000000..ee1850df
--- /dev/null
+++ b/Xamarin.Forms.Core/Thickness.cs
@@ -0,0 +1,92 @@
+using System.Diagnostics;
+
+namespace Xamarin.Forms
+{
+ [DebuggerDisplay("Left={Left}, Top={Top}, Right={Right}, Bottom={Bottom}, HorizontalThickness={HorizontalThickness}, VerticalThickness={VerticalThickness}")]
+ [TypeConverter(typeof(ThicknessTypeConverter))]
+ public struct Thickness
+ {
+ public double Left { get; set; }
+
+ public double Top { get; set; }
+
+ public double Right { get; set; }
+
+ public double Bottom { get; set; }
+
+ public double HorizontalThickness
+ {
+ get { return Left + Right; }
+ }
+
+ public double VerticalThickness
+ {
+ get { return Top + Bottom; }
+ }
+
+ internal bool IsDefault
+ {
+ get { return Left == 0 && Top == 0 && Right == 0 && Left == 0; }
+ }
+
+ public Thickness(double uniformSize) : this(uniformSize, uniformSize, uniformSize, uniformSize)
+ {
+ }
+
+ public Thickness(double horizontalSize, double verticalSize) : this(horizontalSize, verticalSize, horizontalSize, verticalSize)
+ {
+ }
+
+ public Thickness(double left, double top, double right, double bottom) : this()
+ {
+ Left = left;
+ Top = top;
+ Right = right;
+ Bottom = bottom;
+ }
+
+ public static implicit operator Thickness(Size size)
+ {
+ return new Thickness(size.Width, size.Height, size.Width, size.Height);
+ }
+
+ public static implicit operator Thickness(double uniformSize)
+ {
+ return new Thickness(uniformSize);
+ }
+
+ bool Equals(Thickness other)
+ {
+ return Left.Equals(other.Left) && Top.Equals(other.Top) && Right.Equals(other.Right) && Bottom.Equals(other.Bottom);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ return false;
+ return obj is Thickness && Equals((Thickness)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = Left.GetHashCode();
+ hashCode = (hashCode * 397) ^ Top.GetHashCode();
+ hashCode = (hashCode * 397) ^ Right.GetHashCode();
+ hashCode = (hashCode * 397) ^ Bottom.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(Thickness left, Thickness right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Thickness left, Thickness right)
+ {
+ return !left.Equals(right);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ThicknessTypeConverter.cs b/Xamarin.Forms.Core/ThicknessTypeConverter.cs
new file mode 100644
index 00000000..5e79d25c
--- /dev/null
+++ b/Xamarin.Forms.Core/ThicknessTypeConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public class ThicknessTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ {
+ double l, t, r, b;
+ string[] thickness = value.Split(',');
+ switch (thickness.Length)
+ {
+ case 1:
+ if (double.TryParse(thickness[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l))
+ return new Thickness(l);
+ break;
+ case 2:
+ if (double.TryParse(thickness[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l) && double.TryParse(thickness[1], NumberStyles.Number, CultureInfo.InvariantCulture, out t))
+ return new Thickness(l, t);
+ break;
+ case 4:
+ if (double.TryParse(thickness[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l) && double.TryParse(thickness[1], NumberStyles.Number, CultureInfo.InvariantCulture, out t) &&
+ double.TryParse(thickness[2], NumberStyles.Number, CultureInfo.InvariantCulture, out r) && double.TryParse(thickness[3], NumberStyles.Number, CultureInfo.InvariantCulture, out b))
+ return new Thickness(l, t, r, b);
+ break;
+ }
+ }
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Thickness)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Ticker.cs b/Xamarin.Forms.Core/Ticker.cs
new file mode 100644
index 00000000..ed828f8a
--- /dev/null
+++ b/Xamarin.Forms.Core/Ticker.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace Xamarin.Forms
+{
+ internal class Ticker
+ {
+ static Ticker s_ticker;
+ readonly Stopwatch _stopwatch;
+ readonly SynchronizationContext _sync;
+ readonly List<Tuple<int, Func<long, bool>>> _timeouts;
+
+ readonly ITimer _timer;
+ int _count;
+ bool _enabled;
+
+ internal Ticker()
+ {
+ _sync = SynchronizationContext.Current;
+ _count = 0;
+ _timer = Device.PlatformServices.CreateTimer(HandleElapsed, null, Timeout.Infinite, Timeout.Infinite);
+ _timeouts = new List<Tuple<int, Func<long, bool>>>();
+
+ _stopwatch = new Stopwatch();
+ }
+
+ public static Ticker Default
+ {
+ internal set { s_ticker = value; }
+ get { return s_ticker ?? (s_ticker = new Ticker()); }
+ }
+
+ public virtual int Insert(Func<long, bool> timeout)
+ {
+ _count++;
+ _timeouts.Add(new Tuple<int, Func<long, bool>>(_count, timeout));
+
+ if (!_enabled)
+ {
+ _enabled = true;
+ Enable();
+ }
+
+ return _count;
+ }
+
+ public virtual void Remove(int handle)
+ {
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ _timeouts.RemoveAll(t => t.Item1 == handle);
+
+ if (!_timeouts.Any())
+ {
+ _enabled = false;
+ Disable();
+ }
+ });
+ }
+
+ protected virtual void DisableTimer()
+ {
+ _timer.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+
+ protected virtual void EnableTimer()
+ {
+ _timer.Change(16, 16);
+ }
+
+ protected void SendSignals(int timestep = -1)
+ {
+ long step = timestep >= 0 ? timestep : _stopwatch.ElapsedMilliseconds;
+ _stopwatch.Reset();
+ _stopwatch.Start();
+
+ var localCopy = new List<Tuple<int, Func<long, bool>>>(_timeouts);
+ foreach (Tuple<int, Func<long, bool>> timeout in localCopy)
+ {
+ bool remove = !timeout.Item2(step);
+ if (remove)
+ _timeouts.RemoveAll(t => t.Item1 == timeout.Item1);
+ }
+
+ if (!_timeouts.Any())
+ {
+ _enabled = false;
+ Disable();
+ }
+ }
+
+ void Disable()
+ {
+ _stopwatch.Reset();
+ DisableTimer();
+ }
+
+ void Enable()
+ {
+ _stopwatch.Start();
+ EnableTimer();
+ }
+
+ void HandleElapsed(object state)
+ {
+ if (_timeouts.Count > 0)
+ {
+ _sync.Post(o => SendSignals(), null);
+ _stopwatch.Reset();
+ _stopwatch.Start();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TimePicker.cs b/Xamarin.Forms.Core/TimePicker.cs
new file mode 100644
index 00000000..a9cf2e19
--- /dev/null
+++ b/Xamarin.Forms.Core/TimePicker.cs
@@ -0,0 +1,29 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_TimePickerRenderer))]
+ public class TimePicker : View
+ {
+ public static readonly BindableProperty FormatProperty = BindableProperty.Create("Format", typeof(string), typeof(TimePicker), "t");
+
+ public static readonly BindableProperty TimeProperty = BindableProperty.Create("Time", typeof(TimeSpan), typeof(TimePicker), new TimeSpan(0), BindingMode.TwoWay, (bindable, value) =>
+ {
+ var time = (TimeSpan)value;
+ return time.TotalHours < 24 && time.TotalMilliseconds >= 0;
+ });
+
+ public string Format
+ {
+ get { return (string)GetValue(FormatProperty); }
+ set { SetValue(FormatProperty, value); }
+ }
+
+ public TimeSpan Time
+ {
+ get { return (TimeSpan)GetValue(TimeProperty); }
+ set { SetValue(TimeProperty, value); }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ToggledEventArgs.cs b/Xamarin.Forms.Core/ToggledEventArgs.cs
new file mode 100644
index 00000000..227e93cb
--- /dev/null
+++ b/Xamarin.Forms.Core/ToggledEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ToggledEventArgs : EventArgs
+ {
+ public ToggledEventArgs(bool value)
+ {
+ Value = value;
+ }
+
+ public bool Value { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Toolbar.cs b/Xamarin.Forms.Core/Toolbar.cs
new file mode 100644
index 00000000..4405d1f6
--- /dev/null
+++ b/Xamarin.Forms.Core/Toolbar.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ // Marked as internal for 1.0 release until we are ready to release this
+ [RenderWith(typeof(_ToolbarRenderer))]
+ internal class Toolbar : View
+ {
+ readonly List<ToolbarItem> _items = new List<ToolbarItem>();
+
+ public ReadOnlyCollection<ToolbarItem> Items
+ {
+ get { return new ReadOnlyCollection<ToolbarItem>(_items); }
+ }
+
+ public void Add(ToolbarItem item)
+ {
+ if (_items.Contains(item))
+ return;
+ _items.Add(item);
+ if (ItemAdded != null)
+ ItemAdded(this, new ToolbarItemEventArgs(item));
+ }
+
+ public void Clear()
+ {
+ _items.Clear();
+ }
+
+ public event EventHandler<ToolbarItemEventArgs> ItemAdded;
+
+ public event EventHandler<ToolbarItemEventArgs> ItemRemoved;
+
+ public void Remove(ToolbarItem item)
+ {
+ if (_items.Remove(item))
+ {
+ if (ItemRemoved != null)
+ ItemRemoved(this, new ToolbarItemEventArgs(item));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ToolbarItem.cs b/Xamarin.Forms.Core/ToolbarItem.cs
new file mode 100644
index 00000000..0a0a2e05
--- /dev/null
+++ b/Xamarin.Forms.Core/ToolbarItem.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ToolbarItem : MenuItem
+ {
+ static readonly BindableProperty OrderProperty = BindableProperty.Create("Order", typeof(ToolbarItemOrder), typeof(ToolbarItem), ToolbarItemOrder.Default, validateValue: (bo, o) =>
+ {
+ var order = (ToolbarItemOrder)o;
+ return order == ToolbarItemOrder.Default || order == ToolbarItemOrder.Primary || order == ToolbarItemOrder.Secondary;
+ });
+
+ static readonly BindableProperty PriorityProperty = BindableProperty.Create("Priority", typeof(int), typeof(ToolbarItem), 0);
+
+ public ToolbarItem()
+ {
+ }
+
+ public ToolbarItem(string name, string icon, Action activated, ToolbarItemOrder order = ToolbarItemOrder.Default, int priority = 0)
+ {
+ if (activated == null)
+ throw new ArgumentNullException("activated");
+
+ Text = name;
+ Icon = icon;
+ Clicked += (s, e) => activated();
+ Order = order;
+ Priority = priority;
+ }
+
+ [Obsolete("Now that ToolbarItem is based on MenuItem, .Text has replaced .Name")]
+ public string Name
+ {
+ get { return Text; }
+ set { Text = value; }
+ }
+
+ public ToolbarItemOrder Order
+ {
+ get { return (ToolbarItemOrder)GetValue(OrderProperty); }
+ set { SetValue(OrderProperty, value); }
+ }
+
+ public int Priority
+ {
+ get { return (int)GetValue(PriorityProperty); }
+ set { SetValue(PriorityProperty, value); }
+ }
+
+ [Obsolete("Activated has been replaced by the more consistent 'Clicked'")]
+ public event EventHandler Activated
+ {
+ add { Clicked += value; }
+ remove { Clicked -= value; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ToolbarItemEventArgs.cs b/Xamarin.Forms.Core/ToolbarItemEventArgs.cs
new file mode 100644
index 00000000..8a6e0124
--- /dev/null
+++ b/Xamarin.Forms.Core/ToolbarItemEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class ToolbarItemEventArgs : EventArgs
+ {
+ public ToolbarItemEventArgs(ToolbarItem item)
+ {
+ ToolbarItem = item;
+ }
+
+ public ToolbarItem ToolbarItem { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ToolbarItemOrder.cs b/Xamarin.Forms.Core/ToolbarItemOrder.cs
new file mode 100644
index 00000000..0fbf6c17
--- /dev/null
+++ b/Xamarin.Forms.Core/ToolbarItemOrder.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.Forms
+{
+ public enum ToolbarItemOrder
+ {
+ Default,
+ Primary,
+ Secondary
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ToolbarTracker.cs b/Xamarin.Forms.Core/ToolbarTracker.cs
new file mode 100644
index 00000000..74580065
--- /dev/null
+++ b/Xamarin.Forms.Core/ToolbarTracker.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal class ToolbarTracker
+ {
+ int _masterDetails;
+ Page _target;
+
+ public IEnumerable<Page> AdditionalTargets { get; set; }
+
+ public bool HaveMasterDetail
+ {
+ get { return _masterDetails > 0; }
+ }
+
+ public bool SeparateMasterDetail { get; set; }
+
+ public Page Target
+ {
+ get { return _target; }
+ set
+ {
+ if (_target == value)
+ return;
+
+ UntrackTarget(_target);
+ _target = value;
+
+ if (_target != null)
+ TrackTarget(_target);
+ EmitCollectionChanged();
+ }
+ }
+
+ public IEnumerable<ToolbarItem> ToolbarItems
+ {
+ get
+ {
+ if (Target == null)
+ return Enumerable.Empty<ToolbarItem>();
+ IEnumerable<ToolbarItem> items = GetCurrentToolbarItems(Target);
+ if (AdditionalTargets != null)
+ items = items.Concat(AdditionalTargets.SelectMany(t => t.ToolbarItems));
+
+ return items.OrderBy(ti => ti.Priority);
+ }
+ }
+
+ public event EventHandler CollectionChanged;
+
+ void EmitCollectionChanged()
+ {
+ if (CollectionChanged != null)
+ CollectionChanged(this, EventArgs.Empty);
+ }
+
+ IEnumerable<ToolbarItem> GetCurrentToolbarItems(Page page)
+ {
+ var result = new List<ToolbarItem>();
+ result.AddRange(page.ToolbarItems);
+
+ if (page is MasterDetailPage)
+ {
+ var masterDetail = (MasterDetailPage)page;
+ if (SeparateMasterDetail)
+ {
+ if (masterDetail.IsPresented)
+ {
+ if (masterDetail.Master != null)
+ result.AddRange(GetCurrentToolbarItems(masterDetail.Master));
+ }
+ else
+ {
+ if (masterDetail.Detail != null)
+ result.AddRange(GetCurrentToolbarItems(masterDetail.Detail));
+ }
+ }
+ else
+ {
+ if (masterDetail.Master != null)
+ result.AddRange(GetCurrentToolbarItems(masterDetail.Master));
+ if (masterDetail.Detail != null)
+ result.AddRange(GetCurrentToolbarItems(masterDetail.Detail));
+ }
+ }
+ else if (page is IPageContainer<Page>)
+ {
+ var container = (IPageContainer<Page>)page;
+ if (container.CurrentPage != null)
+ result.AddRange(GetCurrentToolbarItems(container.CurrentPage));
+ }
+
+ return result;
+ }
+
+ void OnChildAdded(object sender, ElementEventArgs eventArgs)
+ {
+ var page = eventArgs.Element as Page;
+ if (page == null)
+ return;
+
+ RegisterChildPage(page);
+ }
+
+ void OnChildRemoved(object sender, ElementEventArgs eventArgs)
+ {
+ var page = eventArgs.Element as Page;
+ if (page == null)
+ return;
+
+ UnregisterChildPage(page);
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ EmitCollectionChanged();
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
+ {
+ if (propertyChangedEventArgs.PropertyName == NavigationPage.CurrentPageProperty.PropertyName || propertyChangedEventArgs.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName ||
+ propertyChangedEventArgs.PropertyName == "Detail" || propertyChangedEventArgs.PropertyName == "Master")
+ {
+ EmitCollectionChanged();
+ }
+ }
+
+ void RegisterChildPage(Page page)
+ {
+ if (page is MasterDetailPage)
+ _masterDetails++;
+
+ ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged += OnCollectionChanged;
+ page.PropertyChanged += OnPropertyChanged;
+ }
+
+ void TrackTarget(Page page)
+ {
+ if (page == null)
+ return;
+
+ if (page is MasterDetailPage)
+ _masterDetails++;
+
+ ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged += OnCollectionChanged;
+ page.Descendants().OfType<Page>().ForEach(RegisterChildPage);
+
+ page.DescendantAdded += OnChildAdded;
+ page.DescendantRemoved += OnChildRemoved;
+ page.PropertyChanged += OnPropertyChanged;
+ }
+
+ void UnregisterChildPage(Page page)
+ {
+ if (page is MasterDetailPage)
+ _masterDetails--;
+
+ ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged -= OnCollectionChanged;
+ page.PropertyChanged -= OnPropertyChanged;
+ }
+
+ void UntrackTarget(Page page)
+ {
+ if (page == null)
+ return;
+
+ if (page is MasterDetailPage)
+ _masterDetails--;
+
+ ((ObservableCollection<ToolbarItem>)page.ToolbarItems).CollectionChanged -= OnCollectionChanged;
+ page.Descendants().OfType<Page>().ForEach(UnregisterChildPage);
+
+ page.DescendantAdded -= OnChildAdded;
+ page.DescendantRemoved -= OnChildRemoved;
+ page.PropertyChanged -= OnPropertyChanged;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TrackableCollection.cs b/Xamarin.Forms.Core/TrackableCollection.cs
new file mode 100644
index 00000000..959007ad
--- /dev/null
+++ b/Xamarin.Forms.Core/TrackableCollection.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.ObjectModel;
+
+namespace Xamarin.Forms
+{
+ internal class TrackableCollection<T> : ObservableCollection<T>
+ {
+ public event EventHandler Clearing;
+
+ protected override void ClearItems()
+ {
+ Clearing?.Invoke(this, EventArgs.Empty);
+ base.ClearItems();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Tweener.cs b/Xamarin.Forms.Core/Tweener.cs
new file mode 100644
index 00000000..73ed7853
--- /dev/null
+++ b/Xamarin.Forms.Core/Tweener.cs
@@ -0,0 +1,124 @@
+//
+// Tweener.cs
+//
+// Author:
+// Jason Smith <jason.smith@xamarin.com>
+//
+// Copyright (c) 2012 Xamarin Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Xamarin.Forms
+{
+ internal class Tweener
+ {
+ long _lastMilliseconds;
+
+ int _timer;
+
+ public Tweener(uint length)
+ {
+ Value = 0.0f;
+ Length = length;
+ Loop = false;
+ }
+
+ public AnimatableKey Handle { get; set; }
+
+ public uint Length { get; }
+
+ public bool Loop { get; set; }
+
+ public double Value { get; private set; }
+
+ public event EventHandler Finished;
+
+ public void Pause()
+ {
+ if (_timer != 0)
+ {
+ Ticker.Default.Remove(_timer);
+ _timer = 0;
+ }
+ }
+
+ public void Start()
+ {
+ Pause();
+
+ _lastMilliseconds = 0;
+ _timer = Ticker.Default.Insert(step =>
+ {
+ long ms = step + _lastMilliseconds;
+
+ Value = Math.Min(1.0f, ms / (double)Length);
+
+ _lastMilliseconds = ms;
+
+ if (ValueUpdated != null)
+ ValueUpdated(this, EventArgs.Empty);
+
+ if (Value >= 1.0f)
+ {
+ if (Loop)
+ {
+ _lastMilliseconds = 0;
+ Value = 0.0f;
+ return true;
+ }
+
+ if (Finished != null)
+ Finished(this, EventArgs.Empty);
+ Value = 0.0f;
+ _timer = 0;
+ return false;
+ }
+ return true;
+ });
+ }
+
+ public void Stop()
+ {
+ Pause();
+ Value = 1.0f;
+ if (Finished != null)
+ Finished(this, EventArgs.Empty);
+ Value = 0.0f;
+ }
+
+ public event EventHandler ValueUpdated;
+
+ ~Tweener()
+ {
+ if (_timer != 0)
+ {
+ try
+ {
+ Ticker.Default.Remove(_timer);
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ }
+ _timer = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TypeConverter.cs b/Xamarin.Forms.Core/TypeConverter.cs
new file mode 100644
index 00000000..7bbf221f
--- /dev/null
+++ b/Xamarin.Forms.Core/TypeConverter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms
+{
+ public abstract class TypeConverter
+ {
+ public virtual bool CanConvertFrom(Type sourceType)
+ {
+ if (sourceType == null)
+ throw new ArgumentNullException("sourceType");
+
+ return sourceType == typeof(string);
+ }
+
+ [Obsolete("use ConvertFromInvariantString (string)")]
+ public virtual object ConvertFrom(object o)
+ {
+ return null;
+ }
+
+ [Obsolete("use ConvertFromInvariantString (string)")]
+ public virtual object ConvertFrom(CultureInfo culture, object o)
+ {
+ return null;
+ }
+
+ public virtual object ConvertFromInvariantString(string value)
+ {
+ return ConvertFrom(CultureInfo.InvariantCulture, value);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TypeConverterAttribute.cs b/Xamarin.Forms.Core/TypeConverterAttribute.cs
new file mode 100644
index 00000000..261b00e0
--- /dev/null
+++ b/Xamarin.Forms.Core/TypeConverterAttribute.cs
@@ -0,0 +1,74 @@
+//
+// System.ComponentModel.TypeConverterAttribute
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+// Andreas Nahr (ClassDevelopment@A-SoftTech.com)
+//
+// (C) 2002 Ximian, Inc (http://www.ximian.com)
+// (C) 2003 Andreas Nahr
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Xamarin.Forms
+{
+ [AttributeUsage(AttributeTargets.All)]
+ public sealed class TypeConverterAttribute : Attribute
+ {
+ internal static string[] TypeConvertersType = { "Xamarin.Forms.TypeConverterAttribute", "System.ComponentModel.TypeConverterAttribute" };
+
+ public static readonly TypeConverterAttribute Default = new TypeConverterAttribute();
+
+ public TypeConverterAttribute()
+ {
+ ConverterTypeName = "";
+ }
+
+ public TypeConverterAttribute(string typeName)
+ {
+ ConverterTypeName = typeName;
+ }
+
+ public TypeConverterAttribute(Type type)
+ {
+ ConverterTypeName = type.AssemblyQualifiedName;
+ }
+
+ public string ConverterTypeName { get; }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is TypeConverterAttribute))
+ return false;
+
+ return ((TypeConverterAttribute)obj).ConverterTypeName == ConverterTypeName;
+ }
+
+ public override int GetHashCode()
+ {
+ return ConverterTypeName.GetHashCode();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/TypeTypeConverter.cs b/Xamarin.Forms.Core/TypeTypeConverter.cs
new file mode 100644
index 00000000..115f1d96
--- /dev/null
+++ b/Xamarin.Forms.Core/TypeTypeConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms
+{
+ public sealed class TypeTypeConverter : TypeConverter, IExtendedTypeConverter
+ {
+ [Obsolete("Use ConvertFromInvariantString (string, IServiceProvider)")]
+ object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
+ {
+ return ((IExtendedTypeConverter)this).ConvertFromInvariantString((string)value, serviceProvider);
+ }
+
+ object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
+ {
+ if (serviceProvider == null)
+ throw new ArgumentNullException("serviceProvider");
+ var typeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
+ if (typeResolver == null)
+ throw new ArgumentException("No IXamlTypeResolver in IServiceProvider");
+
+ return typeResolver.Resolve(value, serviceProvider);
+ }
+
+ public override object ConvertFromInvariantString(string value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/UnsolvableConstraintsException.cs b/Xamarin.Forms.Core/UnsolvableConstraintsException.cs
new file mode 100644
index 00000000..40e37812
--- /dev/null
+++ b/Xamarin.Forms.Core/UnsolvableConstraintsException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class UnsolvableConstraintsException : Exception
+ {
+ public UnsolvableConstraintsException(string message) : base(message)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/UriImageSource.cs b/Xamarin.Forms.Core/UriImageSource.cs
new file mode 100644
index 00000000..f6b3ead1
--- /dev/null
+++ b/Xamarin.Forms.Core/UriImageSource.cs
@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ public sealed class UriImageSource : ImageSource
+ {
+ internal const string CacheName = "ImageLoaderCache";
+
+ public static readonly BindableProperty UriProperty = BindableProperty.Create("Uri", typeof(Uri), typeof(UriImageSource), default(Uri),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((UriImageSource)bindable).OnUriChanged(), validateValue: (bindable, value) => value == null || ((Uri)value).IsAbsoluteUri);
+
+ static readonly IIsolatedStorageFile Store = Device.PlatformServices.GetUserStoreForApplication();
+
+ static readonly object s_syncHandle = new object();
+ static readonly Dictionary<string, LockingSemaphore> s_semaphores = new Dictionary<string, LockingSemaphore>();
+
+ TimeSpan _cacheValidity = TimeSpan.FromDays(1);
+
+ bool _cachingEnabled = true;
+
+ static UriImageSource()
+ {
+ if (!Store.GetDirectoryExistsAsync(CacheName).Result)
+ Store.CreateDirectoryAsync(CacheName).Wait();
+ }
+
+ public TimeSpan CacheValidity
+ {
+ get { return _cacheValidity; }
+ set
+ {
+ if (_cacheValidity == value)
+ return;
+
+ OnPropertyChanging();
+ _cacheValidity = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool CachingEnabled
+ {
+ get { return _cachingEnabled; }
+ set
+ {
+ if (_cachingEnabled == value)
+ return;
+
+ OnPropertyChanging();
+ _cachingEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [TypeConverter(typeof(UriTypeConverter))]
+ public Uri Uri
+ {
+ get { return (Uri)GetValue(UriProperty); }
+ set { SetValue(UriProperty, value); }
+ }
+
+ internal async Task<Stream> GetStreamAsync(CancellationToken userToken = default(CancellationToken))
+ {
+ OnLoadingStarted();
+ userToken.Register(CancellationTokenSource.Cancel);
+ Stream stream = null;
+ try
+ {
+ stream = await GetStreamAsync(Uri, CancellationTokenSource.Token);
+ OnLoadingCompleted(false);
+ }
+ catch (OperationCanceledException)
+ {
+ OnLoadingCompleted(true);
+ throw;
+#if DEBUG
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ throw;
+#endif
+ }
+ return stream;
+ }
+
+ static string GetCacheKey(Uri uri)
+ {
+ return Device.PlatformServices.GetMD5Hash(uri.AbsoluteUri);
+ }
+
+ async Task<bool> GetHasLocallyCachedCopyAsync(string key, bool checkValidity = true)
+ {
+ DateTime now = DateTime.UtcNow;
+ DateTime? lastWriteTime = await GetLastWriteTimeUtcAsync(key).ConfigureAwait(false);
+ return lastWriteTime.HasValue && now - lastWriteTime.Value < CacheValidity;
+ }
+
+ static async Task<DateTime?> GetLastWriteTimeUtcAsync(string key)
+ {
+ string path = Path.Combine(CacheName, key);
+ if (!await Store.GetFileExistsAsync(path).ConfigureAwait(false))
+ return null;
+
+ return (await Store.GetLastWriteTimeAsync(path).ConfigureAwait(false)).UtcDateTime;
+ }
+
+ async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ Stream stream;
+ if (!CachingEnabled)
+ {
+ try
+ {
+ stream = await Device.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ stream = null;
+ }
+ }
+ else
+ stream = await GetStreamFromCacheAsync(uri, cancellationToken).ConfigureAwait(false);
+ return stream;
+ }
+
+ async Task<Stream> GetStreamAsyncUnchecked(string key, Uri uri, CancellationToken cancellationToken)
+ {
+ if (await GetHasLocallyCachedCopyAsync(key).ConfigureAwait(false))
+ {
+ var retry = 5;
+ while (retry >= 0)
+ {
+ int backoff;
+ try
+ {
+ Stream result = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false);
+ return result;
+ }
+ catch (IOException)
+ {
+ // iOS seems to not like 2 readers opening the file at the exact same time, back off for random amount of time
+ backoff = new Random().Next(1, 5);
+ retry--;
+ }
+
+ if (backoff > 0)
+ {
+ await Task.Delay(backoff);
+ }
+ }
+ return null;
+ }
+
+ Stream stream;
+ try
+ {
+ stream = await Device.GetStreamAsync(uri, cancellationToken).ConfigureAwait(false);
+ if (stream == null)
+ return null;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+
+ Stream writeStream = await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Create, FileAccess.Write).ConfigureAwait(false);
+ await stream.CopyToAsync(writeStream, 16384, cancellationToken).ConfigureAwait(false);
+ if (writeStream != null)
+ writeStream.Dispose();
+
+ stream.Dispose();
+
+ return await Store.OpenFileAsync(Path.Combine(CacheName, key), FileMode.Open, FileAccess.Read).ConfigureAwait(false);
+ }
+
+ async Task<Stream> GetStreamFromCacheAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ string key = GetCacheKey(uri);
+ LockingSemaphore sem;
+ lock(s_syncHandle)
+ {
+ if (s_semaphores.ContainsKey(key))
+ sem = s_semaphores[key];
+ else
+ s_semaphores.Add(key, sem = new LockingSemaphore(1));
+ }
+
+ try
+ {
+ await sem.WaitAsync(cancellationToken);
+ Stream stream = await GetStreamAsyncUnchecked(key, uri, cancellationToken);
+ if (stream == null)
+ {
+ sem.Release();
+ return null;
+ }
+ var wrapped = new StreamWrapper(stream);
+ wrapped.Disposed += (o, e) => sem.Release();
+ return wrapped;
+ }
+ catch (OperationCanceledException)
+ {
+ sem.Release();
+ throw;
+ }
+ }
+
+ void OnUriChanged()
+ {
+ if (CancellationTokenSource != null)
+ CancellationTokenSource.Cancel();
+ OnSourceChanged();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/UriTypeConverter.cs b/Xamarin.Forms.Core/UriTypeConverter.cs
new file mode 100644
index 00000000..0a24cab3
--- /dev/null
+++ b/Xamarin.Forms.Core/UriTypeConverter.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class UriTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return null;
+ return new Uri(value, UriKind.RelativeOrAbsolute);
+ }
+
+ bool CanConvert(Type type)
+ {
+ if (type == typeof(string))
+ return true;
+ if (type == typeof(Uri))
+ return true;
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/UrlKeyboard.cs b/Xamarin.Forms.Core/UrlKeyboard.cs
new file mode 100644
index 00000000..8266d95e
--- /dev/null
+++ b/Xamarin.Forms.Core/UrlKeyboard.cs
@@ -0,0 +1,6 @@
+namespace Xamarin.Forms
+{
+ internal sealed class UrlKeyboard : Keyboard
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/UrlWebViewSource.cs b/Xamarin.Forms.Core/UrlWebViewSource.cs
new file mode 100644
index 00000000..72541045
--- /dev/null
+++ b/Xamarin.Forms.Core/UrlWebViewSource.cs
@@ -0,0 +1,19 @@
+namespace Xamarin.Forms
+{
+ public class UrlWebViewSource : WebViewSource
+ {
+ public static readonly BindableProperty UrlProperty = BindableProperty.Create("Url", typeof(string), typeof(UrlWebViewSource), default(string),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((UrlWebViewSource)bindable).OnSourceChanged());
+
+ public string Url
+ {
+ get { return (string)GetValue(UrlProperty); }
+ set { SetValue(UrlProperty, value); }
+ }
+
+ internal override void Load(IWebViewRenderer renderer)
+ {
+ renderer.LoadUrl(Url);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ValueChangedEventArgs.cs b/Xamarin.Forms.Core/ValueChangedEventArgs.cs
new file mode 100644
index 00000000..8ea718c4
--- /dev/null
+++ b/Xamarin.Forms.Core/ValueChangedEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class ValueChangedEventArgs : EventArgs
+ {
+ public ValueChangedEventArgs(double oldValue, double newValue)
+ {
+ OldValue = oldValue;
+ NewValue = newValue;
+ }
+
+ public double NewValue { get; private set; }
+
+ public double OldValue { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Vec2.cs b/Xamarin.Forms.Core/Vec2.cs
new file mode 100644
index 00000000..2c7512d3
--- /dev/null
+++ b/Xamarin.Forms.Core/Vec2.cs
@@ -0,0 +1,14 @@
+namespace Xamarin.Forms
+{
+ public struct Vec2
+ {
+ public double X;
+ public double Y;
+
+ public Vec2(double x, double y)
+ {
+ X = x;
+ Y = y;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/View.cs b/Xamarin.Forms.Core/View.cs
new file mode 100644
index 00000000..d03c74bb
--- /dev/null
+++ b/Xamarin.Forms.Core/View.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ public class View : VisualElement, IViewController
+ {
+ public static readonly BindableProperty VerticalOptionsProperty = BindableProperty.Create("VerticalOptions", typeof(LayoutOptions), typeof(View), LayoutOptions.Fill,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((View)bindable).InvalidateMeasure(InvalidationTrigger.VerticalOptionsChanged));
+
+ public static readonly BindableProperty HorizontalOptionsProperty = BindableProperty.Create("HorizontalOptions", typeof(LayoutOptions), typeof(View), LayoutOptions.Fill,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((View)bindable).InvalidateMeasure(InvalidationTrigger.HorizontalOptionsChanged));
+
+ public static readonly BindableProperty MarginProperty = BindableProperty.Create("Margin", typeof(Thickness), typeof(View), default(Thickness), propertyChanged: MarginPropertyChanged);
+
+ readonly ObservableCollection<IGestureRecognizer> _gestureRecognizers = new ObservableCollection<IGestureRecognizer>();
+
+ protected internal View()
+ {
+ _gestureRecognizers.CollectionChanged += (sender, args) =>
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ foreach (IElement item in args.NewItems.OfType<IElement>())
+ {
+ ValidateGesture(item as IGestureRecognizer);
+ item.Parent = this;
+ }
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ foreach (IElement item in args.OldItems.OfType<IElement>())
+ item.Parent = null;
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ foreach (IElement item in args.NewItems.OfType<IElement>())
+ {
+ ValidateGesture(item as IGestureRecognizer);
+ item.Parent = this;
+ }
+ foreach (IElement item in args.OldItems.OfType<IElement>())
+ item.Parent = null;
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ foreach (IElement item in _gestureRecognizers.OfType<IElement>())
+ item.Parent = this;
+ break;
+ }
+ };
+ }
+
+ public IList<IGestureRecognizer> GestureRecognizers
+ {
+ get { return _gestureRecognizers; }
+ }
+
+ public LayoutOptions HorizontalOptions
+ {
+ get { return (LayoutOptions)GetValue(HorizontalOptionsProperty); }
+ set { SetValue(HorizontalOptionsProperty, value); }
+ }
+
+ public Thickness Margin
+ {
+ get { return (Thickness)GetValue(MarginProperty); }
+ set { SetValue(MarginProperty, value); }
+ }
+
+ public LayoutOptions VerticalOptions
+ {
+ get { return (LayoutOptions)GetValue(VerticalOptionsProperty); }
+ set { SetValue(VerticalOptionsProperty, value); }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ var gotBindingContext = false;
+ object bc = null;
+
+ for (var i = 0; i < GestureRecognizers.Count; i++)
+ {
+ var bo = GestureRecognizers[i] as BindableObject;
+ if (bo == null)
+ continue;
+
+ if (!gotBindingContext)
+ {
+ bc = BindingContext;
+ gotBindingContext = true;
+ }
+
+ SetInheritedBindingContext(bo, bc);
+ }
+
+ base.OnBindingContextChanged();
+ }
+
+ static void MarginPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ ((View)bindable).InvalidateMeasure(InvalidationTrigger.MarginChanged);
+ }
+
+ void ValidateGesture(IGestureRecognizer gesture)
+ {
+ if (gesture == null)
+ return;
+ if (gesture is PinchGestureRecognizer && _gestureRecognizers.GetGesturesFor<PinchGestureRecognizer>().Count() > 1)
+ throw new InvalidOperationException($"Only one {nameof(PinchGestureRecognizer)} per view is allowed");
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ViewExtensions.cs b/Xamarin.Forms.Core/ViewExtensions.cs
new file mode 100644
index 00000000..14a70868
--- /dev/null
+++ b/Xamarin.Forms.Core/ViewExtensions.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+ public static class ViewExtensions
+ {
+ public static void CancelAnimations(VisualElement view)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ view.AbortAnimation("LayoutTo");
+ view.AbortAnimation("TranslateTo");
+ view.AbortAnimation("RotateTo");
+ view.AbortAnimation("RotateYTo");
+ view.AbortAnimation("RotateXTo");
+ view.AbortAnimation("ScaleTo");
+ view.AbortAnimation("FadeTo");
+ view.AbortAnimation("SizeTo");
+ }
+
+ public static Task<bool> FadeTo(this VisualElement view, double opacity, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ if (easing == null)
+ easing = Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> fade = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.Opacity = f;
+ };
+
+ new Animation(fade, view.Opacity, opacity, easing).Commit(view, "FadeTo", 16, length, finished: (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+
+ public static Task<bool> LayoutTo(this VisualElement view, Rectangle bounds, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ if (easing == null)
+ easing = Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ Rectangle start = view.Bounds;
+ Func<double, Rectangle> computeBounds = progress =>
+ {
+ double x = start.X + (bounds.X - start.X) * progress;
+ double y = start.Y + (bounds.Y - start.Y) * progress;
+ double w = start.Width + (bounds.Width - start.Width) * progress;
+ double h = start.Height + (bounds.Height - start.Height) * progress;
+
+ return new Rectangle(x, y, w, h);
+ };
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> layout = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.Layout(computeBounds(f));
+ };
+ new Animation(layout, 0, 1, easing).Commit(view, "LayoutTo", 16, length, finished: (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+
+ public static Task<bool> RelRotateTo(this VisualElement view, double drotation, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ return view.RotateTo(view.Rotation + drotation, length, easing);
+ }
+
+ public static Task<bool> RelScaleTo(this VisualElement view, double dscale, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ return view.ScaleTo(view.Scale + dscale, length, easing);
+ }
+
+ public static Task<bool> RotateTo(this VisualElement view, double rotation, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ if (easing == null)
+ easing = Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> rotate = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.Rotation = f;
+ };
+
+ new Animation(rotate, view.Rotation, rotation, easing).Commit(view, "RotateTo", 16, length, finished: (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+
+ public static Task<bool> RotateXTo(this VisualElement view, double rotation, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ if (easing == null)
+ easing = Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> rotatex = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.RotationX = f;
+ };
+
+ new Animation(rotatex, view.RotationX, rotation, easing).Commit(view, "RotateXTo", 16, length, finished: (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+
+ public static Task<bool> RotateYTo(this VisualElement view, double rotation, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ if (easing == null)
+ easing = Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> rotatey = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.RotationY = f;
+ };
+
+ new Animation(rotatey, view.RotationY, rotation, easing).Commit(view, "RotateYTo", 16, length, finished: (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+
+ public static Task<bool> ScaleTo(this VisualElement view, double scale, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ if (easing == null)
+ easing = Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> _scale = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.Scale = f;
+ };
+
+ new Animation(_scale, view.Scale, scale, easing).Commit(view, "ScaleTo", 16, length, finished: (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+
+ public static Task<bool> TranslateTo(this VisualElement view, double x, double y, uint length = 250, Easing easing = null)
+ {
+ if (view == null)
+ throw new ArgumentNullException("view");
+ easing = easing ?? Easing.Linear;
+
+ var tcs = new TaskCompletionSource<bool>();
+ var weakView = new WeakReference<VisualElement>(view);
+ Action<double> translateX = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.TranslationX = f;
+ };
+ Action<double> translateY = f =>
+ {
+ VisualElement v;
+ if (weakView.TryGetTarget(out v))
+ v.TranslationY = f;
+ };
+ new Animation { { 0, 1, new Animation(translateX, view.TranslationX, x) }, { 0, 1, new Animation(translateY, view.TranslationY, y) } }.Commit(view, "TranslateTo", 16, length, easing,
+ (f, a) => tcs.SetResult(a));
+
+ return tcs.Task;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/ViewState.cs b/Xamarin.Forms.Core/ViewState.cs
new file mode 100644
index 00000000..e7db908a
--- /dev/null
+++ b/Xamarin.Forms.Core/ViewState.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ [Flags]
+ public enum ViewState
+ {
+ Default = 0,
+ Prelight = 1,
+ Pressed = 1 << 1
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/VisualElement.cs b/Xamarin.Forms.Core/VisualElement.cs
new file mode 100644
index 00000000..16efe730
--- /dev/null
+++ b/Xamarin.Forms.Core/VisualElement.cs
@@ -0,0 +1,764 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms
+{
+ public partial class VisualElement : Element, IAnimatable, IVisualElementController, IResourcesProvider
+ {
+ internal static readonly BindablePropertyKey NavigationPropertyKey = BindableProperty.CreateReadOnly("Navigation", typeof(INavigation), typeof(VisualElement), default(INavigation));
+
+ public static readonly BindableProperty NavigationProperty = NavigationPropertyKey.BindableProperty;
+
+ public static readonly BindableProperty InputTransparentProperty = BindableProperty.Create("InputTransparent", typeof(bool), typeof(VisualElement), default(bool));
+
+ public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create("IsEnabled", typeof(bool), typeof(VisualElement), true);
+
+ static readonly BindablePropertyKey XPropertyKey = BindableProperty.CreateReadOnly("X", typeof(double), typeof(VisualElement), default(double));
+
+ public static readonly BindableProperty XProperty = XPropertyKey.BindableProperty;
+
+ static readonly BindablePropertyKey YPropertyKey = BindableProperty.CreateReadOnly("Y", typeof(double), typeof(VisualElement), default(double));
+
+ public static readonly BindableProperty YProperty = YPropertyKey.BindableProperty;
+
+ public static readonly BindableProperty AnchorXProperty = BindableProperty.Create("AnchorX", typeof(double), typeof(VisualElement), .5d);
+
+ public static readonly BindableProperty AnchorYProperty = BindableProperty.Create("AnchorY", typeof(double), typeof(VisualElement), .5d);
+
+ public static readonly BindableProperty TranslationXProperty = BindableProperty.Create("TranslationX", typeof(double), typeof(VisualElement), 0d);
+
+ public static readonly BindableProperty TranslationYProperty = BindableProperty.Create("TranslationY", typeof(double), typeof(VisualElement), 0d);
+
+ static readonly BindablePropertyKey WidthPropertyKey = BindableProperty.CreateReadOnly("Width", typeof(double), typeof(VisualElement), -1d,
+ coerceValue: (bindable, value) => double.IsNaN((double)value) ? 0d : value);
+
+ public static readonly BindableProperty WidthProperty = WidthPropertyKey.BindableProperty;
+
+ static readonly BindablePropertyKey HeightPropertyKey = BindableProperty.CreateReadOnly("Height", typeof(double), typeof(VisualElement), -1d,
+ coerceValue: (bindable, value) => double.IsNaN((double)value) ? 0d : value);
+
+ public static readonly BindableProperty HeightProperty = HeightPropertyKey.BindableProperty;
+
+ public static readonly BindableProperty RotationProperty = BindableProperty.Create("Rotation", typeof(double), typeof(VisualElement), default(double));
+
+ public static readonly BindableProperty RotationXProperty = BindableProperty.Create("RotationX", typeof(double), typeof(VisualElement), default(double));
+
+ public static readonly BindableProperty RotationYProperty = BindableProperty.Create("RotationY", typeof(double), typeof(VisualElement), default(double));
+
+ public static readonly BindableProperty ScaleProperty = BindableProperty.Create("Scale", typeof(double), typeof(VisualElement), 1d);
+
+ public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create("IsVisible", typeof(bool), typeof(VisualElement), true,
+ propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue));
+
+ public static readonly BindableProperty OpacityProperty = BindableProperty.Create("Opacity", typeof(double), typeof(VisualElement), 1d, coerceValue: (bindable, value) => ((double)value).Clamp(0, 1));
+
+ public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create("BackgroundColor", typeof(Color), typeof(VisualElement), Color.Default);
+
+ internal static readonly BindablePropertyKey BehaviorsPropertyKey = BindableProperty.CreateReadOnly("Behaviors", typeof(IList<Behavior>), typeof(VisualElement), default(IList<Behavior>),
+ defaultValueCreator: bindable =>
+ {
+ var collection = new AttachedCollection<Behavior>();
+ collection.AttachTo(bindable);
+ return collection;
+ });
+
+ public static readonly BindableProperty BehaviorsProperty = BehaviorsPropertyKey.BindableProperty;
+
+ internal static readonly BindablePropertyKey TriggersPropertyKey = BindableProperty.CreateReadOnly("Triggers", typeof(IList<TriggerBase>), typeof(VisualElement), default(IList<TriggerBase>),
+ defaultValueCreator: bindable =>
+ {
+ var collection = new AttachedCollection<TriggerBase>();
+ collection.AttachTo(bindable);
+ return collection;
+ });
+
+ public static readonly BindableProperty TriggersProperty = TriggersPropertyKey.BindableProperty;
+
+ public static readonly BindableProperty StyleProperty = BindableProperty.Create("Style", typeof(Style), typeof(VisualElement), default(Style),
+ propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable)._mergedStyle.Style = (Style)newvalue);
+
+ public static readonly BindableProperty WidthRequestProperty = BindableProperty.Create("WidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
+
+ public static readonly BindableProperty HeightRequestProperty = BindableProperty.Create("HeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
+
+ public static readonly BindableProperty MinimumWidthRequestProperty = BindableProperty.Create("MinimumWidthRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
+
+ public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create("MinimumHeightRequest", typeof(double), typeof(VisualElement), -1d, propertyChanged: OnRequestChanged);
+
+ internal static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly("IsFocused", typeof(bool), typeof(VisualElement), default(bool),
+ propertyChanged: OnIsFocusedPropertyChanged);
+
+ public static readonly BindableProperty IsFocusedProperty = IsFocusedPropertyKey.BindableProperty;
+
+ readonly Dictionary<Size, SizeRequest> _measureCache = new Dictionary<Size, SizeRequest>();
+
+ readonly MergedStyle _mergedStyle;
+
+ int _batched;
+ LayoutConstraint _computedConstraint;
+
+ bool _isInNativeLayout;
+
+ bool _isNativeStateConsistent = true;
+
+ bool _isPlatformEnabled;
+
+ double _mockHeight = -1;
+
+ double _mockWidth = -1;
+
+ double _mockX = -1;
+
+ double _mockY = -1;
+
+ ResourceDictionary _resources;
+ LayoutConstraint _selfConstraint;
+
+ internal VisualElement()
+ {
+ Navigation = new NavigationProxy();
+ _mergedStyle = new MergedStyle(GetType(), this);
+ }
+
+ public double AnchorX
+ {
+ get { return (double)GetValue(AnchorXProperty); }
+ set { SetValue(AnchorXProperty, value); }
+ }
+
+ public double AnchorY
+ {
+ get { return (double)GetValue(AnchorYProperty); }
+ set { SetValue(AnchorYProperty, value); }
+ }
+
+ public Color BackgroundColor
+ {
+ get { return (Color)GetValue(BackgroundColorProperty); }
+ set { SetValue(BackgroundColorProperty, value); }
+ }
+
+ public IList<Behavior> Behaviors
+ {
+ get { return (IList<Behavior>)GetValue(BehaviorsProperty); }
+ }
+
+ public Rectangle Bounds
+ {
+ get { return new Rectangle(X, Y, Width, Height); }
+ private set
+ {
+ if (value.X == X && value.Y == Y && value.Height == Height && value.Width == Width)
+ return;
+ BatchBegin();
+ X = value.X;
+ Y = value.Y;
+ SetSize(value.Width, value.Height);
+ BatchCommit();
+ }
+ }
+
+ public double Height
+ {
+ get { return _mockHeight == -1 ? (double)GetValue(HeightProperty) : _mockHeight; }
+ private set { SetValue(HeightPropertyKey, value); }
+ }
+
+ public double HeightRequest
+ {
+ get { return (double)GetValue(HeightRequestProperty); }
+ set { SetValue(HeightRequestProperty, value); }
+ }
+
+ public bool InputTransparent
+ {
+ get { return (bool)GetValue(InputTransparentProperty); }
+ set { SetValue(InputTransparentProperty, value); }
+ }
+
+ public bool IsEnabled
+ {
+ get { return (bool)GetValue(IsEnabledProperty); }
+ set { SetValue(IsEnabledProperty, value); }
+ }
+
+ public bool IsFocused
+ {
+ get { return (bool)GetValue(IsFocusedProperty); }
+ }
+
+ public bool IsVisible
+ {
+ get { return (bool)GetValue(IsVisibleProperty); }
+ set { SetValue(IsVisibleProperty, value); }
+ }
+
+ public double MinimumHeightRequest
+ {
+ get { return (double)GetValue(MinimumHeightRequestProperty); }
+ set { SetValue(MinimumHeightRequestProperty, value); }
+ }
+
+ public double MinimumWidthRequest
+ {
+ get { return (double)GetValue(MinimumWidthRequestProperty); }
+ set { SetValue(MinimumWidthRequestProperty, value); }
+ }
+
+ public INavigation Navigation
+ {
+ get { return (INavigation)GetValue(NavigationProperty); }
+ internal set { SetValue(NavigationPropertyKey, value); }
+ }
+
+ public double Opacity
+ {
+ get { return (double)GetValue(OpacityProperty); }
+ set { SetValue(OpacityProperty, value); }
+ }
+
+ public double Rotation
+ {
+ get { return (double)GetValue(RotationProperty); }
+ set { SetValue(RotationProperty, value); }
+ }
+
+ public double RotationX
+ {
+ get { return (double)GetValue(RotationXProperty); }
+ set { SetValue(RotationXProperty, value); }
+ }
+
+ public double RotationY
+ {
+ get { return (double)GetValue(RotationYProperty); }
+ set { SetValue(RotationYProperty, value); }
+ }
+
+ public double Scale
+ {
+ get { return (double)GetValue(ScaleProperty); }
+ set { SetValue(ScaleProperty, value); }
+ }
+
+ public Style Style
+ {
+ get { return (Style)GetValue(StyleProperty); }
+ set { SetValue(StyleProperty, value); }
+ }
+
+ public string StyleClass
+ {
+ get { return _mergedStyle.StyleClass; }
+ set { _mergedStyle.StyleClass = value; }
+ }
+
+ public double TranslationX
+ {
+ get { return (double)GetValue(TranslationXProperty); }
+ set { SetValue(TranslationXProperty, value); }
+ }
+
+ public double TranslationY
+ {
+ get { return (double)GetValue(TranslationYProperty); }
+ set { SetValue(TranslationYProperty, value); }
+ }
+
+ public IList<TriggerBase> Triggers
+ {
+ get { return (IList<TriggerBase>)GetValue(TriggersProperty); }
+ }
+
+ public double Width
+ {
+ get { return _mockWidth == -1 ? (double)GetValue(WidthProperty) : _mockWidth; }
+ private set { SetValue(WidthPropertyKey, value); }
+ }
+
+ public double WidthRequest
+ {
+ get { return (double)GetValue(WidthRequestProperty); }
+ set { SetValue(WidthRequestProperty, value); }
+ }
+
+ public double X
+ {
+ get { return _mockX == -1 ? (double)GetValue(XProperty) : _mockX; }
+ private set { SetValue(XPropertyKey, value); }
+ }
+
+ public double Y
+ {
+ get { return _mockY == -1 ? (double)GetValue(YProperty) : _mockY; }
+ private set { SetValue(YPropertyKey, value); }
+ }
+
+ internal bool Batched
+ {
+ get { return _batched > 0; }
+ }
+
+ internal LayoutConstraint ComputedConstraint
+ {
+ get { return _computedConstraint; }
+ set
+ {
+ if (_computedConstraint == value)
+ return;
+
+ LayoutConstraint oldConstraint = Constraint;
+ _computedConstraint = value;
+ LayoutConstraint newConstraint = Constraint;
+ if (oldConstraint != newConstraint)
+ OnConstraintChanged(oldConstraint, newConstraint);
+ }
+ }
+
+ internal LayoutConstraint Constraint
+ {
+ get { return ComputedConstraint | SelfConstraint; }
+ }
+
+ internal bool DisableLayout { get; set; }
+
+ internal bool IsInNativeLayout
+ {
+ get
+ {
+ if (_isInNativeLayout)
+ return true;
+
+ Element parent = RealParent;
+ while (parent != null)
+ {
+ var visualElement = parent as VisualElement;
+ if (visualElement != null && visualElement.IsInNativeLayout)
+ return true;
+ parent = parent.RealParent;
+ }
+
+ return false;
+ }
+ set { _isInNativeLayout = value; }
+ }
+
+ internal bool IsNativeStateConsistent
+ {
+ get { return _isNativeStateConsistent; }
+ set
+ {
+ if (_isNativeStateConsistent == value)
+ return;
+ _isNativeStateConsistent = value;
+ if (value && IsPlatformEnabled)
+ InvalidateMeasure(InvalidationTrigger.RendererReady);
+ }
+ }
+
+ internal bool IsPlatformEnabled
+ {
+ get { return _isPlatformEnabled; }
+ set
+ {
+ if (value == _isPlatformEnabled)
+ return;
+
+ _isPlatformEnabled = value;
+ if (value && IsNativeStateConsistent)
+ InvalidateMeasure(InvalidationTrigger.RendererReady);
+
+ OnIsPlatformEnabledChanged();
+ }
+ }
+
+ internal NavigationProxy NavigationProxy
+ {
+ get { return Navigation as NavigationProxy; }
+ }
+
+ internal LayoutConstraint SelfConstraint
+ {
+ get { return _selfConstraint; }
+ set
+ {
+ if (_selfConstraint == value)
+ return;
+
+ LayoutConstraint oldConstraint = Constraint;
+ _selfConstraint = value;
+ LayoutConstraint newConstraint = Constraint;
+ if (oldConstraint != newConstraint)
+ {
+ OnConstraintChanged(oldConstraint, newConstraint);
+ }
+ }
+ }
+
+ public void BatchBegin()
+ {
+ _batched++;
+ }
+
+ public void BatchCommit()
+ {
+ _batched = Math.Max(0, _batched - 1);
+ if (!Batched && BatchCommitted != null)
+ BatchCommitted(this, new EventArg<VisualElement>(this));
+ }
+
+ public ResourceDictionary Resources
+ {
+ get { return _resources; }
+ set
+ {
+ if (_resources == value)
+ return;
+ OnPropertyChanging();
+ if (_resources != null)
+ ((IResourceDictionary)_resources).ValuesChanged -= OnResourcesChanged;
+ _resources = value;
+ OnResourcesChanged(value);
+ if (_resources != null)
+ ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged;
+ OnPropertyChanged();
+ }
+ }
+
+ void IVisualElementController.NativeSizeChanged()
+ {
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ public event EventHandler ChildrenReordered;
+
+ public bool Focus()
+ {
+ if (IsFocused)
+ return true;
+
+ if (FocusChangeRequested == null)
+ return false;
+
+ var arg = new FocusRequestArgs { Focus = true };
+ FocusChangeRequested(this, arg);
+ return arg.Result;
+ }
+
+ public event EventHandler<FocusEventArgs> Focused;
+
+ [Obsolete("Use Measure")]
+ public virtual SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ SizeRequest cachedResult;
+ var constraintSize = new Size(widthConstraint, heightConstraint);
+ if (_measureCache.TryGetValue(constraintSize, out cachedResult))
+ {
+ return cachedResult;
+ }
+
+ double widthRequest = WidthRequest;
+ double heightRequest = HeightRequest;
+ if (widthRequest >= 0)
+ widthConstraint = Math.Min(widthConstraint, widthRequest);
+ if (heightRequest >= 0)
+ heightConstraint = Math.Min(heightConstraint, heightRequest);
+
+ SizeRequest result = OnMeasure(widthConstraint, heightConstraint);
+ bool hasMinimum = result.Minimum != result.Request;
+ Size request = result.Request;
+ Size minimum = result.Minimum;
+
+ if (heightRequest != -1)
+ {
+ request.Height = heightRequest;
+ if (!hasMinimum)
+ minimum.Height = heightRequest;
+ }
+
+ if (widthRequest != -1)
+ {
+ request.Width = widthRequest;
+ if (!hasMinimum)
+ minimum.Width = widthRequest;
+ }
+
+ double minimumHeightRequest = MinimumHeightRequest;
+ double minimumWidthRequest = MinimumWidthRequest;
+
+ if (minimumHeightRequest != -1)
+ minimum.Height = minimumHeightRequest;
+ if (minimumWidthRequest != -1)
+ minimum.Width = minimumWidthRequest;
+
+ minimum.Height = Math.Min(request.Height, minimum.Height);
+ minimum.Width = Math.Min(request.Width, minimum.Width);
+
+ var r = new SizeRequest(request, minimum);
+
+ if (r.Request.Width > 0 && r.Request.Height > 0)
+ {
+ _measureCache[constraintSize] = r;
+ }
+
+ return r;
+ }
+
+ public void Layout(Rectangle bounds)
+ {
+ Bounds = bounds;
+ }
+
+ public SizeRequest Measure(double widthConstraint, double heightConstraint, MeasureFlags flags = MeasureFlags.None)
+ {
+ SizeRequest result = GetSizeRequest(widthConstraint, heightConstraint);
+
+ if ((flags & MeasureFlags.IncludeMargins) != 0)
+ {
+ Thickness margin = default(Thickness);
+ var view = this as View;
+ if (view != null)
+ margin = view.Margin;
+
+ if (!margin.IsDefault)
+ {
+ result.Minimum = new Size(result.Minimum.Width + margin.HorizontalThickness, result.Minimum.Height + margin.VerticalThickness);
+ result.Request = new Size(result.Request.Width + margin.HorizontalThickness, result.Request.Height + margin.VerticalThickness);
+ }
+ }
+
+ return result;
+ }
+
+ public event EventHandler MeasureInvalidated;
+
+ public event EventHandler SizeChanged;
+
+ public void Unfocus()
+ {
+ if (!IsFocused)
+ return;
+
+ EventHandler<FocusRequestArgs> unfocus = FocusChangeRequested;
+ if (unfocus != null)
+ {
+ unfocus(this, new FocusRequestArgs());
+ }
+ }
+
+ public event EventHandler<FocusEventArgs> Unfocused;
+
+ protected virtual void InvalidateMeasure()
+ {
+ InvalidateMeasure(InvalidationTrigger.MeasureChanged);
+ }
+
+ protected override void OnChildAdded(Element child)
+ {
+ base.OnChildAdded(child);
+ var view = child as View;
+ if (view != null)
+ ComputeConstraintForView(view);
+ }
+
+ protected override void OnChildRemoved(Element child)
+ {
+ base.OnChildRemoved(child);
+ var view = child as View;
+ if (view != null)
+ view.ComputedConstraint = LayoutConstraint.None;
+ }
+
+ protected void OnChildrenReordered()
+ {
+ if (ChildrenReordered != null)
+ ChildrenReordered(this, EventArgs.Empty);
+ }
+
+ protected virtual SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+ {
+ return OnSizeRequest(widthConstraint, heightConstraint);
+ }
+
+ protected override void OnParentSet()
+ {
+ base.OnParentSet();
+
+ if (ParentView != null)
+ {
+ NavigationProxy.Inner = ParentView.NavigationProxy;
+ }
+ else
+ {
+ NavigationProxy.Inner = null;
+ }
+ }
+
+ protected virtual void OnSizeAllocated(double width, double height)
+ {
+ }
+
+ [Obsolete("Use OnMeasure")]
+ protected virtual SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
+ {
+ if (Platform == null || !IsPlatformEnabled)
+ {
+ return new SizeRequest(new Size(-1, -1));
+ }
+ return Platform.GetNativeSize(this, widthConstraint, heightConstraint);
+ }
+
+ protected void SizeAllocated(double width, double height)
+ {
+ OnSizeAllocated(width, height);
+ }
+
+ internal event EventHandler<EventArg<VisualElement>> BatchCommitted;
+
+ internal void ComputeConstrainsForChildren()
+ {
+ for (var i = 0; i < LogicalChildren.Count; i++)
+ {
+ var child = LogicalChildren[i] as View;
+ if (child != null)
+ ComputeConstraintForView(child);
+ }
+ }
+
+ internal virtual void ComputeConstraintForView(View view)
+ {
+ view.ComputedConstraint = LayoutConstraint.None;
+ }
+
+ internal event EventHandler<FocusRequestArgs> FocusChangeRequested;
+
+ internal virtual void InvalidateMeasure(InvalidationTrigger trigger)
+ {
+ _measureCache.Clear();
+ MeasureInvalidated?.Invoke(this, new InvalidationEventArgs(trigger));
+ }
+
+ internal void MockBounds(Rectangle bounds)
+ {
+ _mockX = bounds.X;
+ _mockY = bounds.Y;
+ _mockWidth = bounds.Width;
+ _mockHeight = bounds.Height;
+ }
+
+ internal virtual void OnConstraintChanged(LayoutConstraint oldConstraint, LayoutConstraint newConstraint)
+ {
+ ComputeConstrainsForChildren();
+ }
+
+ internal virtual void OnIsPlatformEnabledChanged()
+ {
+ }
+
+ internal virtual void OnIsVisibleChanged(bool oldValue, bool newValue)
+ {
+ InvalidateMeasure(InvalidationTrigger.Undefined);
+ }
+
+ internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
+ {
+ if (values == null)
+ return;
+
+ if (Resources == null || Resources.Count == 0)
+ {
+ base.OnParentResourcesChanged(values);
+ return;
+ }
+
+ var innerKeys = new HashSet<string>();
+ var changedResources = new List<KeyValuePair<string, object>>();
+ foreach (KeyValuePair<string, object> c in Resources)
+ innerKeys.Add(c.Key);
+ foreach (KeyValuePair<string, object> value in values)
+ {
+ if (innerKeys.Add(value.Key))
+ changedResources.Add(value);
+ else if (value.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal))
+ {
+ var mergedClassStyles = new List<Style>(Resources[value.Key] as List<Style>);
+ mergedClassStyles.AddRange(value.Value as List<Style>);
+ changedResources.Add(new KeyValuePair<string, object>(value.Key, mergedClassStyles));
+ }
+ }
+ if (changedResources.Count != 0)
+ OnResourcesChanged(changedResources);
+ }
+
+ internal void UnmockBounds()
+ {
+ _mockX = _mockY = _mockWidth = _mockHeight = -1;
+ }
+
+ void OnFocused()
+ {
+ EventHandler<FocusEventArgs> focus = Focused;
+ if (focus != null)
+ focus(this, new FocusEventArgs(this, true));
+ }
+
+ static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ var element = bindable as VisualElement;
+
+ var isFocused = (bool)newvalue;
+ if (isFocused)
+ {
+ element.OnFocused();
+ }
+ else
+ {
+ element.OnUnfocus();
+ }
+ }
+
+ static void OnRequestChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ var constraint = LayoutConstraint.None;
+ var element = (VisualElement)bindable;
+ if (element.WidthRequest >= 0 && element.MinimumWidthRequest >= 0)
+ {
+ constraint |= LayoutConstraint.HorizontallyFixed;
+ }
+ if (element.HeightRequest >= 0 && element.MinimumHeightRequest >= 0)
+ {
+ constraint |= LayoutConstraint.VerticallyFixed;
+ }
+
+ element.SelfConstraint = constraint;
+ ((VisualElement)bindable).InvalidateMeasure(InvalidationTrigger.SizeRequestChanged);
+ }
+
+ void OnUnfocus()
+ {
+ EventHandler<FocusEventArgs> unFocus = Unfocused;
+ if (unFocus != null)
+ unFocus(this, new FocusEventArgs(this, false));
+ }
+
+ void SetSize(double width, double height)
+ {
+ if (Width == width && Height == height)
+ return;
+
+ Width = width;
+ Height = height;
+
+ SizeAllocated(width, height);
+ if (SizeChanged != null)
+ SizeChanged(this, EventArgs.Empty);
+ }
+
+ internal class FocusRequestArgs : EventArgs
+ {
+ public bool Focus { get; set; }
+
+ public bool Result { get; set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WeakReferenceExtensions.cs b/Xamarin.Forms.Core/WeakReferenceExtensions.cs
new file mode 100644
index 00000000..e4e883a4
--- /dev/null
+++ b/Xamarin.Forms.Core/WeakReferenceExtensions.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ internal static class WeakReferenceExtensions
+ {
+ internal static bool TryGetTarget<T>(this WeakReference self, out T target) where T : class
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+
+ target = (T)self.Target;
+ return target != null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebNavigatedEventArgs.cs b/Xamarin.Forms.Core/WebNavigatedEventArgs.cs
new file mode 100644
index 00000000..29bc3d11
--- /dev/null
+++ b/Xamarin.Forms.Core/WebNavigatedEventArgs.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.Forms
+{
+ public class WebNavigatedEventArgs : WebNavigationEventArgs
+ {
+ public WebNavigatedEventArgs(WebNavigationEvent navigationEvent, WebViewSource source, string url, WebNavigationResult result) : base(navigationEvent, source, url)
+ {
+ Result = result;
+ }
+
+ public WebNavigationResult Result { get; private set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebNavigatingEventArgs.cs b/Xamarin.Forms.Core/WebNavigatingEventArgs.cs
new file mode 100644
index 00000000..49aa2dc1
--- /dev/null
+++ b/Xamarin.Forms.Core/WebNavigatingEventArgs.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.Forms
+{
+ public class WebNavigatingEventArgs : WebNavigationEventArgs
+ {
+ public WebNavigatingEventArgs(WebNavigationEvent navigationEvent, WebViewSource source, string url) : base(navigationEvent, source, url)
+ {
+ }
+
+ public bool Cancel { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebNavigationEvent.cs b/Xamarin.Forms.Core/WebNavigationEvent.cs
new file mode 100644
index 00000000..b47bd0ea
--- /dev/null
+++ b/Xamarin.Forms.Core/WebNavigationEvent.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum WebNavigationEvent
+ {
+ Back = 1,
+ Forward = 2,
+ NewPage = 3,
+ Refresh = 4
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebNavigationEventArgs.cs b/Xamarin.Forms.Core/WebNavigationEventArgs.cs
new file mode 100644
index 00000000..e6124c31
--- /dev/null
+++ b/Xamarin.Forms.Core/WebNavigationEventArgs.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class WebNavigationEventArgs : EventArgs
+ {
+ protected WebNavigationEventArgs(WebNavigationEvent navigationEvent, WebViewSource source, string url)
+ {
+ NavigationEvent = navigationEvent;
+ Source = source;
+ Url = url;
+ }
+
+ public WebNavigationEvent NavigationEvent { get; internal set; }
+
+ public WebViewSource Source { get; internal set; }
+
+ public string Url { get; internal set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebNavigationResult.cs b/Xamarin.Forms.Core/WebNavigationResult.cs
new file mode 100644
index 00000000..eb0f6e73
--- /dev/null
+++ b/Xamarin.Forms.Core/WebNavigationResult.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms
+{
+ public enum WebNavigationResult
+ {
+ Success = 1,
+ Cancel = 2,
+ Timeout = 3,
+ Failure = 4
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebView.cs b/Xamarin.Forms.Core/WebView.cs
new file mode 100644
index 00000000..06a5f72c
--- /dev/null
+++ b/Xamarin.Forms.Core/WebView.cs
@@ -0,0 +1,126 @@
+using System;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_WebViewRenderer))]
+ public class WebView : View
+ {
+ public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(WebViewSource), typeof(WebView), default(WebViewSource),
+ propertyChanging: (bindable, oldvalue, newvalue) =>
+ {
+ var source = oldvalue as WebViewSource;
+ if (source != null)
+ source.SourceChanged -= ((WebView)bindable).OnSourceChanged;
+ }, propertyChanged: (bindable, oldvalue, newvalue) =>
+ {
+ var source = newvalue as WebViewSource;
+ var webview = (WebView)bindable;
+ if (source != null)
+ {
+ source.SourceChanged += webview.OnSourceChanged;
+ SetInheritedBindingContext(source, webview.BindingContext);
+ }
+ });
+
+ static readonly BindablePropertyKey CanGoBackPropertyKey = BindableProperty.CreateReadOnly("CanGoBack", typeof(bool), typeof(WebView), false);
+
+ public static readonly BindableProperty CanGoBackProperty = CanGoBackPropertyKey.BindableProperty;
+
+ static readonly BindablePropertyKey CanGoForwardPropertyKey = BindableProperty.CreateReadOnly("CanGoForward", typeof(bool), typeof(WebView), false);
+
+ public static readonly BindableProperty CanGoForwardProperty = CanGoForwardPropertyKey.BindableProperty;
+
+ public bool CanGoBack
+ {
+ get { return (bool)GetValue(CanGoBackProperty); }
+ internal set { SetValue(CanGoBackPropertyKey, value); }
+ }
+
+ public bool CanGoForward
+ {
+ get { return (bool)GetValue(CanGoForwardProperty); }
+ internal set { SetValue(CanGoForwardPropertyKey, value); }
+ }
+
+ [TypeConverter(typeof(WebViewSourceTypeConverter))]
+ public WebViewSource Source
+ {
+ get { return (WebViewSource)GetValue(SourceProperty); }
+ set { SetValue(SourceProperty, value); }
+ }
+
+ public void Eval(string script)
+ {
+ EventHandler<EventArg<string>> handler = EvalRequested;
+ if (handler != null)
+ handler(this, new EventArg<string>(script));
+ }
+
+ public void GoBack()
+ {
+ EventHandler handler = GoBackRequested;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ public void GoForward()
+ {
+ EventHandler handler = GoForwardRequested;
+ if (handler != null)
+ handler(this, EventArgs.Empty);
+ }
+
+ public event EventHandler<WebNavigatedEventArgs> Navigated;
+
+ public event EventHandler<WebNavigatingEventArgs> Navigating;
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ WebViewSource source = Source;
+ if (source != null)
+ {
+ SetInheritedBindingContext(source, BindingContext);
+ }
+ }
+
+ protected override void OnPropertyChanged(string propertyName)
+ {
+ if (propertyName == "BindingContext")
+ {
+ WebViewSource source = Source;
+ if (source != null)
+ SetInheritedBindingContext(source, BindingContext);
+ }
+
+ base.OnPropertyChanged(propertyName);
+ }
+
+ protected void OnSourceChanged(object sender, EventArgs e)
+ {
+ OnPropertyChanged(SourceProperty.PropertyName);
+ }
+
+ internal event EventHandler<EventArg<string>> EvalRequested;
+
+ internal event EventHandler GoBackRequested;
+
+ internal event EventHandler GoForwardRequested;
+
+ internal void SendNavigated(WebNavigatedEventArgs args)
+ {
+ EventHandler<WebNavigatedEventArgs> handler = Navigated;
+ if (handler != null)
+ handler(this, args);
+ }
+
+ internal void SendNavigating(WebNavigatingEventArgs args)
+ {
+ EventHandler<WebNavigatingEventArgs> handler = Navigating;
+ if (handler != null)
+ handler(this, args);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebViewSource.cs b/Xamarin.Forms.Core/WebViewSource.cs
new file mode 100644
index 00000000..6dc2635f
--- /dev/null
+++ b/Xamarin.Forms.Core/WebViewSource.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public abstract class WebViewSource : BindableObject
+ {
+ public static implicit operator WebViewSource(Uri url)
+ {
+ return new UrlWebViewSource { Url = url?.AbsoluteUri };
+ }
+
+ public static implicit operator WebViewSource(string url)
+ {
+ return new UrlWebViewSource { Url = url };
+ }
+
+ protected void OnSourceChanged()
+ {
+ EventHandler eh = SourceChanged;
+ if (eh != null)
+ eh(this, EventArgs.Empty);
+ }
+
+ internal abstract void Load(IWebViewRenderer renderer);
+
+ internal event EventHandler SourceChanged;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebViewSourceTypeConverter.cs b/Xamarin.Forms.Core/WebViewSourceTypeConverter.cs
new file mode 100644
index 00000000..5955eb14
--- /dev/null
+++ b/Xamarin.Forms.Core/WebViewSourceTypeConverter.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms
+{
+ public class WebViewSourceTypeConverter : TypeConverter
+ {
+ public override object ConvertFromInvariantString(string value)
+ {
+ if (value != null)
+ return new UrlWebViewSource { Url = value };
+
+ throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Color)));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
new file mode 100644
index 00000000..ce2a3b57
--- /dev/null
+++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
@@ -0,0 +1,408 @@
+<?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>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</ProjectGuid>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <OutputType>Library</OutputType>
+ <RootNamespace>Xamarin.Forms</RootNamespace>
+ <AssemblyName>Xamarin.Forms.Core</AssemblyName>
+ <TargetFrameworkProfile>Profile259</TargetFrameworkProfile>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
+ </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>
+ <ConsolePause>false</ConsolePause>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <NoWarn>0618</NoWarn>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>full</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|AnyCPU'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\Turkey\</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <NoWarn>0618</NoWarn>
+ <DebugType>full</DebugType>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="AbsoluteLayoutFlags.cs" />
+ <Compile Include="ActionSheetArguments.cs" />
+ <Compile Include="AlertArguments.cs" />
+ <Compile Include="AnimatableKey.cs" />
+ <Compile Include="Application.cs" />
+ <Compile Include="Aspect.cs" />
+ <Compile Include="BackButtonPressedEventArgs.cs" />
+ <Compile Include="BaseMenuItem.cs" />
+ <Compile Include="BindablePropertyConverter.cs" />
+ <Compile Include="BindingMode.cs" />
+ <Compile Include="BindingTypeConverter.cs" />
+ <Compile Include="BoundsConstraint.cs" />
+ <Compile Include="BoundsTypeConverter.cs" />
+ <Compile Include="CarouselView.cs" />
+ <Compile Include="CastingEnumerator.cs" />
+ <Compile Include="Cells\EntryCell.cs" />
+ <Compile Include="Cells\ImageCell.cs" />
+ <Compile Include="Cells\INativeElementView.cs" />
+ <Compile Include="ChatKeyboard.cs" />
+ <Compile Include="ChildCollectionChangedEventArgs.cs" />
+ <Compile Include="CollectionSynchronizationCallback.cs" />
+ <Compile Include="CollectionSynchronizationContext.cs" />
+ <Compile Include="ColorTypeConverter.cs" />
+ <Compile Include="ColumnDefinition.cs" />
+ <Compile Include="ColumnDefinitionCollection.cs" />
+ <Compile Include="Constraint.cs" />
+ <Compile Include="ConstraintExpression.cs" />
+ <Compile Include="ConstraintType.cs" />
+ <Compile Include="ConstraintTypeConverter.cs" />
+ <Compile Include="ContentPresenter.cs" />
+ <Compile Include="ControlTemplate.cs" />
+ <Compile Include="CustomKeyboard.cs" />
+ <Compile Include="DataTemplateExtensions.cs" />
+ <Compile Include="DataTemplateSelector.cs" />
+ <Compile Include="DateChangedEventArgs.cs" />
+ <Compile Include="DelegateLogListener.cs" />
+ <Compile Include="DependencyAttribute.cs" />
+ <Compile Include="DependencyFetchTarget.cs" />
+ <Compile Include="DeviceOrientation.cs" />
+ <Compile Include="DeviceOrientationExtensions.cs" />
+ <Compile Include="Effect.cs" />
+ <Compile Include="ElementEventArgs.cs" />
+ <Compile Include="ElementTemplate.cs" />
+ <Compile Include="EmailKeyboard.cs" />
+ <Compile Include="ExportEffectAttribute.cs" />
+ <Compile Include="ExpressionSearch.cs" />
+ <Compile Include="FileAccess.cs" />
+ <Compile Include="FileImageSourceConverter.cs" />
+ <Compile Include="FileMode.cs" />
+ <Compile Include="FileShare.cs" />
+ <Compile Include="FontAttributes.cs" />
+ <Compile Include="FontSizeConverter.cs" />
+ <Compile Include="FontTypeConverter.cs" />
+ <Compile Include="GestureRecognizer.cs" />
+ <Compile Include="GridLength.cs" />
+ <Compile Include="GridLengthTypeConverter.cs" />
+ <Compile Include="GridUnitType.cs" />
+ <Compile Include="HandlerAttribute.cs" />
+ <Compile Include="HtmlWebViewSource.cs" />
+ <Compile Include="IButtonController.cs" />
+ <Compile Include="ICarouselViewController.cs" />
+ <Compile Include="IControlTemplated.cs" />
+ <Compile Include="IDefinition.cs" />
+ <Compile Include="IEffectControlProvider.cs" />
+ <Compile Include="IElementController.cs" />
+ <Compile Include="IExpressionSearch.cs" />
+ <Compile Include="IExtendedTypeConverter.cs" />
+ <Compile Include="IFontElement.cs" />
+ <Compile Include="IGestureRecognizer.cs" />
+ <Compile Include="IItemsView.cs" />
+ <Compile Include="IItemViewController.cs" />
+ <Compile Include="ILayoutController.cs" />
+ <Compile Include="IListViewController.cs" />
+ <Compile Include="ImageSourceConverter.cs" />
+ <Compile Include="INavigation.cs" />
+ <Compile Include="InvalidationEventArgs.cs" />
+ <Compile Include="InvalidationTrigger.cs" />
+ <Compile Include="InvalidNavigationException.cs" />
+ <Compile Include="IOpenGlViewController.cs" />
+ <Compile Include="IPanGestureController.cs" />
+ <Compile Include="IPinchGestureController.cs" />
+ <Compile Include="IProvideParentValues.cs" />
+ <Compile Include="IProvideValueTarget.cs" />
+ <Compile Include="IRegisterable.cs" />
+ <Compile Include="IResourceDictionary.cs" />
+ <Compile Include="IResourcesProvider.cs" />
+ <Compile Include="IRootObjectProvider.cs" />
+ <Compile Include="IScrollViewController.cs" />
+ <Compile Include="ItemsViewSimple.cs" />
+ <Compile Include="ItemTappedEventArgs.cs" />
+ <Compile Include="ItemVisibilityEventArgs.cs" />
+ <Compile Include="IValueConverterProvider.cs" />
+ <Compile Include="IValueProvider.cs" />
+ <Compile Include="IViewController.cs" />
+ <Compile Include="IVisualElementController.cs" />
+ <Compile Include="IWebViewRenderer.cs" />
+ <Compile Include="IXamlTypeResolver.cs" />
+ <Compile Include="IXmlLineInfoProvider.cs" />
+ <Compile Include="KeyboardFlags.cs" />
+ <Compile Include="KeyboardTypeConverter.cs" />
+ <Compile Include="LayoutAlignment.cs" />
+ <Compile Include="LayoutAlignmentExtensions.cs" />
+ <Compile Include="LayoutConstraint.cs" />
+ <Compile Include="LayoutExpandFlag.cs" />
+ <Compile Include="LayoutOptionsConverter.cs" />
+ <Compile Include="ListViewCachingStrategy.cs" />
+ <Compile Include="LockingSemaphore.cs" />
+ <Compile Include="Log.cs" />
+ <Compile Include="LogListener.cs" />
+ <Compile Include="MasterBehavior.cs" />
+ <Compile Include="MeasureFlags.cs" />
+ <Compile Include="MenuItem.cs" />
+ <Compile Include="ModalEventArgs.cs" />
+ <Compile Include="ModalPoppedEventArgs.cs" />
+ <Compile Include="ModalPoppingEventArgs.cs" />
+ <Compile Include="ModalPushedEventArgs.cs" />
+ <Compile Include="ModalPushingEventArgs.cs" />
+ <Compile Include="NavigationEventArgs.cs" />
+ <Compile Include="NavigationModel.cs" />
+ <Compile Include="NavigationRequestedEventArgs.cs" />
+ <Compile Include="NotifyCollectionChangedEventArgsEx.cs" />
+ <Compile Include="NotifyCollectionChangedEventArgsExtensions.cs" />
+ <Compile Include="NullEffect.cs" />
+ <Compile Include="NumericKeyboard.cs" />
+ <Compile Include="ObservableList.cs" />
+ <Compile Include="OrderedDictionary.cs" />
+ <Compile Include="PanGestureRecognizer.cs" />
+ <Compile Include="PanUpdatedEventArgs.cs" />
+ <Compile Include="Performance.cs" />
+ <Compile Include="PinchGestureUpdatedEventArgs.cs" />
+ <Compile Include="PlatformEffect.cs" />
+ <Compile Include="PointTypeConverter.cs" />
+ <Compile Include="PreserveAttribute.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="BindableObject.cs" />
+ <Compile Include="BindableObjectExtensions.cs" />
+ <Compile Include="BindableProperty.cs" />
+ <Compile Include="Binding.cs" />
+ <Compile Include="BindingBase.cs" />
+ <Compile Include="BindingBaseExtensions.cs" />
+ <Compile Include="BindingExpression.cs" />
+ <Compile Include="Properties\GlobalAssemblyInfo.cs" />
+ <Compile Include="PropertyChangingEventArgs.cs" />
+ <Compile Include="IValueConverter.cs" />
+ <Compile Include="ConcurrentDictionary.cs" />
+ <Compile Include="PropertyChangingEventHandler.cs" />
+ <Compile Include="ReadOnlyCastingList.cs" />
+ <Compile Include="ReadOnlyListAdapter.cs" />
+ <Compile Include="RectangleTypeConverter.cs" />
+ <Compile Include="RenderWithAttribute.cs" />
+ <Compile Include="ResolutionGroupNameAttribute.cs" />
+ <Compile Include="ResourcesChangedEventArgs.cs" />
+ <Compile Include="ResourcesExtensions.cs" />
+ <Compile Include="RoutingEffect.cs" />
+ <Compile Include="RowDefinition.cs" />
+ <Compile Include="RowDefinitionCollection.cs" />
+ <Compile Include="ScrollOrientation.cs" />
+ <Compile Include="ScrollToMode.cs" />
+ <Compile Include="ScrollToPosition.cs" />
+ <Compile Include="SelectedItemChangedEventArgs.cs" />
+ <Compile Include="SelectedPositionChangedEventArgs.cs" />
+ <Compile Include="SeparatorMenuItem.cs" />
+ <Compile Include="SeparatorVisibility.cs" />
+ <Compile Include="SizeRequest.cs" />
+ <Compile Include="Span.cs" />
+ <Compile Include="SplitOrderedList.cs" />
+ <Compile Include="StackOrientation.cs" />
+ <Compile Include="StreamWrapper.cs" />
+ <Compile Include="SynchronizedList.cs" />
+ <Compile Include="TableIntent.cs" />
+ <Compile Include="TableRoot.cs" />
+ <Compile Include="TableSectionBase.cs" />
+ <Compile Include="TapGestureRecognizer.cs" />
+ <Compile Include="TelephoneKeyboard.cs" />
+ <Compile Include="TemplateBinding.cs" />
+ <Compile Include="TemplatedItemsList.cs" />
+ <Compile Include="TemplatedPage.cs" />
+ <Compile Include="TemplatedView.cs" />
+ <Compile Include="TemplateUtilities.cs" />
+ <Compile Include="TextChangedEventArgs.cs" />
+ <Compile Include="TextKeyboard.cs" />
+ <Compile Include="ThicknessTypeConverter.cs" />
+ <Compile Include="Ticker.cs" />
+ <Compile Include="ToggledEventArgs.cs" />
+ <Compile Include="ToolbarItemEventArgs.cs" />
+ <Compile Include="ToolbarItemOrder.cs" />
+ <Compile Include="TrackableCollection.cs" />
+ <Compile Include="TypeConverterAttribute.cs" />
+ <Compile Include="TypeConverter.cs" />
+ <Compile Include="ReflectionExtensions.cs" />
+ <Compile Include="Device.cs" />
+ <Compile Include="TargetPlatform.cs" />
+ <Compile Include="TargetIdiom.cs" />
+ <Compile Include="UnsolvableConstraintsException.cs" />
+ <Compile Include="UrlKeyboard.cs" />
+ <Compile Include="UrlWebViewSource.cs" />
+ <Compile Include="ValueChangedEventArgs.cs" />
+ <Compile Include="Vec2.cs" />
+ <Compile Include="View.cs" />
+ <Compile Include="IElement.cs" />
+ <Compile Include="IAnimatable.cs" />
+ <Compile Include="Easing.cs" />
+ <Compile Include="NavigationProxy.cs" />
+ <Compile Include="NavigationPage.cs" />
+ <Compile Include="Page.cs" />
+ <Compile Include="LayoutOptions.cs" />
+ <Compile Include="CarouselPage.cs" />
+ <Compile Include="Rectangle.cs" />
+ <Compile Include="Color.cs" />
+ <Compile Include="ResourceDictionary.cs" />
+ <Compile Include="EventArg.cs" />
+ <Compile Include="IPlatform.cs" />
+ <Compile Include="EnumerableExtensions.cs" />
+ <Compile Include="Size.cs" />
+ <Compile Include="GestureState.cs" />
+ <Compile Include="Point.cs" />
+ <Compile Include="Thickness.cs" />
+ <Compile Include="ToolbarItem.cs" />
+ <Compile Include="ContentPropertyAttribute.cs" />
+ <Compile Include="MessagingCenter.cs" />
+ <Compile Include="Cells\Cell.cs" />
+ <Compile Include="Cells\SwitchCell.cs" />
+ <Compile Include="Cells\TextCell.cs" />
+ <Compile Include="Cells\ViewCell.cs" />
+ <Compile Include="ListView.cs" />
+ <Compile Include="TableView.cs" />
+ <Compile Include="ItemsView.cs" />
+ <Compile Include="TableModel.cs" />
+ <Compile Include="TableSection.cs" />
+ <Compile Include="DataTemplate.cs" />
+ <Compile Include="ListProxy.cs" />
+ <Compile Include="AbsoluteLayout.cs" />
+ <Compile Include="Registrar.cs" />
+ <Compile Include="ActivityIndicator.cs" />
+ <Compile Include="BoxView.cs" />
+ <Compile Include="Button.cs" />
+ <Compile Include="Command.cs" />
+ <Compile Include="ContentView.cs" />
+ <Compile Include="DatePicker.cs" />
+ <Compile Include="DependencyService.cs" />
+ <Compile Include="Editor.cs" />
+ <Compile Include="Entry.cs" />
+ <Compile Include="Frame.cs" />
+ <Compile Include="Image.cs" />
+ <Compile Include="UriImageSource.cs" />
+ <Compile Include="IMarkupExtension.cs" />
+ <Compile Include="InputView.cs" />
+ <Compile Include="Keyboard.cs" />
+ <Compile Include="Label.cs" />
+ <Compile Include="LineBreakMode.cs" />
+ <Compile Include="MasterDetailPage.cs" />
+ <Compile Include="NavigationMenu.cs" />
+ <Compile Include="OpenGLView.cs" />
+ <Compile Include="ProgressBar.cs" />
+ <Compile Include="RelativeLayout.cs" />
+ <Compile Include="ScrollView.cs" />
+ <Compile Include="SearchBar.cs" />
+ <Compile Include="Slider.cs" />
+ <Compile Include="StackLayout.cs" />
+ <Compile Include="Stepper.cs" />
+ <Compile Include="Switch.cs" />
+ <Compile Include="TabbedPage.cs" />
+ <Compile Include="TemplateExtensions.cs" />
+ <Compile Include="TimePicker.cs" />
+ <Compile Include="Toolbar.cs" />
+ <Compile Include="ToolbarTracker.cs" />
+ <Compile Include="ViewExtensions.cs" />
+ <Compile Include="ViewState.cs" />
+ <Compile Include="WeakReferenceExtensions.cs" />
+ <Compile Include="WebNavigatedEventArgs.cs" />
+ <Compile Include="WebNavigatingEventArgs.cs" />
+ <Compile Include="WebNavigationEvent.cs" />
+ <Compile Include="WebNavigationEventArgs.cs" />
+ <Compile Include="WebNavigationResult.cs" />
+ <Compile Include="WebView.cs" />
+ <Compile Include="Animation.cs" />
+ <Compile Include="AnimationExtensions.cs" />
+ <Compile Include="Tweener.cs" />
+ <Compile Include="IPlatformServices.cs" />
+ <Compile Include="UriTypeConverter.cs" />
+ <Compile Include="ITimer.cs" />
+ <Compile Include="IIsolatedStorageFile.cs" />
+ <Compile Include="Grid.cs" />
+ <Compile Include="GridCalc.cs" />
+ <Compile Include="DefinitionCollection.cs" />
+ <Compile Include="Element.cs" />
+ <Compile Include="ObservableWrapper.cs" />
+ <Compile Include="ElementCollection.cs" />
+ <Compile Include="IViewContainer.cs" />
+ <Compile Include="Layout.cs" />
+ <Compile Include="ContentPage.cs" />
+ <Compile Include="IPageContainer.cs" />
+ <Compile Include="MultiPage.cs" />
+ <Compile Include="ILayout.cs" />
+ <Compile Include="FocusEventArgs.cs" />
+ <Compile Include="VisualElement.cs" />
+ <Compile Include="NameScopeExtensions.cs" />
+ <Compile Include="NamedSize.cs" />
+ <Compile Include="TextAlignment.cs" />
+ <Compile Include="Font.cs" />
+ <Compile Include="Picker.cs" />
+ <Compile Include="NumericExtensions.cs" />
+ <Compile Include="BindablePropertyKey.cs" />
+ <Compile Include="ImageSource.cs" />
+ <Compile Include="FileImageSource.cs" />
+ <Compile Include="StreamImageSource.cs" />
+ <Compile Include="OnPlatform.cs" />
+ <Compile Include="OnIdiom.cs" />
+ <Compile Include="TappedEventArgs.cs" />
+ <Compile Include="FormattedString.cs" />
+ <Compile Include="Interactivity\Behavior.cs" />
+ <Compile Include="Interactivity\IAttachedObject.cs" />
+ <Compile Include="Interactivity\AttachedCollection.cs" />
+ <Compile Include="Interactivity\TriggerAction.cs" />
+ <Compile Include="Interactivity\EventTrigger.cs" />
+ <Compile Include="Interactivity\TriggerBase.cs" />
+ <Compile Include="Interactivity\Trigger.cs" />
+ <Compile Include="Setter.cs" />
+ <Compile Include="Style.cs" />
+ <Compile Include="ParameterAttribute.cs" />
+ <Compile Include="TypeTypeConverter.cs" />
+ <Compile Include="SettersExtensions.cs" />
+ <Compile Include="IDeserializer.cs" />
+ <Compile Include="ISystemResourcesProvider.cs" />
+ <Compile Include="Interactivity\DataTrigger.cs" />
+ <Compile Include="Interactivity\Condition.cs" />
+ <Compile Include="Interactivity\BindingCondition.cs" />
+ <Compile Include="Interactivity\PropertyCondition.cs" />
+ <Compile Include="Interactivity\MultiTrigger.cs" />
+ <Compile Include="Interactivity\MultiCondition.cs" />
+ <Compile Include="DeviceInfo.cs" />
+ <Compile Include="WebViewSource.cs" />
+ <Compile Include="WebViewSourceTypeConverter.cs" />
+ <Compile Include="XamlParseException.cs" />
+ <Compile Include="ScrolledEventArgs.cs" />
+ <Compile Include="ScrollToRequestedEventArgs.cs" />
+ <Compile Include="XmlLineInfo.cs" />
+ <Compile Include="GestureStatus.cs" />
+ <Compile Include="Internals\INameScope.cs" />
+ <Compile Include="Internals\NameScope.cs" />
+ <Compile Include="Internals\IDataTemplate.cs" />
+ <Compile Include="Internals\IDynamicResourceHandler.cs" />
+ <Compile Include="Internals\DynamicResource.cs" />
+ <Compile Include="PinchGestureRecognizer.cs" />
+ <Compile Include="IStyle.cs" />
+ <Compile Include="MergedStyle.cs" />
+ <Compile Include="IApplicationController.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
+ <ItemGroup>
+ <ProjectReference Include="..\Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj">
+ <Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
+ <Name>Xamarin.Forms.Platform</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup />
+ <PropertyGroup>
+ <PostBuildEvent>
+ </PostBuildEvent>
+ </PropertyGroup>
+ <ItemGroup />
+</Project> \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec b/Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec
new file mode 100644
index 00000000..b5dff885
--- /dev/null
+++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.nuspec
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<package >
+ <metadata>
+ <id>Xamarin.Forms.Core</id>
+ <version>$version$</version>
+ <title>Xamarin.Forms (.NET 4.5)</title>
+ <authors>Xamarin Inc.</authors>
+ <owners>Xamarin Inc.</owners>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <description>Xamarin.Forms platform abstruction library for mobile applications.</description>
+ <releaseNotes></releaseNotes>
+ <copyright>Copyright 2013</copyright>
+ </metadata>
+</package>
diff --git a/Xamarin.Forms.Core/XamlParseException.cs b/Xamarin.Forms.Core/XamlParseException.cs
new file mode 100644
index 00000000..42e8b618
--- /dev/null
+++ b/Xamarin.Forms.Core/XamlParseException.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ public class XamlParseException : Exception
+ {
+ readonly string _unformattedMessage;
+
+ public XamlParseException(string message, IXmlLineInfo xmlInfo) : base(FormatMessage(message, xmlInfo))
+ {
+ _unformattedMessage = message;
+ XmlInfo = xmlInfo;
+ }
+
+ public IXmlLineInfo XmlInfo { get; private set; }
+
+ internal string UnformattedMessage
+ {
+ get { return _unformattedMessage ?? Message; }
+ }
+
+ static string FormatMessage(string message, IXmlLineInfo xmlinfo)
+ {
+ if (xmlinfo == null || !xmlinfo.HasLineInfo())
+ return message;
+ return string.Format("Position {0}:{1}. {2}", xmlinfo.LineNumber, xmlinfo.LinePosition, message);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/XmlLineInfo.cs b/Xamarin.Forms.Core/XmlLineInfo.cs
new file mode 100644
index 00000000..eb11e316
--- /dev/null
+++ b/Xamarin.Forms.Core/XmlLineInfo.cs
@@ -0,0 +1,29 @@
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ public class XmlLineInfo : IXmlLineInfo
+ {
+ readonly bool _hasLineInfo;
+
+ public XmlLineInfo()
+ {
+ }
+
+ public XmlLineInfo(int linenumber, int lineposition)
+ {
+ _hasLineInfo = true;
+ LineNumber = linenumber;
+ LinePosition = lineposition;
+ }
+
+ public bool HasLineInfo()
+ {
+ return _hasLineInfo;
+ }
+
+ public int LineNumber { get; }
+
+ public int LinePosition { get; }
+ }
+} \ No newline at end of file