Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved tab and directional navigation #200

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

> - Breaking Changes:
> - Features:
> - Introduced a new BringIntoView method overload in NodifyEditor that accepts an ItemContainer
> - Improved tab and directional navigation, ensuring that focused containers are automatically brought into view
> - Added BringIntoViewEdgeOffset to ItemContainer to control the viewport edge offset when bringing the container into view
> - Bugfixes:

#### **Version 7.0.0**
Expand Down
3 changes: 3 additions & 0 deletions Examples/Nodify.Calculator/OperationsMenuView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
mc:Ignorable="d"
MinWidth="250"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabNavigation="Once"
d:DesignHeight="400"
d:DesignWidth="250"
d:DataContext="{d:DesignInstance local:OperationsMenuViewModel}">
Expand All @@ -31,6 +33,7 @@
</Grid.RowDefinitions>

<ItemsControl Grid.Row="1"
IsTabStop="False"
ItemsSource="{Binding AvailableOperations}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OperationInfoViewModel}">
Expand Down
14 changes: 14 additions & 0 deletions Examples/Nodify.Playground/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Nodify.xaml" />
<ResourceDictionary>
<Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle StrokeThickness="2"
RadiusX="3"
RadiusY="3"
Stroke="{DynamicResource ItemContainer.SelectedBrush}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml" />
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Nodify.xaml" />
<ResourceDictionary Source="pack://application:,,,/Nodify.Playground;component/Themes/Nodify.xaml" />
Expand Down
11 changes: 9 additions & 2 deletions Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@
</nodify:Node.ContentTemplate>
<nodify:Node.HeaderTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl ItemsSource="{Binding}"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
<nodify:NodeInput Orientation="Vertical" />
Expand All @@ -548,7 +549,8 @@
</nodify:Node.HeaderTemplate>
<nodify:Node.FooterTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl ItemsSource="{Binding}"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
<nodify:NodeOutput Orientation="Vertical" />
Expand Down Expand Up @@ -592,6 +594,11 @@
<Setter Property="Panel.ZIndex"
Value="1" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin"
Value="True">
<Setter Property="Panel.ZIndex"
Value="1" />
</Trigger>
</Style.Triggers>
</Style>
</nodify:NodifyEditor.ItemContainerStyle>
Expand Down
3 changes: 3 additions & 0 deletions Nodify/Containers/DecoratorContainer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Nodify
{
Expand Down Expand Up @@ -65,6 +66,8 @@ protected void OnLocationChanged()
static DecoratorContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DecoratorContainer), new FrameworkPropertyMetadata(typeof(DecoratorContainer)));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(DecoratorContainer), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(DecoratorContainer), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
}

/// <inheritdoc />
Expand Down
18 changes: 17 additions & 1 deletion Nodify/Containers/ItemContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyCh
/// <remarks>Has no effect if the container has a context menu.</remarks>
public static bool PreserveSelectionOnRightClick { get; set; }

/// <summary>
/// Gets or sets the default viewport edge offset applied when bringing an item into view as a result of keyboard focus.
/// </summary>
public static double BringIntoViewEdgeOffset { get; set; } = 32d;

/// <summary>
/// The <see cref="NodifyEditor"/> that owns this <see cref="ItemContainer"/>.
/// </summary>
Expand Down Expand Up @@ -268,6 +273,8 @@ static ItemContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemContainer), new FrameworkPropertyMetadata(typeof(ItemContainer)));
FocusableProperty.OverrideMetadata(typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.True));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(ItemContainer), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(ItemContainer), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
}

/// <summary>
Expand Down Expand Up @@ -358,6 +365,15 @@ public void Select(SelectionType type)
}
}

protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
// Only bring the item into view when navigated using the keyboard.
if (InputManager.Current.MostRecentInputDevice is KeyboardDevice)
{
Editor.BringIntoView(this, BringIntoViewEdgeOffset);
}
}

#region Gesture Handling

