using System; using System.Collections.Generic; using Unity.UIWidgets.async; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; using Unity.UIWidgets.rendering; using Unity.UIWidgets.scheduler; using Unity.UIWidgets.ui; namespace Unity.UIWidgets.widgets { public delegate UiWidgetAction ActionFactory(); public delegate void OnInvokeCallback(FocusNode node, Intent tag); public class Intent : Diagnosticable { public Intent(LocalKey key) : base() { D.assert(key != null); this.key = key; } public static readonly Intent doNothing = new Intent(DoNothingAction.key); public readonly LocalKey key; public bool isEnabled(BuildContext context) => true; public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("key", key)); } } public abstract class UiWidgetAction : Diagnosticable { public UiWidgetAction(LocalKey intentKey) { D.assert(intentKey != null); this.intentKey = intentKey; } public readonly LocalKey intentKey; public abstract void invoke(FocusNode node, Intent intent); public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("intentKey", intentKey)); } } public class CallbackAction : UiWidgetAction { public CallbackAction(LocalKey intentKey = null, OnInvokeCallback onInvoke = null) : base(intentKey: intentKey) { D.assert(onInvoke != null); this.onInvoke = onInvoke; } protected readonly OnInvokeCallback onInvoke; public override void invoke(FocusNode node, Intent intent) { onInvoke.Invoke(node, intent); } } public class ActionDispatcher : Diagnosticable { public ActionDispatcher() { } public bool invokeAction(UiWidgetAction action, Intent intent, FocusNode focusNode = null) { D.assert(action != null); D.assert(intent != null); focusNode = focusNode ?? FocusManagerUtils.primaryFocus; if (action != null && intent.isEnabled(focusNode.context)) { action.invoke(focusNode, intent); return true; } return false; } } public class Actions : InheritedWidget { public Actions( Key key = null, ActionDispatcher dispatcher = null, Dictionary actions = null, Widget child = null ) : base(key: key, child: child) { D.assert(actions != null); this.dispatcher = dispatcher; this.actions = actions; } public readonly ActionDispatcher dispatcher; public readonly Dictionary actions; public static ActionDispatcher _findDispatcher(Element element) { D.assert(element.widget is Actions); Actions actions = element.widget as Actions; ActionDispatcher dispatcher = actions.dispatcher; if (dispatcher == null) { bool visitAncestorElement(Element visitedElement) { if (!(visitedElement.widget is Actions)) { return true; } Actions actions1 = visitedElement.widget as Actions; if (actions1.dispatcher == null) { return true; } dispatcher = actions1.dispatcher; return false; } element.visitAncestorElements(visitAncestorElement); } return dispatcher ?? new ActionDispatcher(); } public static ActionDispatcher of(BuildContext context, bool nullOk = false) { D.assert(context != null); InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType(); Actions inherited = context.dependOnInheritedElement(inheritedElement) as Actions; D.assert(() => { if (nullOk) { return true; } if (inherited == null) { throw new UIWidgetsError("Unable to find an $Actions widget in the context.\n" + "Actions.of() was called with a context that does not contain an " + $"{typeof(Actions)} widget.\n" + $"No {typeof(Actions)} ancestor could be found starting from the context that " + "was passed to {Actions.of()}. This can happen if the context comes " + "from a widget above those widgets.\n" + "The context used was:\n" + $"{context}"); } return true; }); return inherited?.dispatcher ?? _findDispatcher(inheritedElement); } public static bool invoke( BuildContext context, Intent intent, FocusNode focusNode = null, bool nullOk = false ) { D.assert(context != null); D.assert(intent != null); Element actionsElement = null; UiWidgetAction action = null; bool visitAncestorElement(Element element) { if (!(element.widget is Actions)) { return true; } actionsElement = element; Actions actions = element.widget as Actions; action = actions.actions.getOrDefault(intent.key)?.Invoke(); return action == null; } context.visitAncestorElements(visitAncestorElement); D.assert(() => { if (nullOk) { return true; } if (actionsElement == null) { throw new UIWidgetsError($"Unable to find a {typeof(Actions)} widget in the context.\n" + "Actions.invoke() was called with a context that does not contain an " + $"{typeof(Actions)} widget.\n" + $"No {typeof(Actions)} ancestor could be found starting from the context that " + "was passed to Actions.invoke(). This can happen if the context comes " + "from a widget above those widgets.\n" + "The context used was:\n" + $" {context}"); } if (action == null) { throw new UIWidgetsError( $"Unable to find an action for an intent in the {typeof(Actions)} widget in the context.\n" + "Actions.invoke() was called on an {typeof(Actions)} widget that doesn't " + "contain a mapping for the given intent.\n" + "The context used was:\n" + $" {context}\n" + "The intent requested was:\n" + $" {intent}"); } return true; }); if (action == null) { return false; } return _findDispatcher(actionsElement).invokeAction(action, intent, focusNode: focusNode); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("dispatcher", dispatcher)); properties.add(new DiagnosticsProperty>("actions", actions)); } public override bool updateShouldNotify(InheritedWidget oldWidget) { oldWidget = (Actions) oldWidget; var dispatcherBool = ((Actions) oldWidget).dispatcher != dispatcher; bool actionBool = ((Actions) oldWidget).actions == actions; foreach (var actionsKey in ((Actions) oldWidget).actions.Keys) { if (!actions.ContainsKey(actionsKey) || actions[actionsKey] != ((Actions) oldWidget).actions[actionsKey]) { actionBool = false; } } return dispatcherBool || actionBool; } } public class FocusableActionDetector : StatefulWidget { public FocusableActionDetector( Key key = null, bool enabled = true, FocusNode focusNode = null, bool autofocus = false, Dictionary shortcuts = null, Dictionary actions = null, ValueChanged onShowFocusHighlight = null, ValueChanged onShowHoverHighlight = null, ValueChanged onFocusChange = null, Widget child = null ) : base(key: key) { D.assert(enabled != null); D.assert(autofocus != null); D.assert(child != null); this.enabled = enabled; this.focusNode = focusNode; this.autofocus = autofocus; this.shortcuts = shortcuts; this.actions = actions; this.onShowFocusHighlight = onShowFocusHighlight; this.onShowHoverHighlight = onShowHoverHighlight; this.onFocusChange = onFocusChange; this.child = child; } public readonly bool enabled; public readonly FocusNode focusNode; public readonly bool autofocus; public readonly Dictionary actions; public readonly Dictionary shortcuts; public readonly ValueChanged onShowFocusHighlight; public readonly ValueChanged onShowHoverHighlight; public readonly ValueChanged onFocusChange; public readonly Widget child; public override State createState() { return new _FocusableActionDetectorState(); } } class _FocusableActionDetectorState : State { public override void initState() { base.initState(); SchedulerBinding.instance.addPostFrameCallback(((TimeSpan timespan) => { _updateHighlightMode(FocusManager.instance.highlightMode); })); FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange); } public override void dispose() { FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange); base.dispose(); } bool _canShowHighlight = false; void _updateHighlightMode(FocusHighlightMode mode) { _mayTriggerCallback(task: () =>{ switch (FocusManager.instance.highlightMode) { case FocusHighlightMode.touch: _canShowHighlight = false; break; case FocusHighlightMode.traditional: _canShowHighlight = true; break; } }); } void _handleFocusHighlightModeChange(FocusHighlightMode mode) { if (!mounted) { return; } _updateHighlightMode(mode); } bool _hovering = false; void _handleMouseEnter(PointerEnterEvent Event) { D.assert(widget.onShowHoverHighlight != null); if (!_hovering) { _mayTriggerCallback(task: ()=> { _hovering = true; }); } } void _handleMouseExit(PointerExitEvent Event) { D.assert(widget.onShowHoverHighlight != null); if (_hovering) { _mayTriggerCallback(task: ()=>{ _hovering = false; }); } } bool _focused = false; void _handleFocusChange(bool focused) { if (_focused != focused) { _mayTriggerCallback(task: () =>{ _focused = focused; }); widget.onFocusChange?.Invoke(_focused); } } void _mayTriggerCallback(VoidCallback task = null, FocusableActionDetector oldWidget = null) { bool shouldShowHoverHighlight(FocusableActionDetector target) { return _hovering && target.enabled && _canShowHighlight; } bool shouldShowFocusHighlight(FocusableActionDetector target) { return _focused && target.enabled && _canShowHighlight; } D.assert(SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks); FocusableActionDetector oldTarget = oldWidget ?? widget; bool didShowHoverHighlight = shouldShowHoverHighlight(oldTarget); bool didShowFocusHighlight = shouldShowFocusHighlight(oldTarget); if (task != null) task(); bool doShowHoverHighlight = shouldShowHoverHighlight(widget); bool doShowFocusHighlight = shouldShowFocusHighlight(widget); if (didShowFocusHighlight != doShowFocusHighlight) widget.onShowFocusHighlight?.Invoke(doShowFocusHighlight); if (didShowHoverHighlight != doShowHoverHighlight) widget.onShowHoverHighlight?.Invoke(doShowHoverHighlight); } public override void didUpdateWidget(StatefulWidget oldWidget) { oldWidget =(FocusableActionDetector) oldWidget; base.didUpdateWidget(oldWidget); if (widget.enabled != ((FocusableActionDetector)oldWidget).enabled) { SchedulerBinding.instance.addPostFrameCallback((TimeSpan timespan)=> { _mayTriggerCallback(oldWidget: (FocusableActionDetector)oldWidget); }); } } public override Widget build(BuildContext context) { Widget child = new MouseRegion( onEnter: _handleMouseEnter, onExit: _handleMouseExit, child: new Focus( focusNode: widget.focusNode, autofocus: widget.autofocus, canRequestFocus: widget.enabled, onFocusChange: _handleFocusChange, child: widget.child ) ); if (widget.enabled && widget.actions != null && widget.actions.isNotEmpty()) { child = new Actions(actions: widget.actions, child: child); } if (widget.enabled && widget.shortcuts != null && widget.shortcuts.isNotEmpty()) { child = new Shortcuts(shortcuts: widget.shortcuts, child: child); } return child; } } public class DoNothingAction : UiWidgetAction { public DoNothingAction() : base(key) { } public readonly static LocalKey key = new ValueKey(typeof(DoNothingAction)); public override void invoke(FocusNode node, Intent intent) { } } public abstract class ActivateAction : UiWidgetAction { public ActivateAction() : base(key) { } public readonly static LocalKey key = new ValueKey(typeof(ActivateAction)); } public abstract class SelectAction : UiWidgetAction { public SelectAction() : base(key) { } public readonly static LocalKey key = new ValueKey(typeof(SelectAction)); } }