/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using Tizen.NUI.BaseComponents;
using System.ComponentModel;
using System;
namespace Tizen.NUI
{
///
/// [Draft] The class that initiates the Measuring and Layouting of the tree,
/// It sets a callback that becomes the entry point into the C# Layouting.
///
internal class LayoutController : Disposable
{
private static int layoutControllerID = 1;
private int id;
private Window window;
private float windowWidth;
private float windowHeight;
private LayoutTransitionManager transitionManager;
private int layoutCount = 0;
///
/// Constructs a LayoutController which controls the measuring and layouting.
/// Window attach this LayoutController to.
///
public LayoutController(Window window)
{
transitionManager = new LayoutTransitionManager();
id = layoutControllerID++;
SetBaseWindowAndSize(window);
}
///
/// Dispose Explicit or Implicit
///
protected override void Dispose(DisposeTypes type)
{
if (Disposed)
{
return;
}
LayoutCount = 0;
//Release your own unmanaged resources here.
//You should not access any managed member here except static instance.
//because the execution order of Finalizes is non-deterministic.
if (SwigCPtr.Handle != global::System.IntPtr.Zero)
{
SwigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
}
transitionManager.Dispose();
base.Dispose(type);
}
///
/// Set or Get Layouting core animation override property.
/// Gives explicit control over the Layouting animation playback if set to True.
/// Reset to False if explicit control no longer required.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public bool OverrideCoreAnimation
{
get
{
return transitionManager.OverrideCoreAnimation;
}
set
{
transitionManager.OverrideCoreAnimation = value;
}
}
///
/// Get the unique id of the LayoutController
///
public int GetId()
{
return id;
}
///
/// Request relayout of a LayoutItem and it's parent tree.
///
/// LayoutItem that is required to be laid out again.
public void RequestLayout(LayoutItem layoutItem)
{
// Go up the tree and mark all parents to relayout
ILayoutParent layoutParent = layoutItem.GetParent();
if (layoutParent != null)
{
LayoutGroup layoutGroup = layoutParent as LayoutGroup;
if (layoutGroup != null && !layoutGroup.LayoutRequested)
{
layoutGroup.RequestLayout();
}
}
}
///
/// Entry point into the C# Layouting that starts the Processing
///
public void Process(object source, EventArgs e)
{
window.LayersChildren?.ForEach(layer =>
{
if(layer?.LayoutCount > 0)
{
layer?.Children?.ForEach(view =>
{
if (view != null)
{
FindRootLayouts(view, windowWidth, windowHeight);
}
});
}
});
transitionManager.SetupCoreAndPlayAnimation();
}
///
/// Get the Layouting animation object that transitions layouts and content.
/// Use OverrideCoreAnimation to explicitly control Playback.
///
/// The layouting core Animation.
[EditorBrowsable(EditorBrowsableState.Never)]
public Animation GetCoreAnimation()
{
return transitionManager.CoreAnimation;
}
///
/// Add transition data for a LayoutItem to the transition stack.
///
/// Transition data for a LayoutItem.
internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
{
transitionManager.AddTransitionDataEntry(transitionDataEntry);
}
///
/// Add LayoutItem to a removal stack for removal after transitions finish.
///
/// LayoutItem to remove.
internal void AddToRemovalStack(LayoutItem itemToRemove)
{
transitionManager.AddToRemovalStack(itemToRemove);
}
///
/// The number of layouts.
/// This can be used to set/unset Process callback to calculate Layout.
///
internal int LayoutCount
{
get
{
return layoutCount;
}
set
{
if (layoutCount == value) return;
if (value < 0) throw new global::System.ArgumentOutOfRangeException(nameof(LayoutCount), "LayoutCount(" + LayoutCount + ") should not be less than zero");
if (layoutCount == 0)
{
ProcessorController.Instance.LayoutProcessorEvent += Process;
}
else if (value == 0)
{
ProcessorController.Instance.LayoutProcessorEvent -= Process;
}
layoutCount = value;
}
}
// Traverse the tree looking for a root node that is a layout.
// Once found, it's children can be assigned Layouts and the Measure process started.
private void FindRootLayouts(View rootNode, float parentWidth, float parentHeight)
{
if (rootNode.Layout != null)
{
NUILog.Debug("LayoutController Root found:" + rootNode.Name);
// rootNode has a layout, start measuring and layouting from here.
MeasureAndLayout(rootNode, parentWidth, parentHeight);
}
else
{
float rootWidth = rootNode.SizeWidth;
float rootHeight = rootNode.SizeHeight;
// Search children of supplied node for a layout.
for (uint i = 0; i < rootNode.ChildCount; i++)
{
FindRootLayouts(rootNode.GetChildAt(i), rootWidth, rootHeight);
}
}
}
// Starts of the actual measuring and layouting from the given root node.
// Can be called from multiple starting roots but in series.
// Get parent View's Size. If using Legacy size negotiation then should have been set already.
// Parent not a View so assume it's a Layer which is the size of the window.
private void MeasureAndLayout(View root, float parentWidth, float parentHeight)
{
var layout = root.Layout;
if (layout != null)
{
// Determine measure specification for root.
// The root layout policy could be an exact size, be match parent or wrap children.
// If wrap children then at most it can be the root parent size.
// If match parent then should be root parent size.
// If exact then should be that size limited by the root parent size.
float widthSize = GetLengthSize(parentWidth, root.WidthSpecification);
float heightSize = GetLengthSize(parentHeight, root.HeightSpecification);
var widthMode = GetMode(root.WidthSpecification);
var heightMode = GetMode(root.HeightSpecification);
if (layout.NeedsLayout(widthSize, heightSize, widthMode, heightMode))
{
var widthSpec = CreateMeasureSpecification(widthSize, widthMode);
var heightSpec = CreateMeasureSpecification(heightSize, heightMode);
// Start at root with it's parent's widthSpecification and heightSpecification
MeasureHierarchy(root, widthSpec, heightSpec);
}
float positionX = root.PositionX;
float positionY = root.PositionY;
// Start at root which was just measured.
PerformLayout(root, new LayoutLength(positionX),
new LayoutLength(positionY),
new LayoutLength(positionX) + layout.MeasuredWidth.Size,
new LayoutLength(positionY) + layout.MeasuredHeight.Size);
}
}
private float GetLengthSize(float size, int specification)
{
// exact size provided so match width exactly
return (specification >= 0) ? specification : size;
}
private MeasureSpecification.ModeType GetMode(int specification)
{
if (specification >= 0 || specification == LayoutParamPolicies.MatchParent)
{
return MeasureSpecification.ModeType.Exactly;
}
return MeasureSpecification.ModeType.Unspecified;
}
private MeasureSpecification CreateMeasureSpecification(float size, MeasureSpecification.ModeType mode)
{
return new MeasureSpecification(new LayoutLength(size), mode);
}
///
/// Starts measuring the tree, starting from the root layout.
///
private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
{
// Does this View have a layout?
// Yes - measure the layout. It will call this method again for each of it's children.
// No - reached leaf or no layouts set
//
// If in a leaf View with no layout, it's natural size is bubbled back up.
root.Layout?.Measure(widthSpec, heightSpec);
}
///
/// Performs the layouting positioning
///
private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
root.Layout?.Layout(left, top, right, bottom);
}
private void SetBaseWindowAndSize(Window window)
{
this.window = window;
this.window.Resized += OnWindowResized;
var windowSize = window.GetSize();
windowWidth = windowSize.Width;
windowHeight = windowSize.Height;
windowSize.Dispose();
windowSize = null;
}
private void OnWindowResized(object sender, Window.ResizedEventArgs e)
{
windowWidth = e.WindowSize.Width;
windowHeight = e.WindowSize.Height;
}
} // class LayoutController
} // namespace Tizen.NUI