/*
* Copyright(c) 2019 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 System;
using System.ComponentModel;
using System.Collections.Generic;
using Tizen.NUI.BaseComponents;
namespace Tizen.NUI.Components
{
///
/// FlexibleView ItemClick Event Arguments.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public class FlexibleViewItemClickedEventArgs : EventArgs
{
///
/// The clicked FlexibleViewViewHolder.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewViewHolder ClickedView { get; set; }
}
///
/// FlexibleView ItemTouch Event Arguments.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public class FlexibleViewItemTouchEventArgs : View.TouchEventArgs
{
///
/// The touched FlexibleViewViewHolder.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewViewHolder TouchedView { get; set; }
}
///
/// A flexible view for providing a limited window into a large data set.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public partial class FlexibleView : Control
{
///
/// Constant value: -1.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly int NO_POSITION = -1;
///
/// Constant value: -1.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly int INVALID_TYPE = -1;
private FlexibleViewAdapter mAdapter;
private FlexibleViewLayoutManager mLayout;
private FlexibleViewRecycler mRecycler;
private RecycledViewPool mRecyclerPool;
private ChildHelper mChildHelper;
private PanGestureDetector mPanGestureDetector;
private int mFocusedItemIndex = NO_POSITION;
private AdapterHelper mAdapteHelper;
private ScrollBar mScrollBar = null;
private Timer mScrollBarShowTimer = null;
private EventHandler clickEventHandlers;
private EventHandler touchEventHandlers;
private EventHandler styleChangedEventHandlers;
///
/// Creates a FlexibleView instance.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleView()
{
mRecyclerPool = new RecycledViewPool(this);
mRecycler = new FlexibleViewRecycler(this);
mRecycler.SetRecycledViewPool(mRecyclerPool);
mChildHelper = new ChildHelper(this);
mPanGestureDetector = new PanGestureDetector();
mPanGestureDetector.Attach(this);
mPanGestureDetector.Detected += OnPanGestureDetected;
mAdapteHelper = new AdapterHelper(this);
ClippingMode = ClippingModeType.ClipToBoundingBox;
}
///
/// Item click event.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler ItemClicked
{
add
{
clickEventHandlers += value;
}
remove
{
clickEventHandlers -= value;
}
}
///
/// Item touch event.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler ItemTouch
{
add
{
touchEventHandlers += value;
}
remove
{
touchEventHandlers -= value;
}
}
///
/// Style changed, for example default font size.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler StyleChanged
{
add
{
styleChangedEventHandlers += value;
}
remove
{
styleChangedEventHandlers -= value;
}
}
private new Extents padding;
///
/// overwrite the Padding.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public new Extents Padding
{
get
{
if (null == padding)
{
padding = new Extents((ushort start, ushort end, ushort top, ushort bottom) =>
{
padding.Start = start;
padding.End = end;
padding.Top = top;
padding.Bottom = bottom;
}, 0, 0, 0, 0);
}
return padding;
}
set
{
if (null == padding)
{
padding = new Extents((ushort start, ushort end, ushort top, ushort bottom) =>
{
padding.Start = start;
padding.End = end;
padding.Top = top;
padding.Bottom = bottom;
}, 0, 0, 0, 0);
}
padding.CopyFrom(value);
}
}
///
/// Gets or sets the focused item index(adapter position).
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public int FocusedItemIndex
{
get
{
return mFocusedItemIndex;
}
set
{
if (value == mFocusedItemIndex)
{
return;
}
if (mAdapter == null)
{
return;
}
if (mLayout == null)
{
return;
}
FlexibleViewViewHolder nextFocusView = FindViewHolderForAdapterPosition(value);
if (nextFocusView == null)
{
mLayout.ScrollToPosition(value);
}
else
{
mLayout.RequestChildRectangleOnScreen(this, nextFocusView, mRecycler, true);
DispatchFocusChanged(value);
}
}
}
///
/// Set a new adapter to provide child views on demand.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetAdapter(FlexibleViewAdapter adapter)
{
if (adapter == null)
{
return;
}
mAdapter = adapter;
mAdapter.ItemEvent += OnItemEvent;
}
///
/// Retrieves the previously set adapter or null if no adapter is set.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewAdapter GetAdapter()
{
return mAdapter;
}
///
/// Set the FlexibleViewLayoutManager that this FlexibleView will use.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetLayoutManager(FlexibleViewLayoutManager layoutManager)
{
if (null == layoutManager) return;
mLayout = layoutManager;
mLayout.SetRecyclerView(this);
if (mLayout.CanScrollHorizontally())
{
mPanGestureDetector.AddDirection(PanGestureDetector.DirectionHorizontal);
}
else if (mLayout.CanScrollVertically())
{
mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
}
}
///
/// Return the FlexibleViewLayoutManager currently responsible for layout policy for this FlexibleView.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewLayoutManager GetLayoutManager()
{
return mLayout;
}
///
/// Convenience method to scroll to a certain position
///
/// Adapter position
/// The distance (in pixels) between the start edge of the item view and start edge of the FlexibleView.
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public void ScrollToPositionWithOffset(int position, int offset)
{
mLayout.ScrollToPositionWithOffset(position, offset);
}
///
/// Move focus by direction.
///
/// Direction. Should be "Left", "Right", "Up" or "Down"
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public void MoveFocus(FlexibleViewLayoutManager.Direction direction)
{
mLayout.MoveFocus(direction, mRecycler);
}
///
/// Attach a scrollbar to this FlexibleView.
///
/// ScrollBar
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public void AttachScrollBar(ScrollBar scrollBar)
{
if (scrollBar == null)
{
return;
}
mScrollBar = scrollBar;
Add(mScrollBar);
}
///
/// Detach the scrollbar from this FlexibleView.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public void DetachScrollBar()
{
if (mScrollBar == null)
{
return;
}
Remove(mScrollBar);
mScrollBar = null;
}
///
/// Return the FlexibleViewViewHolder for the item in the given position of the data set as of the latest layout pass.
/// This method checks only the children of FlexibleViewRecyclerView. If the item at the given position is not laid out, it will not create a new one.
///
/// The position of the item in the data set of the adapter
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewViewHolder FindViewHolderForLayoutPosition(int position)
{
int childCount = mChildHelper.GetChildCount();
for (int i = 0; i < childCount; i++)
{
if (mChildHelper.GetChildAt(i) is FlexibleViewViewHolder holder)
{
if (holder.LayoutPosition == position)
{
return holder;
}
}
}
return null;
}
///
/// Return the FlexibleViewViewHolder for the item in the given position of the data set.
/// This method checks only the children of FlexibleViewRecyclerView. If the item at the given position is not laid out, it will not create a new one.
///
/// The position of the item in the data set of the adapter
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewViewHolder FindViewHolderForAdapterPosition(int position)
{
int childCount = mChildHelper.GetChildCount();
for (int i = 0; i < childCount; i++)
{
if (mChildHelper.GetChildAt(i) is FlexibleViewViewHolder holder)
{
if (holder.AdapterPosition == position)
{
return holder;
}
}
}
return null;
}
///
/// Return the recycler instance.
///
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public FlexibleViewRecycler GetRecycler()
{
return mRecycler;
}
///
/// you can override it to clean-up your own resources.
///
/// DisposeTypes
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void Dispose(DisposeTypes type)
{
if (disposed)
{
return;
}
if (type == DisposeTypes.Explicit)
{
if (mLayout != null)
{
mLayout.StopScroll(false);
mLayout.ClearRecyclerView();
mLayout = null;
}
if (mAdapter != null)
{
mAdapter.ItemEvent -= OnItemEvent;
}
if (mPanGestureDetector != null)
{
mPanGestureDetector.Detected -= OnPanGestureDetected;
mPanGestureDetector.Dispose();
mPanGestureDetector = null;
}
if (mScrollBarShowTimer != null)
{
mScrollBarShowTimer.Tick -= OnShowTimerTick;
mScrollBarShowTimer.Stop();
mScrollBarShowTimer.Dispose();
mScrollBarShowTimer = null;
}
if (mRecyclerPool != null)
{
mRecyclerPool.Clear();
mRecyclerPool = null;
}
if (mChildHelper != null)
{
mChildHelper.Clear();
mChildHelper.Dispose();
mChildHelper = null;
}
}
base.Dispose(type);
}
///
/// you can override it to create your own default style.
///
/// 6
/// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override ViewStyle CreateViewStyle()
{
return null;
}
///
/// you can override it to relayout elements.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public override void OnRelayout(Vector2 size, RelayoutContainer container)
{
if (mAdapter == null)
{
return;
}
if (mLayout == null)
{
return;
}
DispatchLayoutStep1();
mLayout.OnLayoutChildren(mRecycler);
RemoveAndRecycleScrapInt();
}
///
/// you can override it to do something for style change.
///
/// 6
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public override void OnStyleChange(NUI.StyleManager styleManager, StyleChangeType change)
{
if (change == StyleChangeType.DefaultFontSizeChange)
{
NUI.StyleManager.StyleChangedEventArgs args = new NUI.StyleManager.StyleChangedEventArgs();
args.StyleManager = styleManager;
args.StyleChange = change;
styleChangedEventHandlers?.Invoke(this, args);
RelayoutRequest();
}
}
private void DispatchLayoutStep1()
{
ProcessAdapterUpdates();
SaveOldPositions();
ClearOldPositions();
}
private void ProcessAdapterUpdates()
{
mAdapteHelper.PreProcess();
}
private void OffsetPositionRecordsForInsert(int positionStart, int itemCount)
{
int childCount = mChildHelper.GetChildCount();
for (int i = 0; i < childCount; i++)
{
FlexibleViewViewHolder holder = mChildHelper.GetChildAt(i);
if (holder != null && holder.AdapterPosition >= positionStart)
{
holder.OffsetPosition(itemCount, false);
}
}
if (positionStart <= mFocusedItemIndex)
{
mFocusedItemIndex += itemCount;
}
}
private void OffsetPositionRecordsForRemove(int positionStart, int itemCount, bool applyToPreLayout)
{
int positionEnd = positionStart + itemCount;
int childCount = mChildHelper.GetChildCount();
for (int i = 0; i < childCount; i++)
{
FlexibleViewViewHolder holder = mChildHelper.GetChildAt(i);
if (holder != null)
{
if (holder.AdapterPosition >= positionEnd)
{
holder.OffsetPosition(-itemCount, applyToPreLayout);
}
else if (holder.AdapterPosition >= positionStart)
{
holder.FlagRemovedAndOffsetPosition(positionStart - 1, -itemCount, applyToPreLayout);
}
}
}
if (positionEnd <= mFocusedItemIndex)
{
mFocusedItemIndex -= itemCount;
}
else if (positionStart <= mFocusedItemIndex)
{
mFocusedItemIndex = positionStart;
if (mFocusedItemIndex >= mAdapter.GetItemCount())
{
mFocusedItemIndex = mAdapter.GetItemCount() - 1;
}
}
}
private void SaveOldPositions()
{
int childCount = mChildHelper.GetChildCount();
for (int i = 0; i < childCount; i++)
{
FlexibleViewViewHolder holder = mChildHelper.GetChildAt(i);
holder.SaveOldPosition();
}
}
private void ClearOldPositions()
{
int childCount = mChildHelper.GetChildCount();
for (int i = 0; i < childCount; i++)
{
FlexibleViewViewHolder holder = mChildHelper.GetChildAt(i);
holder.ClearOldPosition();
}
}
private void RemoveAndRecycleScrapInt()
{
int scrapCount = mRecycler.GetScrapCount();
for (int i = 0; i < scrapCount; i++)
{
FlexibleViewViewHolder scrap = mRecycler.GetScrapViewAt(i);
mChildHelper.RemoveView(scrap);
mRecycler.RecycleView(scrap);
}
mRecycler.Clear();
}
private void ShowScrollBar(uint millisecond = 700, bool flagAni = false)
{
if (mScrollBar == null || mLayout == null)
{
return;
}
float extent = mLayout.ComputeScrollExtent();
float range = mLayout.ComputeScrollRange();
if (range == 0)
{
return;
}
float offset = mLayout.ComputeScrollOffset();
float size = mScrollBar.Direction == ScrollBar.DirectionType.Vertical ? mScrollBar.SizeHeight : mScrollBar.SizeWidth;
float thickness = mScrollBar.Direction == ScrollBar.DirectionType.Vertical ? mScrollBar.SizeWidth : mScrollBar.SizeHeight;
float length = (float)Math.Round(size * extent / range);
// avoid the tiny thumb
float minLength = thickness * 2;
if (length < minLength)
{
length = minLength;
}
// avoid the too-big thumb
if (offset > range - extent)
{
offset = range - extent;
}
if (offset < 0)
{
offset = 0;
}
if (mScrollBar.Direction == ScrollBar.DirectionType.Vertical)
{
mScrollBar.Style.Thumb.Size = new Size(thickness, length);
}
else
{
mScrollBar.Style.Thumb.Size = new Size(length, thickness);
}
mScrollBar.MinValue = 0;
mScrollBar.MaxValue = (int)(range - extent);
mScrollBar.SetCurrentValue((int)offset, flagAni);
mScrollBar.Show();
if (mScrollBarShowTimer == null)
{
mScrollBarShowTimer = new Timer(millisecond);
mScrollBarShowTimer.Tick += OnShowTimerTick;
}
else
{
mScrollBarShowTimer.Interval = millisecond;
}
mScrollBarShowTimer.Start();
}
private bool OnShowTimerTick(object source, EventArgs e)
{
if (mScrollBar != null)
{
mScrollBar.Hide();
}
return false;
}
internal void DispatchFocusChanged(int nextFocusPosition)
{
mAdapter.OnFocusChange(this, mFocusedItemIndex, nextFocusPosition);
mFocusedItemIndex = nextFocusPosition;
ShowScrollBar();
}
private void DispatchChildAttached(FlexibleViewViewHolder holder)
{
if (mAdapter != null && holder != null)
{
mAdapter.OnViewAttachedToWindow(holder);
}
}
private void DispatchChildDetached(FlexibleViewViewHolder holder)
{
if (mAdapter != null && holder != null)
{
mAdapter.OnViewDetachedFromWindow(holder);
}
}
private void DispatchChildDestroyed(FlexibleViewViewHolder holder)
{
if (mAdapter != null && holder != null)
{
mAdapter.OnDestroyViewHolder(holder);
}
}
private void DispatchItemClicked(FlexibleViewViewHolder clickedHolder)
{
FlexibleViewItemClickedEventArgs args = new FlexibleViewItemClickedEventArgs();
args.ClickedView = clickedHolder;
OnClickEvent(this, args);
}
private void DispatchItemTouched(FlexibleViewViewHolder touchedHolder, Touch touchEvent)
{
FlexibleViewItemTouchEventArgs args = new FlexibleViewItemTouchEventArgs();
args.TouchedView = touchedHolder;
args.Touch = touchEvent;
OnTouchEvent(this, args);
}
private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
{
if (e.PanGesture.State == Gesture.StateType.Started)
{
mLayout.StopScroll(true);
}
else if (e.PanGesture.State == Gesture.StateType.Continuing)
{
if (mLayout.CanScrollVertically())
{
mLayout.ScrollVerticallyBy(e.PanGesture.Displacement.Y, mRecycler, true);
}
else if (mLayout.CanScrollHorizontally())
{
mLayout.ScrollHorizontallyBy(e.PanGesture.Displacement.X, mRecycler, true);
}
ShowScrollBar();
}
else if (e.PanGesture.State == Gesture.StateType.Finished)
{
if (mLayout.CanScrollVertically())
{
mLayout.ScrollVerticallyBy(e.PanGesture.Velocity.Y * 600, mRecycler, false);
}
else if (mLayout.CanScrollHorizontally())
{
mLayout.ScrollHorizontallyBy(e.PanGesture.Velocity.X * 600, mRecycler, false);
}
ShowScrollBar(1200, true);
}
}
private void OnItemEvent(object sender, FlexibleViewAdapter.ItemEventArgs e)
{
switch (e.EventType)
{
case FlexibleViewAdapter.ItemEventType.Insert:
mAdapteHelper.OnItemRangeInserted(e.param[0], e.param[1]);
ShowScrollBar();
break;
case FlexibleViewAdapter.ItemEventType.Remove:
mAdapteHelper.OnItemRangeRemoved(e.param[0], e.param[1]);
ShowScrollBar();
break;
case FlexibleViewAdapter.ItemEventType.Move:
break;
case FlexibleViewAdapter.ItemEventType.Change:
break;
default:
return;
}
RelayoutRequest();
}
private void OnClickEvent(object sender, FlexibleViewItemClickedEventArgs e)
{
clickEventHandlers?.Invoke(sender, e);
}
private void OnTouchEvent(object sender, FlexibleViewItemTouchEventArgs e)
{
touchEventHandlers?.Invoke(sender, e);
}
internal void LayoutManagerRelayoutRequest()
{
RelayoutRequest();
}
internal ChildHelper GetChildHelper()
{
return mChildHelper;
}
}
}