/*
* 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.Runtime.InteropServices;
using Tizen.NUI.BaseComponents;
using System.ComponentModel;
namespace Tizen.NUI
{
///
/// Provides the functionality of handling keyboard navigation and maintaining the two-dimensional keyboard focus chain.
/// It provides functionality of setting the focus and moving the focus in four directions( i.e., left, right, up, and down).
/// It also draws a highlight for the focused view and sends an event when the focus is changed.
///
/// 3
public class FocusManager : BaseHandle
{
private static readonly FocusManager instance = FocusManager.Get();
private CustomAlgorithmInterfaceWrapper _customAlgorithmInterfaceWrapper;
private EventHandlerWithReturnType _preFocusChangeEventHandler;
private PreFocusChangeEventCallback _preFocusChangeCallback;
private EventHandler _focusChangedEventHandler;
private FocusChangedEventCallback _focusChangedEventCallback;
private EventHandler _focusGroupChangedEventHandler;
private FocusGroupChangedEventCallback _focusGroupChangedEventCallback;
private EventHandler _focusedViewEnterKeyEventHandler;
private FocusedViewEnterKeyEventCallback _focusedViewEnterKeyEventCallback;
private EventHandler _focusedViewEnterKeyEventHandler2;
private FocusedViewEnterKeyEventCallback2 _focusedViewEnterKeyEventCallback2;
internal FocusManager(global::System.IntPtr cPtr, bool cMemoryOwn) : base(Interop.FocusManager.Upcast(cPtr), cMemoryOwn)
{
}
internal FocusManager() : this(Interop.FocusManager.NewFocusManager(), true)
{
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate IntPtr PreFocusChangeEventCallback(IntPtr current, IntPtr proposed, View.FocusDirection direction);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate void FocusChangedEventCallback(IntPtr current, IntPtr next);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void FocusGroupChangedEventCallback(IntPtr current, bool forwardDirection);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void FocusedViewEnterKeyEventCallback(IntPtr view);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void FocusedViewEnterKeyEventCallback2(IntPtr view);
///
/// PreFocusChange will be triggered before the focus is going to be changed.
/// The FocusManager makes the best guess for which view to focus towards the given direction, but applications might want to change that.
/// By connecting with this event, they can check the proposed view to focus and return a different view if they wish.
/// This event is only triggered when the navigation key is pressed and KeyboardFocusManager tries to move the focus automatically.
/// It won't be emitted for focus movement by calling the SetCurrentFocusView directly.
///
/// 3
public event EventHandlerWithReturnType PreFocusChange
{
add
{
if (_preFocusChangeEventHandler == null)
{
_preFocusChangeCallback = OnPreFocusChange;
PreFocusChangeSignal().Connect(_preFocusChangeCallback);
}
_preFocusChangeEventHandler += value;
}
remove
{
_preFocusChangeEventHandler -= value;
if (_preFocusChangeEventHandler == null && PreFocusChangeSignal().Empty() == false)
{
PreFocusChangeSignal().Disconnect(_preFocusChangeCallback);
}
}
}
///
/// The FocusGroupChanged will be triggered after the current focused view has been changed.
///
/// 3
public event EventHandler FocusChanged
{
add
{
if (_focusChangedEventCallback == null)
{
_focusChangedEventCallback = OnFocusChanged;
FocusChangedSignal().Connect(_focusChangedEventCallback);
}
_focusChangedEventHandler += value;
}
remove
{
_focusChangedEventHandler -= value;
if (_focusChangedEventCallback == null && FocusChangedSignal().Empty() == false)
{
FocusChangedSignal().Disconnect(_focusChangedEventCallback);
}
}
}
///
/// The FocusGroupChanged will be triggered when the focus group has been changed.
/// If the current focus group has a parent layout control, the FocusManager will make the best guess for the next focus group to move the focus to in the given direction (forward or backward).
/// If not, the application has to set the new focus.
///
/// 3
public event EventHandler FocusGroupChanged
{
add
{
if (_focusGroupChangedEventCallback == null)
{
_focusGroupChangedEventCallback = OnFocusGroupChanged;
FocusGroupChangedSignal().Connect(_focusGroupChangedEventCallback);
}
_focusGroupChangedEventHandler += value;
}
remove
{
_focusGroupChangedEventHandler -= value;
if (_focusGroupChangedEventCallback == null && FocusGroupChangedSignal().Empty() == false)
{
FocusGroupChangedSignal().Disconnect(_focusGroupChangedEventCallback);
}
}
}
///
/// The FocusedViewActivated will be triggered when the current focused view has the enter key pressed on it.
///
/// 3
public event EventHandler FocusedViewActivated
{
add
{
if (_focusedViewEnterKeyEventCallback == null)
{
_focusedViewEnterKeyEventCallback = OnFocusedViewEnterKey;
FocusedViewEnterKeySignal().Connect(_focusedViewEnterKeyEventCallback);
}
_focusedViewEnterKeyEventHandler += value;
}
remove
{
_focusedViewEnterKeyEventHandler -= value;
if (_focusedViewEnterKeyEventCallback != null && FocusedViewEnterKeySignal().Empty() == false)
{
FocusedViewEnterKeySignal().Disconnect(_focusedViewEnterKeyEventCallback);
}
}
}
///
/// [Obsolete("Please do not use! this will be deprecated")]
///
/// 3
/// Please do not use! this will be deprecated!
/// Instead please use FocusedViewActivated.
[Obsolete("Please do not use! This will be deprecated! Please use FocusManager.FocusedViewActivated instead! " +
"Like: " +
"FocusManager.Instance.FocusedViewActivated = OnFocusedViewActivated; " +
"private void OnFocusedViewActivated(object source, FocusManager.FocusedViewActivatedEventArgs args) {...}")]
[EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler FocusedViewEnterKeyPressed
{
add
{
if (_focusedViewEnterKeyEventCallback2 == null)
{
_focusedViewEnterKeyEventCallback2 = OnFocusedViewEnterKey2;
FocusedViewEnterKeySignal().Connect(_focusedViewEnterKeyEventCallback2);
}
_focusedViewEnterKeyEventHandler2 += value;
}
remove
{
_focusedViewEnterKeyEventHandler2 -= value;
if (_focusedViewEnterKeyEventCallback2 != null && FocusedViewEnterKeySignal().Empty() == false)
{
FocusedViewEnterKeySignal().Disconnect(_focusedViewEnterKeyEventCallback2);
}
}
}
///
/// ICustomFocusAlgorithm is used to provide the custom keyboard focus algorithm for retrieving the next focusable view.
/// The application can implement the interface and override the keyboard focus behavior.
/// If the focus is changing within a layout container, then the layout container is queried first to provide the next focusable view.
/// If this does not provide a valid view, then the Keyboard FocusManager will check focusable properties to determine the next focusable actor.
/// If focusable properties are not set, then the keyboard FocusManager calls the GetNextFocusableView() method of this interface.
///
/// 3
public interface ICustomFocusAlgorithm
{
///
/// Get the next focus actor.
///
/// The current focus view.
/// The proposed focus view
/// The focus move direction
/// The next focus actor.
/// 3
View GetNextFocusableView(View current, View proposed, View.FocusDirection direction);
}
///
/// Gets or sets the status of whether the focus movement should be looped within the same focus group.
/// The focus movement is not looped by default.
///
/// 3
public bool FocusGroupLoop
{
set
{
SetFocusGroupLoop(value);
}
get
{
return GetFocusGroupLoop();
}
}
///
/// Gets or sets the focus indicator view.
/// This will replace the default focus indicator view in the FocusManager and will be added to the focused view as a highlight.
///
/// 3
public View FocusIndicator
{
set
{
SetFocusIndicatorView(value);
}
get
{
return GetFocusIndicatorView();
}
}
///
/// Gets the singleton of the FocusManager object.
///
/// 3
public static FocusManager Instance
{
get
{
return instance;
}
}
///
/// Moves the keyboard focus to the given view.
/// Only one view can be focused at the same time.
/// The view must be in the stage already and keyboard focusable.
///
/// The view to be focused.
/// Whether the focus is successful or not.
/// 3
public bool SetCurrentFocusView(View view)
{
if (view == null)
{
throw new ArgumentNullException(nameof(view), "the target view should not be null");
}
bool ret = Interop.FocusManager.SetCurrentFocusActor(SwigCPtr, View.getCPtr(view));
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
///
/// Gets the current focused view.
///
/// A handle to the current focused view or an empty handle if no view is focused.
/// 3
public View GetCurrentFocusView()
{
//to fix memory leak issue, match the handle count with native side.
IntPtr cPtr = Interop.FocusManager.GetCurrentFocusActor(SwigCPtr);
View ret = this.GetInstanceSafely(cPtr);
return ret;
}
///
/// Moves the focus to the next focusable view in the focus chain in the given direction (according to the focus traversal order).
///
/// The direction of the focus movement.
/// True if the movement was successful.
/// 3
public bool MoveFocus(View.FocusDirection direction)
{
bool ret = Interop.FocusManager.MoveFocus(SwigCPtr, (int)direction);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
///
/// Clears the focus from the current focused view if any, so that no view is focused in the focus chain.
/// It will emit the FocusChanged event without the current focused view.
///
/// 3
public void ClearFocus()
{
Interop.FocusManager.ClearFocus(SwigCPtr);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
///
/// Move the focus to previous focused view.
///
/// 3
public void MoveFocusBackward()
{
Interop.FocusManager.MoveFocusBackward(SwigCPtr);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
///
/// Sets whether the view is a focus group that can limit the scope of the focus movement to its child views in the focus chain.
/// Layout controls set themselves as focus groups by default.
///
/// The view to be set as a focus group.
/// Whether to set the view as a focus group or not.
/// 3
public void SetAsFocusGroup(View view, bool isFocusGroup)
{
Interop.FocusManager.SetAsFocusGroup(SwigCPtr, View.getCPtr(view), isFocusGroup);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
///
/// Checks whether the view is set as a focus group or not.
///
/// The view to be checked.
/// Whether the view is set as a focus group.
/// 3
public bool IsFocusGroup(View view)
{
bool ret = Interop.FocusManager.IsFocusGroup(SwigCPtr, View.getCPtr(view));
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
///
/// Returns the closest ancestor of the given view that is a focus group.
///
/// The view to be checked for its focus group.
/// The focus group the given view belongs to or an empty handle if the given view.
/// 3
public View GetFocusGroup(View view)
{
//to fix memory leak issue, match the handle count with native side.
IntPtr cPtr = Interop.FocusManager.GetFocusGroup(SwigCPtr, View.getCPtr(view));
View ret = this.GetInstanceSafely(cPtr);
return ret;
}
///
/// Provides the implementation of a custom focus algorithm interface to allow the application to define the focus logic.
///
/// The user's implementation of ICustomFocusAlgorithm.
/// 3
public void SetCustomAlgorithm(ICustomFocusAlgorithm arg0)
{
if (arg0 != null)
{
_customAlgorithmInterfaceWrapper = new CustomAlgorithmInterfaceWrapper();
_customAlgorithmInterfaceWrapper.SetFocusAlgorithm(arg0);
Interop.NDalic.SetCustomAlgorithm(SwigCPtr, CustomAlgorithmInterface.getCPtr(_customAlgorithmInterfaceWrapper));
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
else
{
Interop.NDalic.SetCustomAlgorithm(SwigCPtr, new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero));
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
}
internal static FocusManager Get()
{
FocusManager ret = new FocusManager(Interop.FocusManager.Get(), true);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
internal void SetFocusGroupLoop(bool enabled)
{
Interop.FocusManager.SetFocusGroupLoop(SwigCPtr, enabled);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
internal bool GetFocusGroupLoop()
{
bool ret = Interop.FocusManager.GetFocusGroupLoop(SwigCPtr);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
internal void SetFocusIndicatorView(View indicator)
{
Interop.FocusManager.SetFocusIndicatorActor(SwigCPtr, View.getCPtr(indicator));
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
}
internal View GetFocusIndicatorView()
{
//to fix memory leak issue, match the handle count with native side.
IntPtr cPtr = Interop.FocusManager.GetFocusIndicatorActor(SwigCPtr);
View ret = this.GetInstanceSafely(cPtr);
return ret;
}
internal PreFocusChangeSignal PreFocusChangeSignal()
{
PreFocusChangeSignal ret = new PreFocusChangeSignal(Interop.FocusManager.PreFocusChangeSignal(SwigCPtr), false);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
internal FocusChangedSignal FocusChangedSignal()
{
FocusChangedSignal ret = new FocusChangedSignal(Interop.FocusManager.FocusChangedSignal(SwigCPtr), false);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
internal FocusGroupChangedSignal FocusGroupChangedSignal()
{
FocusGroupChangedSignal ret = new FocusGroupChangedSignal(Interop.FocusManager.FocusGroupChangedSignal(SwigCPtr), false);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
internal ViewSignal FocusedViewEnterKeySignal()
{
ViewSignal ret = new ViewSignal(Interop.FocusManager.FocusedActorEnterKeySignal(SwigCPtr), false);
if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
return ret;
}
private IntPtr OnPreFocusChange(IntPtr current, IntPtr proposed, View.FocusDirection direction)
{
View view = null;
PreFocusChangeEventArgs e = new PreFocusChangeEventArgs();
if (current != global::System.IntPtr.Zero)
{
e.CurrentView = Registry.GetManagedBaseHandleFromNativePtr(current) as View;
}
if (proposed != global::System.IntPtr.Zero)
{
e.ProposedView = Registry.GetManagedBaseHandleFromNativePtr(proposed) as View;
}
e.Direction = direction;
if (_preFocusChangeEventHandler != null)
{
view = _preFocusChangeEventHandler(this, e);
}
if (view != null)
{
return view.GetPtrfromView();
}
else
{
if (e.ProposedView) return proposed;
else return current;
}
}
private void OnFocusChanged(IntPtr current, IntPtr next)
{
FocusChangedEventArgs e = new FocusChangedEventArgs();
e.CurrentView = Registry.GetManagedBaseHandleFromNativePtr(current) as View;
e.NextView = Registry.GetManagedBaseHandleFromNativePtr(next) as View;
if (_focusChangedEventHandler != null)
{
_focusChangedEventHandler(this, e);
}
}
private void OnFocusGroupChanged(IntPtr current, bool forwardDirection)
{
FocusGroupChangedEventArgs e = new FocusGroupChangedEventArgs();
e.CurrentView = Registry.GetManagedBaseHandleFromNativePtr(current) as View;
e.ForwardDirection = forwardDirection;
if (_focusGroupChangedEventHandler != null)
{
_focusGroupChangedEventHandler(this, e);
}
}
private void OnFocusedViewEnterKey(IntPtr view)
{
FocusedViewActivatedEventArgs e = new FocusedViewActivatedEventArgs();
e.View = Registry.GetManagedBaseHandleFromNativePtr(view) as View;
if (_focusedViewEnterKeyEventHandler != null)
{
_focusedViewEnterKeyEventHandler(this, e);
}
}
///
/// Please do not use! this will be deprecated!
///
/// Please do not use! this will be deprecated!
/// Instead please use OnFocusedViewEnterKey.
[Obsolete("Please do not use! This will be deprecated! Please use FocusManager.OnFocusedViewEnterKey instead!")]
[EditorBrowsable(EditorBrowsableState.Never)]
private void OnFocusedViewEnterKey2(IntPtr view)
{
FocusedViewActivatedEventArgs e = new FocusedViewActivatedEventArgs();
e.View = Registry.GetManagedBaseHandleFromNativePtr(view) as View;
if (_focusedViewEnterKeyEventHandler != null)
{
_focusedViewEnterKeyEventHandler(this, e);
}
}
///
/// Event arguments that passed via the PreFocusChange signal.
///
/// 3
public class PreFocusChangeEventArgs : EventArgs
{
private View _current;
private View _proposed;
private View.FocusDirection _direction;
///
/// The current focus view.
///
/// 3
public View CurrentView
{
get
{
return _current;
}
set
{
_current = value;
}
}
///
/// The proposed view.
///
/// 3
public View ProposedView
{
get
{
return _proposed;
}
set
{
_proposed = value;
}
}
///
/// The focus move direction.
///
/// 3
public View.FocusDirection Direction
{
get
{
return _direction;
}
set
{
_direction = value;
}
}
}
///
/// Event arguments that passed via the FocusChanged signal.
///
/// 3
public class FocusChangedEventArgs : EventArgs
{
private View _current;
private View _next;
///
/// The current focus view.
///
/// 3
public View CurrentView
{
get
{
return _current;
}
set
{
_current = value;
}
}
///
/// The next focus view.
///
/// 3
public View NextView
{
get
{
return _next;
}
set
{
_next = value;
}
}
}
///
/// Event arguments that passed via the FocusGroupChanged signal.
///
/// 3
public class FocusGroupChangedEventArgs : EventArgs
{
private View _current;
private bool _forwardDirection;
///
/// The current focus view.
///
/// 3
public View CurrentView
{
get
{
return _current;
}
set
{
_current = value;
}
}
///
/// The forward direction.
///
/// 3
public bool ForwardDirection
{
get
{
return _forwardDirection;
}
set
{
_forwardDirection = value;
}
}
}
///
/// Event arguments that passed via the FocusedViewEnterKey signal.
///
/// 3
public class FocusedViewActivatedEventArgs : EventArgs
{
private View _view;
///
/// View.
///
/// 3
public View View
{
get
{
return _view;
}
set
{
_view = value;
}
}
}
///
/// Please do not use! this will be deprecated
///
/// 3
/// Please do not use! this will be deprecated.
/// Instead please use FocusedViewActivatedEventArgs.
[Obsolete("Please do not use! This will be deprecated! Please use FocusedViewActivatedEventArgs instead! " +
"Like: " +
"FocusManager.Instance.FocusedViewActivated = OnFocusedViewActivated; " +
"private void OnFocusedViewActivated(object source, FocusManager.FocusedViewActivatedEventArgs arg)" +
"{...}")]
[EditorBrowsable(EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public class FocusedViewEnterKeyEventArgs : EventArgs
{
private View _view;
///
/// View.
///
/// 3
public View View
{
get
{
return _view;
}
set
{
_view = value;
}
}
}
private class CustomAlgorithmInterfaceWrapper : CustomAlgorithmInterface
{
private FocusManager.ICustomFocusAlgorithm _customFocusAlgorithm;
public CustomAlgorithmInterfaceWrapper()
{
}
public void SetFocusAlgorithm(FocusManager.ICustomFocusAlgorithm customFocusAlgorithm)
{
_customFocusAlgorithm = customFocusAlgorithm;
}
public override View GetNextFocusableView(View current, View proposed, View.FocusDirection direction)
{
if (_customFocusAlgorithm == null)
{
Tizen.Log.Error("NUI", $"[ERROR] User defined ICustomFocusAlgorithm interface class becomes unreachable. Null will be proposed for next focusing!");
return null;
}
return _customFocusAlgorithm.GetNextFocusableView(current, proposed, direction);
}
}
}
}