protected InputProcessor InputProcessor { get; } = new InputProcessor();
Expand All @@ -370,7 +386,7 @@ protected override void OnMouseDown(MouseButtonEventArgs e)
protected override void OnMouseUp(MouseButtonEventArgs e)
{
InputProcessor.ProcessEvent(e);

// Release the mouse capture if all the mouse buttons are released and there's no interaction in progress
if (!InputProcessor.RequiresInputCapture && IsMouseCaptured && e.RightButton == MouseButtonState.Released && e.LeftButton == MouseButtonState.Released && e.MiddleButton == MouseButtonState.Released)
{
Expand Down
46 changes: 46 additions & 0 deletions Nodify/Editor/NodifyEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,52 @@ public void BringIntoView(Point point, bool animated = true, Action? onFinish =
public new void BringIntoView(Rect area)
=> BringIntoView(new Point(area.X + area.Width / 2, area.Y + area.Height / 2));

/// <summary>
/// Ensures the specified item container is fully visible within the viewport, optionally with padding around the edges.
/// </summary>
/// <param name="container">The item container to bring into view.</param>
/// <param name="offsetFromEdge">The padding to apply around the container</param>
public void BringIntoView(ItemContainer container, double offsetFromEdge = 32d)
{
var viewport = new Rect(ViewportLocation, ViewportSize);
var containerBounds = new Rect(container.Location, container.RenderSize);

containerBounds.Inflate(offsetFromEdge, offsetFromEdge);

if (!viewport.Contains(containerBounds))
{
if (viewport.IntersectsWith(containerBounds))
{
double newX = viewport.X;
double newY = viewport.Y;

if (containerBounds.Left < viewport.Left)
{
newX = containerBounds.Left;
}
else if (containerBounds.Right > viewport.Right)
{
newX = containerBounds.Right - viewport.Width;
}

if (containerBounds.Top < viewport.Top)
{
newY = containerBounds.Top;
}
else if (containerBounds.Bottom > viewport.Bottom)
{
newY = containerBounds.Bottom - viewport.Height;
}

BringIntoView(new Point(newX, newY) + new Vector(viewport.Width / 2, viewport.Height / 2));
}
else
{
BringIntoView(containerBounds);
}
}
}

/// <summary>
/// Scales the viewport to fit the specified <paramref name="area"/> or all the <see cref="ItemContainer"/>s if that's possible.
/// </summary>
Expand Down
2 changes: 0 additions & 2 deletions Nodify/Themes/Styles/DecoratorContainer.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
<local:UnscaleTransformConverter x:Key="UnscaleTransformConverter" />

<Style TargetType="{x:Type local:DecoratorContainer}">
<Setter Property="KeyboardNavigation.IsTabStop"
Value="False" />
<Setter Property="RenderTransform"
Value="{Binding ViewportTransform, RelativeSource={RelativeSource AncestorType=local:NodifyEditor}, Converter={StaticResource UnscaleTransformConverter}}" />
<Setter Property="Template">
Expand Down
2 changes: 2 additions & 0 deletions Nodify/Themes/Styles/GroupingNode.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
Value="30" />
<Setter Property="MinWidth"
Value="150" />
<Setter Property="KeyboardNavigation.IsTabStop"
Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GroupingNode}">
Expand Down
2 changes: 0 additions & 2 deletions Nodify/Themes/Styles/ItemContainer.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
Value="Orange" />
<Setter Property="HighlightBrush"
Value="{StaticResource ItemContainer.HighlightBrush}" />
<Setter Property="KeyboardNavigation.IsTabStop"
Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ItemContainer}">
Expand Down
2 changes: 2 additions & 0 deletions Nodify/Themes/Styles/KnotNode.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
Value="15 5" />
<Setter Property="Cursor"
Value="SizeAll" />
<Setter Property="KeyboardNavigation.IsTabStop"
Value="False" />
<Setter Property="ContentTemplate"
Value="{StaticResource DefaultConnectorTemplate}" />
<Setter Property="Template">
Expand Down
8 changes: 6 additions & 2 deletions Nodify/Themes/Styles/Node.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
Value="Center" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="KeyboardNavigation.IsTabStop"
Value="False" />
<Setter Property="InputConnectorTemplate"
Value="{StaticResource DefaultInputConnectorTemplate}" />
<Setter Property="OutputConnectorTemplate"
Expand Down Expand Up @@ -99,7 +101,8 @@
ItemsSource="{TemplateBinding Input}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
ItemTemplate="{TemplateBinding InputConnectorTemplate}"
Focusable="False" />
Focusable="False"
IsTabStop="False" />

<!--Content-->
<Border Grid.Column="1"
Expand All @@ -116,7 +119,8 @@
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalContentAlignment="Right"
Grid.Column="2"
Focusable="False" />
Focusable="False"
IsTabStop="False" />
</Grid>
</Border>

Expand Down
2 changes: 2 additions & 0 deletions Nodify/Themes/Styles/NodifyEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
<Canvas RenderTransform="{TemplateBinding ViewportTransform}">
<local:NodifyCanvas x:Name="PART_ItemsHost"
IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabNavigation="Cycle"
Extent="{Binding ItemsExtent, Mode=OneWayToSource, RelativeSource={RelativeSource TemplatedParent}}" />

<local:ConnectionsMultiSelector x:Name="PART_ConnectionsHost"
Expand Down
2 changes: 2 additions & 0 deletions Nodify/Themes/Styles/StateNode.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
Value="#D6D3D6" />
<Setter Property="CornerRadius"
Value="3" />
<Setter Property="KeyboardNavigation.IsTabStop"
Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:StateNode}">
Expand Down
Loading