maui-linux/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs

510 строки
12 KiB
C#

using System;
using ElmSharp;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Tizen.Native
{
/// <summary>
/// The native widget which provides Xamarin.MasterDetailPage features.
/// </summary>
public class MasterDetailPage : Box
{
/// <summary>
/// The default master behavior (a.k.a mode).
/// </summary>
static readonly MasterBehavior s_defaultMasterBehavior = (Device.Idiom == TargetIdiom.Phone || Device.Idiom == TargetIdiom.Watch) ? MasterBehavior.Popover : MasterBehavior.SplitOnLandscape;
/// <summary>
/// The MasterPage native container.
/// </summary>
readonly Canvas _masterCanvas;
/// <summary>
/// The DetailPage native container.
/// </summary>
readonly Canvas _detailCanvas;
/// <summary>
/// The container for <c>_masterCanvas</c> and <c>_detailCanvas</c> used in split mode.
/// </summary>
readonly Panes _splitPane;
/// <summary>
/// The container for <c>_masterCanvas</c> used in popover mode.
/// </summary>
readonly Panel _drawer;
/// <summary>
/// The <see cref="MasterBehavior"/> property value.
/// </summary>
MasterBehavior _masterBehavior = s_defaultMasterBehavior;
/// <summary>
/// The actual MasterDetailPage mode - either split or popover. It depends on <c>_masterBehavior</c> and screen orientation.
/// </summary>
MasterBehavior _internalMasterBehavior = MasterBehavior.Popover;
/// <summary>
/// The <see cref="Master"/> property value.
/// </summary>
EvasObject _master;
/// <summary>
/// The <see cref="Detail"/> property value.
/// </summary>
EvasObject _detail;
/// <summary>
/// The main widget - either <see cref="_splitPlane"/> or <see cref="_detailPage"/>, depending on the mode.
/// </summary>
EvasObject _mainWidget;
/// <summary>
/// The <see cref="IsGestureEnabled"/> property value.
/// </summary>
bool _isGestureEnabled = true;
/// <summary>
/// The portion of the screen that the MasterPage takes in Split mode.
/// </summary>
double _splitRatio = 0.35;
/// <summary>
/// The portion of the screen that the MasterPage takes in Popover mode.
/// </summary>
double _popoverRatio = 0.8;
/// <summary>
/// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.MasterDetailPage"/> class.
/// </summary>
/// <param name="parent">Parent evas object.</param>
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();
}
};
}
/// <summary>
/// Occurs when the MasterPage is shown or hidden.
/// </summary>
public event EventHandler<IsPresentedChangedEventArgs> IsPresentedChanged;
/// <summary>
/// Occurs when the IsPresentChangeable was changed.
/// </summary>
public event EventHandler<UpdateIsPresentChangeableEventArgs> UpdateIsPresentChangeable;
/// <summary>
/// Gets or sets the MasterDetailPage behavior.
/// </summary>
/// <value>The behavior of the <c>MasterDetailPage</c> requested by the user.</value>
public MasterBehavior MasterBehavior
{
get
{
return _masterBehavior;
}
set
{
_masterBehavior = value;
UpdateMasterBehavior();
}
}
/// <summary>
/// Gets the MasterDEtailPage was splited
/// </summary>
public bool IsSplit => _internalMasterBehavior == MasterBehavior.Split;
/// <summary>
/// Gets or sets the content of the MasterPage.
/// </summary>
/// <value>The MasterPage.</value>
public EvasObject Master
{
get
{
return _master;
}
set
{
if (_master != value)
{
_master = value;
UpdatePageGeometry(_master);
_masterCanvas.Children.Clear();
_masterCanvas.Children.Add(_master);
if (!IsSplit)
UpdateFocusPolicy();
}
}
}
/// <summary>
/// Gets or sets the content of the DetailPage.
/// </summary>
/// <value>The DetailPage.</value>
public EvasObject Detail
{
get
{
return _detail;
}
set
{
if (_detail != value)
{
_detail = value;
UpdatePageGeometry(_detail);
_detailCanvas.Children.Clear();
_detailCanvas.Children.Add(_detail);
if (!IsSplit)
UpdateFocusPolicy();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the MasterPage is shown.
/// </summary>
/// <value><c>true</c> if the MasterPage is presented.</value>
public bool IsPresented
{
get
{
return _drawer.IsOpen;
}
set
{
if (_drawer.IsOpen != value)
{
_drawer.IsOpen = value;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether a MasterDetailPage allows showing MasterPage with swipe gesture.
/// </summary>
/// <value><c>true</c> if the MasterPage can be revealed with a gesture.</value>
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);
}
}
}
/// <summary>
/// Gets or Sets the portion of the screen that the MasterPage takes in split mode.
/// </summary>
/// <value>The portion.</value>
public double SplitRatio
{
get
{
return _splitRatio;
}
set
{
if (_splitRatio != value)
{
_splitRatio = value;
_splitPane.Proportion = _splitRatio;
}
}
}
/// <summary>
/// Gets or sets the portion of the screen that the MasterPage takes in Popover mode.
/// </summary>
/// <value>The portion.</value>
public double PopoverRatio
{
get
{
return _popoverRatio;
}
set
{
if (_popoverRatio != value)
{
_popoverRatio = value;
UpdateChildCanvasGeometry();
}
}
}
/// <summary>
/// Provides destruction for native element and contained elements.
/// </summary>
protected override void OnUnrealize()
{
// Views that are not belong to view tree should be unrealized.
if (IsSplit)
{
_drawer.Unrealize();
}
else
{
_splitPane.Unrealize();
}
base.OnUnrealize();
}
/// <summary>
/// Updates the geometry of the selected page.
/// </summary>
/// <param name="page">Master or Detail page to be updated.</param>
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;
}
}
}
/// <summary>
/// Updates <see cref="_internalMasterBehavior"/> according to <see cref="MasterDetailBehavior"/> set by the user and current screen orientation.
/// </summary>
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();
}
}
/// <summary>
/// Composes the structure of all the necessary widgets.
/// </summary>
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;
}
/// <summary>
/// Force update the focus management
/// </summary>
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;
}
/// <summary>
/// Value of IsPresent
/// </summary>
public bool IsPresent { get; private set; }
}
public class UpdateIsPresentChangeableEventArgs : EventArgs
{
public UpdateIsPresentChangeableEventArgs(bool canChange)
{
CanChange = canChange;
}
/// <summary>
/// Value of changeable
/// </summary>
public bool CanChange { get; private set; }
}
}