using System; using ElmSharp; using Xamarin.Forms.Internals; namespace Xamarin.Forms.Platform.Tizen.Native { /// /// The native widget which provides Xamarin.MasterDetailPage features. /// public class MasterDetailPage : Box { /// /// The default master behavior (a.k.a mode). /// static readonly MasterBehavior s_defaultMasterBehavior = (Device.Idiom == TargetIdiom.Phone || Device.Idiom == TargetIdiom.Watch) ? MasterBehavior.Popover : MasterBehavior.SplitOnLandscape; /// /// The MasterPage native container. /// readonly Canvas _masterCanvas; /// /// The DetailPage native container. /// readonly Canvas _detailCanvas; /// /// The container for _masterCanvas and _detailCanvas used in split mode. /// readonly Panes _splitPane; /// /// The container for _masterCanvas used in popover mode. /// readonly Panel _drawer; /// /// The property value. /// MasterBehavior _masterBehavior = s_defaultMasterBehavior; /// /// The actual MasterDetailPage mode - either split or popover. It depends on _masterBehavior and screen orientation. /// MasterBehavior _internalMasterBehavior = MasterBehavior.Popover; /// /// The property value. /// EvasObject _master; /// /// The property value. /// EvasObject _detail; /// /// The main widget - either or , depending on the mode. /// EvasObject _mainWidget; /// /// The property value. /// bool _isGestureEnabled = true; /// /// The portion of the screen that the MasterPage takes in Split mode. /// double _splitRatio = 0.35; /// /// The portion of the screen that the MasterPage takes in Popover mode. /// double _popoverRatio = 0.8; /// /// Initializes a new instance of the class. /// /// Parent evas object. public MasterDetailPage(EvasObject parent) : base(parent) { LayoutUpdated += (s, e) => { UpdateChildCanvasGeometry(); }; // create the controls which will hold the master and detail pages _masterCanvas = new Canvas(this); _masterCanvas.SetAlignment(-1.0, -1.0); // fill _masterCanvas.SetWeight(1.0, 1.0); // expand _masterCanvas.LayoutUpdated += (sender, e) => { UpdatePageGeometry(_master); }; _detailCanvas = new Canvas(this); _detailCanvas.SetAlignment(-1.0, -1.0); // fill _detailCanvas.SetWeight(1.0, 1.0); // expand _detailCanvas.LayoutUpdated += (sender, e) => { UpdatePageGeometry(_detail); }; _splitPane = new Panes(this) { AlignmentX = -1, AlignmentY = -1, WeightX = 1, WeightY = 1, IsFixed = true, IsHorizontal = false, Proportion = _splitRatio, }; _drawer = new Panel(Forms.NativeParent); _drawer.SetScrollable(_isGestureEnabled); _drawer.SetScrollableArea(1.0); _drawer.Direction = PanelDirection.Left; _drawer.Toggled += (object sender, EventArgs e) => { UpdateFocusPolicy(); IsPresentedChanged?.Invoke(this, new IsPresentedChangedEventArgs(_drawer.IsOpen)); }; ConfigureLayout(); // in case of the screen rotation we may need to update the choice between split // and popover behaviors and reconfigure the layout Device.Info.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(Device.Info.CurrentOrientation)) { UpdateMasterBehavior(); } }; } /// /// Occurs when the MasterPage is shown or hidden. /// public event EventHandler IsPresentedChanged; /// /// Occurs when the IsPresentChangeable was changed. /// public event EventHandler UpdateIsPresentChangeable; /// /// Gets or sets the MasterDetailPage behavior. /// /// The behavior of the MasterDetailPage requested by the user. public MasterBehavior MasterBehavior { get { return _masterBehavior; } set { _masterBehavior = value; UpdateMasterBehavior(); } } /// /// Gets the MasterDEtailPage was splited /// public bool IsSplit => _internalMasterBehavior == MasterBehavior.Split; /// /// Gets or sets the content of the MasterPage. /// /// The MasterPage. public EvasObject Master { get { return _master; } set { if (_master != value) { _master = value; UpdatePageGeometry(_master); _masterCanvas.Children.Clear(); _masterCanvas.Children.Add(_master); if (!IsSplit) UpdateFocusPolicy(); } } } /// /// Gets or sets the content of the DetailPage. /// /// The DetailPage. public EvasObject Detail { get { return _detail; } set { if (_detail != value) { _detail = value; UpdatePageGeometry(_detail); _detailCanvas.Children.Clear(); _detailCanvas.Children.Add(_detail); if (!IsSplit) UpdateFocusPolicy(); } } } /// /// Gets or sets a value indicating whether the MasterPage is shown. /// /// true if the MasterPage is presented. public bool IsPresented { get { return _drawer.IsOpen; } set { if (_drawer.IsOpen != value) { _drawer.IsOpen = value; } } } /// /// Gets or sets a value indicating whether a MasterDetailPage allows showing MasterPage with swipe gesture. /// /// true if the MasterPage can be revealed with a gesture. public bool IsGestureEnabled { get { return _isGestureEnabled; } set { if (_isGestureEnabled != value) { _isGestureEnabled = value; // Fixme // Elementary panel was not support to change scrollable property on runtime // Please uncomment when EFL was updated //_drawer.SetScrollable(_isGestureEnabled); } } } /// /// Gets or Sets the portion of the screen that the MasterPage takes in split mode. /// /// The portion. public double SplitRatio { get { return _splitRatio; } set { if (_splitRatio != value) { _splitRatio = value; _splitPane.Proportion = _splitRatio; } } } /// /// Gets or sets the portion of the screen that the MasterPage takes in Popover mode. /// /// The portion. public double PopoverRatio { get { return _popoverRatio; } set { if (_popoverRatio != value) { _popoverRatio = value; UpdateChildCanvasGeometry(); } } } /// /// Provides destruction for native element and contained elements. /// protected override void OnUnrealize() { // Views that are not belong to view tree should be unrealized. if (IsSplit) { _drawer.Unrealize(); } else { _splitPane.Unrealize(); } base.OnUnrealize(); } /// /// Updates the geometry of the selected page. /// /// Master or Detail page to be updated. void UpdatePageGeometry(EvasObject page) { if (page != null) { if (_master == page) { // update the geometry of the master page page.Geometry = _masterCanvas.Geometry; } else if (_detail == page) { // update the geometry of the detail page page.Geometry = _detailCanvas.Geometry; } } } /// /// Updates according to set by the user and current screen orientation. /// void UpdateMasterBehavior() { var behavior = (_masterBehavior == MasterBehavior.Default) ? s_defaultMasterBehavior : _masterBehavior; // Screen orientation affects those 2 behaviors if (behavior == MasterBehavior.SplitOnLandscape || behavior == MasterBehavior.SplitOnPortrait) { var orientation = Device.Info.CurrentOrientation; if ((orientation.IsLandscape() && behavior == MasterBehavior.SplitOnLandscape) || (orientation.IsPortrait() && behavior == MasterBehavior.SplitOnPortrait)) { behavior = MasterBehavior.Split; } else { behavior = MasterBehavior.Popover; } } if (behavior != _internalMasterBehavior) { _internalMasterBehavior = behavior; ConfigureLayout(); } } /// /// Composes the structure of all the necessary widgets. /// void ConfigureLayout() { _drawer.SetContent(null, true); _drawer.Hide(); _splitPane.SetPartContent("left", null, true); _splitPane.SetPartContent("right", null, true); _splitPane.Hide(); UnPackAll(); // the structure for split mode and for popover mode looks differently if (IsSplit) { _splitPane.SetPartContent("left", _masterCanvas, true); _splitPane.SetPartContent("right", _detailCanvas, true); _splitPane.Show(); _mainWidget = _splitPane; PackEnd(_splitPane); IsPresented = true; UpdateIsPresentChangeable?.Invoke(this, new UpdateIsPresentChangeableEventArgs(false)); UpdateFocusPolicy(true); } else { _drawer.SetContent(_masterCanvas, true); _drawer.Show(); _mainWidget = _detailCanvas; PackEnd(_detailCanvas); PackEnd(_drawer); _drawer.IsOpen = IsPresented; UpdateIsPresentChangeable?.Invoke(this, new UpdateIsPresentChangeableEventArgs(true)); UpdateFocusPolicy(); } _masterCanvas.Show(); _detailCanvas.Show(); // even though child was changed, Layout callback was not called, so i manually call layout function. // Layout callback was filter out when geometry was not changed in Native.Box UpdateChildCanvasGeometry(); } void UpdateChildCanvasGeometry() { var bound = Geometry; // main widget should fill the area of the MasterDetailPage if (_mainWidget != null) { _mainWidget.Geometry = bound; } bound.Width = (int)((_popoverRatio * bound.Width)); _drawer.Geometry = bound; // When a _drawer.IsOpen was false, Content of _drawer area is not allocated. So, need to manaully set the content area if (!IsSplit) _masterCanvas.Geometry = bound; } /// /// Force update the focus management /// void UpdateFocusPolicy(bool forceAllowFocusAll=false) { var master = _master as Widget; var detail = _detail as Widget; if(forceAllowFocusAll) { if (master != null) master.AllowTreeFocus = true; if (detail != null) detail.AllowTreeFocus = true; return; } if (_drawer.IsOpen) { if (detail != null) { detail.AllowTreeFocus = false; } if (master != null) { master.AllowTreeFocus = true; master.SetFocus(true); } } else { if (master != null) { master.AllowTreeFocus = false; } if (detail != null) { detail.AllowTreeFocus = true; detail.SetFocus(true); } } } } public class IsPresentedChangedEventArgs : EventArgs { public IsPresentedChangedEventArgs (bool isPresent) { IsPresent = isPresent; } /// /// Value of IsPresent /// public bool IsPresent { get; private set; } } public class UpdateIsPresentChangeableEventArgs : EventArgs { public UpdateIsPresentChangeableEventArgs(bool canChange) { CanChange = canChange; } /// /// Value of changeable /// public bool CanChange { get; private set; } } }