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; }
}
}