diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b0c67f..7eb77274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,27 @@ > - Breaking Changes: > - Added ProcessHandledEvents to IInputHandler and removed it from InputProcessor +> - Renamed EditorGestures.Editor.ResetViewportLocation to EditorGestures.Editor.ResetViewport > - Features: +> - Introduced a new BringIntoView method overload in NodifyEditor that accepts an offset from the viewport edges +> - Added BringIntoViewEdgeOffset to NodifyEditor to control the viewport edge offset when bringing the focused element into view +> - Added ResetViewport to NodifyEditor to reset the viewport's location and zoom +> - Improved tab and directional navigation, ensuring that focused elements are automatically brought into view +> - Added keyboard navigation layers for nodes, connections and decorators; restricting keyboard navigation to the active layer +> - Added ActiveNavigationLayer, ActivateNextNavigationLayer, ActivatePreviousNavigationLayer, RegisterNavigationLayer, RemoveNavigationLayer and ActivateNavigationLayer to NodifyEditor for keyboard layers management +> - Added KeyboardNavigationLayer property to NodifyEditor that allows navigating through the ItemContainers +> - Added AutoRegisterConnectionsLayer, AutoRegisterDecoratorsLayer, AutoFocusFirstElement, AutoPanOnNodeFocus, PanViewportOnKeyboardDrag and MinimumNavigationStepSize to NodifyEditor +> - Added EditorGestures.Editor.Keyboard for keyboard navigation gestures +> - Added FindNextFocusTarget, OnElementFocused and OnKeyboardNavigationLayerActivated virtual methods to NodifyEditor +> - Added new gestures for keyboard navigation available in EditorGestures.Editor.Keyboard +> - Added ToggleContentSelection to GroupingNode and its corresponding gesture to toggle the selection of nodes inside the group +> - Added ZoomIn, ZoomOut and ResetViewport methods to the Minimap control +> - Added ZoomIn, ZoomOut, ResetViewport and Pan gestures to EditorGestures.Minimap +> - Added NavigationStepSize static property to Minimap +> - Added Unbind to all gestures inside EditorGestures +> - Added the KeyComboGesture that requires a trigger key to be held down before pressing a combo key +> - Added FocusVisualPen and FocusVisualPadding dependency properties to BaseConnection +> - Added default focus visuals for base editor controls that can be included by referencing the FocusVisual.xaml file > - Bugfixes: #### **Version 7.0.4** diff --git a/Examples/Nodify.Calculator/App.xaml b/Examples/Nodify.Calculator/App.xaml index 2c6b2108..083121d7 100644 --- a/Examples/Nodify.Calculator/App.xaml +++ b/Examples/Nodify.Calculator/App.xaml @@ -1,13 +1,43 @@  + + + + + + + + + + + + DodgerBlue + diff --git a/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml b/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml index eb514c82..76c97759 100644 --- a/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml +++ b/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml @@ -175,6 +175,12 @@ Value="{Binding SelectableConnections, Source={x:Static local:EditorSettings.Instance}}" /> + + + @@ -531,7 +537,8 @@ - + @@ -548,7 +555,8 @@ - + diff --git a/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs b/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs index 6867bfe8..e25e9ae9 100644 --- a/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs +++ b/Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs @@ -11,6 +11,8 @@ public partial class NodifyEditorView : UserControl public NodifyEditorView() { InitializeComponent(); + + EditorInstance.ActiveNavigationLayerChanged += DisplayActiveNavigationLayer; } static NodifyEditorView() @@ -23,5 +25,27 @@ private void Minimap_Zoom(object sender, ZoomEventArgs e) { EditorInstance.ZoomAtPosition(e.Zoom, e.Location); } + + private void DisplayActiveNavigationLayer(KeyboardNavigationLayerId layerId) + { + var editorVm = (NodifyEditorViewModel)EditorInstance.DataContext; + + if (layerId == KeyboardNavigationLayerId.Nodes) + { + editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Nodes); + } + else if (layerId == KeyboardNavigationLayerId.Connections) + { + editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Connections); + } + else if (layerId == KeyboardNavigationLayerId.Decorators) + { + editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Decorators); + } + else + { + editorVm.KeyboardNavigationLayer = "Custom"; + } + } } } diff --git a/Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs b/Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs index d38a5af9..dd44ab28 100644 --- a/Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs +++ b/Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs @@ -102,6 +102,13 @@ public NodeViewModel? SelectedNode public GraphSchema Schema { get; } + private string? _keyboardNavigationLayer; + public string? KeyboardNavigationLayer + { + get => _keyboardNavigationLayer; + set => SetProperty(ref _keyboardNavigationLayer, value); + } + public ICommand DeleteSelectionCommand { get; } public ICommand DisconnectConnectorCommand { get; } public ICommand CreateConnectionCommand { get; } diff --git a/Examples/Nodify.Playground/EditorSettings.cs b/Examples/Nodify.Playground/EditorSettings.cs index aac5d31b..7bd89785 100644 --- a/Examples/Nodify.Playground/EditorSettings.cs +++ b/Examples/Nodify.Playground/EditorSettings.cs @@ -193,6 +193,18 @@ private EditorSettings() () => Instance.ConnectionTargetOffset, val => Instance.ConnectionTargetOffset = val, "Connection target offset: "), + new ProxySettingViewModel( + () => Instance.ConnectionStrokeThickness, + val => Instance.ConnectionStrokeThickness = val, + "Connection stroke thickness: "), + new ProxySettingViewModel( + () => Instance.ConnectionOutlineThickness, + val => Instance.ConnectionOutlineThickness = val, + "Connection outline thickness: "), + new ProxySettingViewModel( + () => Instance.ConnectionFocusVisualPadding, + val => Instance.ConnectionFocusVisualPadding = val, + "Connection focus visual padding: "), new ProxySettingViewModel( () => Instance.DisplayConnectionsOnTop, val => Instance.DisplayConnectionsOnTop = val, @@ -614,6 +626,27 @@ public PointEditor ConnectionTargetOffset set => SetProperty(ref _connectionTargetOffset, value); } + private double _connectionStrokeThickness = 3; + public double ConnectionStrokeThickness + { + get => _connectionStrokeThickness; + set => SetProperty(ref _connectionStrokeThickness, value); + } + + private double _connectionOutlineThickness = 5; + public double ConnectionOutlineThickness + { + get => _connectionOutlineThickness; + set => SetProperty(ref _connectionOutlineThickness, value); + } + + private double _connectionFocusVisualPadding = 1; + public double ConnectionFocusVisualPadding + { + get => _connectionFocusVisualPadding; + set => SetProperty(ref _connectionFocusVisualPadding, value); + } + private uint _directionalArrowsCount = 3; public uint DirectionalArrowsCount { diff --git a/Examples/Nodify.Playground/MainWindow.xaml b/Examples/Nodify.Playground/MainWindow.xaml index 67812655..9c74ff0b 100644 --- a/Examples/Nodify.Playground/MainWindow.xaml +++ b/Examples/Nodify.Playground/MainWindow.xaml @@ -14,6 +14,7 @@ + @@ -219,6 +220,22 @@ + + + + + + + + - + - - + + - + - + - + - - + + - + - - + + + + - + - - - - - - - - + - - - - - - - - + + + - - - - - - - - + - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + - - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Nodify.Shapes/App.xaml.cs b/Examples/Nodify.Shapes/App.xaml.cs index bff9a081..5b0e8d6b 100644 --- a/Examples/Nodify.Shapes/App.xaml.cs +++ b/Examples/Nodify.Shapes/App.xaml.cs @@ -15,6 +15,7 @@ public App() NodifyEditor.EnableCuttingLinePreview = true; EditorGestures.Mappings.Connection.Disconnect.Value = new AnyGesture(new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt), new Interactivity.MouseGesture(MouseAction.RightClick)); + EditorGestures.Mappings.Editor.Pan.Value = new AnyGesture(EditorGestures.Mappings.Editor.Pan.Value, new Interactivity.MouseGesture(MouseAction.LeftClick, Key.Space)); } } } diff --git a/Examples/Nodify.Shapes/Canvas/CanvasView.xaml b/Examples/Nodify.Shapes/Canvas/CanvasView.xaml index 7c5186bd..60b0b75a 100644 --- a/Examples/Nodify.Shapes/Canvas/CanvasView.xaml +++ b/Examples/Nodify.Shapes/Canvas/CanvasView.xaml @@ -70,6 +70,7 @@ + GridCellSize="5" + Focusable="False"> @@ -378,6 +383,7 @@ MinWidth="250" MinHeight="150" BorderBrush="{x:Null}" + Focusable="False" Margin="20"> - + + SelectedValue="{Binding CanvasToolbar.SelectedTool}" + KeyboardNavigation.ControlTabNavigation="None"> - + - + - + diff --git a/Examples/Nodify.Shapes/MainWindow.xaml.cs b/Examples/Nodify.Shapes/MainWindow.xaml.cs index 43014701..c126ba7b 100644 --- a/Examples/Nodify.Shapes/MainWindow.xaml.cs +++ b/Examples/Nodify.Shapes/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using System.Windows; +using System.Windows.Input; namespace Nodify.Shapes { @@ -10,6 +11,16 @@ public partial class MainWindow : Window public MainWindow() { InitializeComponent(); + + EventManager.RegisterClassHandler( + typeof(UIElement), + Keyboard.PreviewGotKeyboardFocusEvent, + (KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus); + } + + private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + Title = e.NewFocus.ToString(); } } } \ No newline at end of file diff --git a/Examples/Nodify.Shared/Controls/EditableTextBlock.cs b/Examples/Nodify.Shared/Controls/EditableTextBlock.cs index 4a59dadc..8998152b 100644 --- a/Examples/Nodify.Shared/Controls/EditableTextBlock.cs +++ b/Examples/Nodify.Shared/Controls/EditableTextBlock.cs @@ -99,6 +99,13 @@ public override void OnApplyTemplate() { base.OnApplyTemplate(); + if (TextBox != null) + { + TextBox.LostFocus -= OnLostFocus; + TextBox.LostKeyboardFocus -= OnLostFocus; + TextBox.IsVisibleChanged -= OnTextBoxVisiblityChanged; + } + TextBox = GetTemplateChild(ElementTextBox) as TextBox; if (TextBox != null) @@ -163,7 +170,7 @@ protected override void OnKeyDown(KeyEventArgs e) IsEditing = false; } - if(e.Key == Key.Enter && IsFocused && !IsEditing) + if (e.Key == Key.Enter && IsFocused && !IsEditing) { IsEditing = true; } diff --git a/Examples/Nodify.Shared/ThemeManager.cs b/Examples/Nodify.Shared/ThemeManager.cs index fd1cbcc6..ab84be50 100644 --- a/Examples/Nodify.Shared/ThemeManager.cs +++ b/Examples/Nodify.Shared/ThemeManager.cs @@ -115,7 +115,7 @@ public static void SetTheme(string themeName) { foreach (var res in resources) { - Application.Current.Resources.MergedDictionaries.Add(res); + Application.Current.Resources.MergedDictionaries.Insert(0, res); } // Unload current theme diff --git a/Examples/Nodify.Shared/Themes/Controls.xaml b/Examples/Nodify.Shared/Themes/Controls.xaml index e6a15285..ffe5f4a6 100644 --- a/Examples/Nodify.Shared/Themes/Controls.xaml +++ b/Examples/Nodify.Shared/Themes/Controls.xaml @@ -15,6 +15,8 @@ Value="1" /> + @@ -91,6 +93,8 @@ Value="4 2" /> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Nodify/Themes/Styles/DecoratorContainer.xaml b/Nodify/Themes/Styles/DecoratorContainer.xaml index 6ca87de7..78390c7d 100644 --- a/Nodify/Themes/Styles/DecoratorContainer.xaml +++ b/Nodify/Themes/Styles/DecoratorContainer.xaml @@ -5,8 +5,6 @@