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