diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 2018763e733..25f6582a0b6 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -282,6 +282,8 @@ + + @@ -329,7 +331,6 @@ - @@ -399,7 +400,6 @@ - @@ -647,6 +647,9 @@ + + + @@ -862,9 +865,6 @@ ScrollHeaderPage.xaml - - GridSplitterPage.xaml - FadeHeaderBehaviorPage.xaml @@ -1301,10 +1301,6 @@ MSBuild:Compile Designer - - Designer - MSBuild:Compile - MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml b/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml index 57f54f29aa5..887f76df9cd 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml @@ -220,8 +220,8 @@ Width="11" Background="Transparent" HorizontalAlignment="Left" - GripperForeground="{ThemeResource Brush-Alt}" - ParentLevel="1" /> + ParentLevel="1"> + + Foreground="{ThemeResource Brush-Alt}"> @@ -45,16 +44,12 @@ Grid.Row="1" Background="{ThemeResource Brush-Grey-04}" Height="11" HorizontalAlignment="Stretch"> - + Foreground="{ThemeResource Brush-Alt}" /> - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitterPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitterPage.xaml deleted file mode 100644 index 268de9613bb..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitterPage.xaml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitterPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitterPage.xaml.cs deleted file mode 100644 index 3afb58d856e..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitterPage.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Toolkit.Uwp.SampleApp.Models; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - public sealed partial class GridSplitterPage : Page - { - public GridSplitterPage() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/ContentSizer.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/ContentSizer.bind new file mode 100644 index 00000000000..2e88e813af1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/ContentSizer.bind @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + Side Content + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/ContentSizer.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/ContentSizer.png new file mode 100644 index 00000000000..004cf329744 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/ContentSizer.png differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitter.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/GridSplitter.bind similarity index 88% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitter.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/GridSplitter.bind index 4cec3829383..c4d3cc553d8 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitter.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/GridSplitter.bind @@ -63,12 +63,11 @@ @@ -85,14 +84,6 @@ - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitter.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/GridSplitter.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/GridSplitter/GridSplitter.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/GridSplitter.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/PropertySizer.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/PropertySizer.bind new file mode 100644 index 00000000000..9f8b66e1661 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Sizers/PropertySizer.bind @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml index a4187d65e0a..4bb92e0fa4f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml @@ -50,6 +50,7 @@ + @@ -63,6 +64,8 @@ + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 8cdfcc3a619..aa4eb7f3948 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -175,16 +175,33 @@ "Icon": "/SamplePages/ScrollHeader/ScrollHeader.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/HeaderBehaviors.md" }, + { + "Name": "ContentSizer", + "Subcategory": "Layout", + "About": "ContentSizer is a general sizing control which can manipulate the size of its parent or other elements. Used as a building block for more complex UI systems.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer", + "XamlCodeFile": "/SamplePages/Sizers/ContentSizer.bind", + "Icon": "/SamplePages/Sizers/ContentSizer.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ContentSizer.md" + }, { "Name": "GridSplitter", - "Type": "GridSplitterPage", "Subcategory": "Layout", "About": "GridSplitter represents the control that redistributes space between columns or rows of a Grid control.", - "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter", - "XamlCodeFile": "GridSplitter.bind", - "Icon": "/SamplePages/GridSplitter/GridSplitter.png", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter", + "XamlCodeFile": "/SamplePages/Sizers/GridSplitter.bind", + "Icon": "/SamplePages/Sizers/GridSplitter.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/GridSplitter.md" }, + { + "Name": "PropertySizer", + "Subcategory": "Layout", + "About": "PropertySizer is a specific sizing control which can manipulate any double value or some other element or property.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer", + "XamlCodeFile": "/SamplePages/Sizers/PropertySizer.bind", + "Icon": "/SamplePages/Sizers/ContentSizer.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ContentSizer.md" + }, { "Name": "AttachedDropShadow (Composition)", "Type": "AttachedDropShadowPage", diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Metadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Metadata.cs index cfc9fc1fa0e..8a3828601be 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Metadata.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Metadata.cs @@ -20,14 +20,12 @@ public GridSplitterMetadata() b.AddCustomAttributes(nameof(GridSplitter.Element), new CategoryAttribute(Resources.CategoryCommon)); b.AddCustomAttributes(nameof(GridSplitter.ResizeDirection), new CategoryAttribute(Resources.CategoryCommon)); b.AddCustomAttributes(nameof(GridSplitter.ResizeBehavior), new CategoryAttribute(Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(GridSplitter.GripperForeground), new CategoryAttribute(Resources.CategoryBrush)); b.AddCustomAttributes(nameof(GridSplitter.ParentLevel), new CategoryAttribute(Resources.CategoryCommon)); b.AddCustomAttributes(nameof(GridSplitter.GripperCursor), new CategoryAttribute(Resources.CategoryAppearance)); b.AddCustomAttributes(nameof(GridSplitter.GripperCustomCursorResource), new CategoryAttribute(Resources.CategoryAppearance), new EditorBrowsableAttribute(EditorBrowsableState.Advanced) ); - b.AddCustomAttributes(nameof(GridSplitter.CursorBehavior), new CategoryAttribute(Resources.CategoryCommon)); b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); } ); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Typedata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Typedata.cs index 13c64c02a0e..41bfb550c2f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Typedata.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout.Design/Controls/GridSplitter.Typedata.cs @@ -13,11 +13,9 @@ internal static partial class ControlTypes internal static class GridSplitter { - internal const string CursorBehavior = nameof(CursorBehavior); internal const string Element = nameof(Element); internal const string GripperCursor = nameof(GripperCursor); internal const string GripperCustomCursorResource = nameof(GripperCustomCursorResource); - internal const string GripperForeground = nameof(GripperForeground); internal const string ParentLevel = nameof(ParentLevel); internal const string ResizeBehavior = nameof(ResizeBehavior); internal const string ResizeDirection = nameof(ResizeDirection); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Data.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Data.cs deleted file mode 100644 index 6781e4ba1bf..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Data.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Represents the control that redistributes space between columns or rows of a Grid control. - /// - public partial class GridSplitter - { - /// - /// Enum to indicate whether GridSplitter resizes Columns or Rows - /// - public enum GridResizeDirection - { - /// - /// Determines whether to resize rows or columns based on its Alignment and - /// width compared to height - /// - Auto, - - /// - /// Resize columns when dragging Splitter. - /// - Columns, - - /// - /// Resize rows when dragging Splitter. - /// - Rows - } - - /// - /// Enum to indicate what Columns or Rows the GridSplitter resizes - /// - public enum GridResizeBehavior - { - /// - /// Determine which columns or rows to resize based on its Alignment. - /// - BasedOnAlignment, - - /// - /// Resize the current and next Columns or Rows. - /// - CurrentAndNext, - - /// - /// Resize the previous and current Columns or Rows. - /// - PreviousAndCurrent, - - /// - /// Resize the previous and next Columns or Rows. - /// - PreviousAndNext - } - - /// - /// Enum to indicate the supported gripper cursor types. - /// - public enum GripperCursorType - { - /// - /// Change the cursor based on the splitter direction - /// - Default = -1, - - /// - /// Standard Arrow cursor - /// - Arrow, - - /// - /// Standard Cross cursor - /// - Cross, - - /// - /// Standard Custom cursor - /// - Custom, - - /// - /// Standard Hand cursor - /// - Hand, - - /// - /// Standard Help cursor - /// - Help, - - /// - /// Standard IBeam cursor - /// - IBeam, - - /// - /// Standard SizeAll cursor - /// - SizeAll, - - /// - /// Standard SizeNortheastSouthwest cursor - /// - SizeNortheastSouthwest, - - /// - /// Standard SizeNorthSouth cursor - /// - SizeNorthSouth, - - /// - /// Standard SizeNorthwestSoutheast cursor - /// - SizeNorthwestSoutheast, - - /// - /// Standard SizeWestEast cursor - /// - SizeWestEast, - - /// - /// Standard UniversalNo cursor - /// - UniversalNo, - - /// - /// Standard UpArrow cursor - /// - UpArrow, - - /// - /// Standard Wait cursor - /// - Wait - } - - /// - /// Enum to indicate the behavior of window cursor on grid splitter hover - /// - public enum SplitterCursorBehavior - { - /// - /// Update window cursor on Grid Splitter hover - /// - ChangeOnSplitterHover, - - /// - /// Update window cursor on Grid Splitter Gripper hover - /// - ChangeOnGripperHover - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Events.cs deleted file mode 100644 index a310d1b7d03..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Events.cs +++ /dev/null @@ -1,309 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.System; -using Windows.UI.Core; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Represents the control that redistributes space between columns or rows of a Grid control. - /// - public partial class GridSplitter - { - // Symbols for GripperBar in Segoe MDL2 Assets - private const string GripperBarVertical = "\xE784"; - private const string GripperBarHorizontal = "\xE76F"; - private const string GripperDisplayFont = "Segoe MDL2 Assets"; - - private void GridSplitter_Loaded(object sender, RoutedEventArgs e) - { - _resizeDirection = GetResizeDirection(); - _resizeBehavior = GetResizeBehavior(); - - // Adding Grip to Grid Splitter - if (Element == default(UIElement)) - { - CreateGripperDisplay(); - Element = _gripperDisplay; - } - - if (_hoverWrapper == null) - { - var hoverWrapper = new GripperHoverWrapper( - CursorBehavior == SplitterCursorBehavior.ChangeOnSplitterHover - ? this - : Element, - _resizeDirection, - GripperCursor, - GripperCustomCursorResource); - ManipulationStarted += hoverWrapper.SplitterManipulationStarted; - ManipulationCompleted += hoverWrapper.SplitterManipulationCompleted; - - _hoverWrapper = hoverWrapper; - } - } - - private void CreateGripperDisplay() - { - if (_gripperDisplay == null) - { - _gripperDisplay = new TextBlock - { - FontFamily = new FontFamily(GripperDisplayFont), - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - Foreground = GripperForeground, - Text = _resizeDirection == GridResizeDirection.Columns ? GripperBarVertical : GripperBarHorizontal - }; - _gripperDisplay.SetValue( - Windows.UI.Xaml.Automation.AutomationProperties.AccessibilityViewProperty, - Windows.UI.Xaml.Automation.Peers.AccessibilityView.Raw); - } - } - - /// - protected override void OnKeyDown(KeyRoutedEventArgs e) - { - var step = 1; - var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control); - if (ctrl.HasFlag(CoreVirtualKeyStates.Down)) - { - step = 5; - } - - if (_resizeDirection == GridResizeDirection.Columns) - { - if (e.Key == VirtualKey.Left) - { - HorizontalMove(-step); - } - else if (e.Key == VirtualKey.Right) - { - HorizontalMove(step); - } - else - { - return; - } - - e.Handled = true; - return; - } - - if (_resizeDirection == GridResizeDirection.Rows) - { - if (e.Key == VirtualKey.Up) - { - VerticalMove(-step); - } - else if (e.Key == VirtualKey.Down) - { - VerticalMove(step); - } - else - { - return; - } - - e.Handled = true; - } - - base.OnKeyDown(e); - } - - /// - protected override void OnManipulationStarted(ManipulationStartedRoutedEventArgs e) - { - // saving the previous state - PreviousCursor = Window.Current.CoreWindow.PointerCursor; - _resizeDirection = GetResizeDirection(); - _resizeBehavior = GetResizeBehavior(); - - if (_resizeDirection == GridResizeDirection.Columns) - { - Window.Current.CoreWindow.PointerCursor = ColumnsSplitterCursor; - } - else if (_resizeDirection == GridResizeDirection.Rows) - { - Window.Current.CoreWindow.PointerCursor = RowSplitterCursor; - } - - base.OnManipulationStarted(e); - } - - /// - protected override void OnManipulationCompleted(ManipulationCompletedRoutedEventArgs e) - { - Window.Current.CoreWindow.PointerCursor = PreviousCursor; - - base.OnManipulationCompleted(e); - } - - /// - protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e) - { - var horizontalChange = e.Delta.Translation.X; - var verticalChange = e.Delta.Translation.Y; - - if (this.FlowDirection == FlowDirection.RightToLeft) - { - horizontalChange *= -1; - } - - if (_resizeDirection == GridResizeDirection.Columns) - { - if (HorizontalMove(horizontalChange)) - { - return; - } - } - else if (_resizeDirection == GridResizeDirection.Rows) - { - if (VerticalMove(verticalChange)) - { - return; - } - } - - base.OnManipulationDelta(e); - } - - private bool VerticalMove(double verticalChange) - { - if (CurrentRow == null || SiblingRow == null) - { - return true; - } - - // if current row has fixed height then resize it - if (!IsStarRow(CurrentRow)) - { - // No need to check for the row Min height because it is automatically respected - if (!SetRowHeight(CurrentRow, verticalChange, GridUnitType.Pixel)) - { - return true; - } - } - - // if sibling row has fixed width then resize it - else if (!IsStarRow(SiblingRow)) - { - // Would adding to this column make the current column violate the MinWidth? - if (IsValidRowHeight(CurrentRow, verticalChange) == false) - { - return false; - } - - if (!SetRowHeight(SiblingRow, verticalChange * -1, GridUnitType.Pixel)) - { - return true; - } - } - - // if both row haven't fixed height (auto *) - else - { - // change current row height to the new height with respecting the auto - // change sibling row height to the new height relative to current row - // respect the other star row height by setting it's height to it's actual height with stars - - // We need to validate current and sibling height to not cause any unexpected behavior - if (!IsValidRowHeight(CurrentRow, verticalChange) || - !IsValidRowHeight(SiblingRow, verticalChange * -1)) - { - return true; - } - - foreach (var rowDefinition in Resizable.RowDefinitions) - { - if (rowDefinition == CurrentRow) - { - SetRowHeight(CurrentRow, verticalChange, GridUnitType.Star); - } - else if (rowDefinition == SiblingRow) - { - SetRowHeight(SiblingRow, verticalChange * -1, GridUnitType.Star); - } - else if (IsStarRow(rowDefinition)) - { - rowDefinition.Height = new GridLength(rowDefinition.ActualHeight, GridUnitType.Star); - } - } - } - - return false; - } - - private bool HorizontalMove(double horizontalChange) - { - if (CurrentColumn == null || SiblingColumn == null) - { - return true; - } - - // if current column has fixed width then resize it - if (!IsStarColumn(CurrentColumn)) - { - // No need to check for the Column Min width because it is automatically respected - if (!SetColumnWidth(CurrentColumn, horizontalChange, GridUnitType.Pixel)) - { - return true; - } - } - - // if sibling column has fixed width then resize it - else if (!IsStarColumn(SiblingColumn)) - { - // Would adding to this column make the current column violate the MinWidth? - if (IsValidColumnWidth(CurrentColumn, horizontalChange) == false) - { - return false; - } - - if (!SetColumnWidth(SiblingColumn, horizontalChange * -1, GridUnitType.Pixel)) - { - return true; - } - } - - // if both column haven't fixed width (auto *) - else - { - // change current column width to the new width with respecting the auto - // change sibling column width to the new width relative to current column - // respect the other star column width by setting it's width to it's actual width with stars - - // We need to validate current and sibling width to not cause any unexpected behavior - if (!IsValidColumnWidth(CurrentColumn, horizontalChange) || - !IsValidColumnWidth(SiblingColumn, horizontalChange * -1)) - { - return true; - } - - foreach (var columnDefinition in Resizable.ColumnDefinitions) - { - if (columnDefinition == CurrentColumn) - { - SetColumnWidth(CurrentColumn, horizontalChange, GridUnitType.Star); - } - else if (columnDefinition == SiblingColumn) - { - SetColumnWidth(SiblingColumn, horizontalChange * -1, GridUnitType.Star); - } - else if (IsStarColumn(columnDefinition)) - { - columnDefinition.Width = new GridLength(columnDefinition.ActualWidth, GridUnitType.Star); - } - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Options.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Options.cs deleted file mode 100644 index 5564c0336c7..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Options.cs +++ /dev/null @@ -1,229 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Core; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Represents the control that redistributes space between columns or rows of a Grid control. - /// - public partial class GridSplitter - { - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ElementProperty - = DependencyProperty.Register( - nameof(Element), - typeof(UIElement), - typeof(GridSplitter), - new PropertyMetadata(default(UIElement), OnElementPropertyChanged)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ResizeDirectionProperty - = DependencyProperty.Register( - nameof(ResizeDirection), - typeof(GridResizeDirection), - typeof(GridSplitter), - new PropertyMetadata(GridResizeDirection.Auto)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ResizeBehaviorProperty - = DependencyProperty.Register( - nameof(ResizeBehavior), - typeof(GridResizeBehavior), - typeof(GridSplitter), - new PropertyMetadata(GridResizeBehavior.BasedOnAlignment)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty GripperForegroundProperty - = DependencyProperty.Register( - nameof(GripperForeground), - typeof(Brush), - typeof(GridSplitter), - new PropertyMetadata(default(Brush), OnGripperForegroundPropertyChanged)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ParentLevelProperty - = DependencyProperty.Register( - nameof(ParentLevel), - typeof(int), - typeof(GridSplitter), - new PropertyMetadata(default(int))); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty GripperCursorProperty = - DependencyProperty.RegisterAttached( - nameof(GripperCursor), - typeof(CoreCursorType?), - typeof(GridSplitter), - new PropertyMetadata(GripperCursorType.Default, OnGripperCursorPropertyChanged)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty GripperCustomCursorResourceProperty = - DependencyProperty.RegisterAttached( - nameof(GripperCustomCursorResource), - typeof(uint), - typeof(GridSplitter), - new PropertyMetadata(GripperCustomCursorDefaultResource, GripperCustomCursorResourcePropertyChanged)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty CursorBehaviorProperty = - DependencyProperty.RegisterAttached( - nameof(CursorBehavior), - typeof(SplitterCursorBehavior), - typeof(GridSplitter), - new PropertyMetadata(SplitterCursorBehavior.ChangeOnSplitterHover, CursorBehaviorPropertyChanged)); - - /// - /// Gets or sets the visual content of this Grid Splitter - /// - public UIElement Element - { - get { return (UIElement)GetValue(ElementProperty); } - set { SetValue(ElementProperty, value); } - } - - /// - /// Gets or sets whether the Splitter resizes the Columns, Rows, or Both. - /// - public GridResizeDirection ResizeDirection - { - get { return (GridResizeDirection)GetValue(ResizeDirectionProperty); } - - set { SetValue(ResizeDirectionProperty, value); } - } - - /// - /// Gets or sets which Columns or Rows the Splitter resizes. - /// - public GridResizeBehavior ResizeBehavior - { - get { return (GridResizeBehavior)GetValue(ResizeBehaviorProperty); } - - set { SetValue(ResizeBehaviorProperty, value); } - } - - /// - /// Gets or sets the foreground color of grid splitter grip - /// - public Brush GripperForeground - { - get { return (Brush)GetValue(GripperForegroundProperty); } - - set { SetValue(GripperForegroundProperty, value); } - } - - /// - /// Gets or sets the level of the parent grid to resize - /// - public int ParentLevel - { - get { return (int)GetValue(ParentLevelProperty); } - - set { SetValue(ParentLevelProperty, value); } - } - - /// - /// Gets or sets the gripper Cursor type - /// - public GripperCursorType GripperCursor - { - get { return (GripperCursorType)GetValue(GripperCursorProperty); } - set { SetValue(GripperCursorProperty, value); } - } - - /// - /// Gets or sets the gripper Custom Cursor resource number - /// - public int GripperCustomCursorResource - { - get { return (int)GetValue(GripperCustomCursorResourceProperty); } - set { SetValue(GripperCustomCursorResourceProperty, value); } - } - - /// - /// Gets or sets splitter cursor on hover behavior - /// - public SplitterCursorBehavior CursorBehavior - { - get { return (SplitterCursorBehavior)GetValue(CursorBehaviorProperty); } - set { SetValue(CursorBehaviorProperty, value); } - } - - private static void OnGripperForegroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var gridSplitter = (GridSplitter)d; - - if (gridSplitter._gripperDisplay == null) - { - return; - } - - gridSplitter._gripperDisplay.Foreground = gridSplitter.GripperForeground; - } - - private static void OnGripperCursorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var gridSplitter = (GridSplitter)d; - - if (gridSplitter._hoverWrapper == null) - { - return; - } - - gridSplitter._hoverWrapper.GripperCursor = gridSplitter.GripperCursor; - } - - private static void GripperCustomCursorResourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var gridSplitter = (GridSplitter)d; - - if (gridSplitter._hoverWrapper == null) - { - return; - } - - gridSplitter._hoverWrapper.GripperCustomCursorResource = gridSplitter.GripperCustomCursorResource; - } - - private static void CursorBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var gridSplitter = (GridSplitter)d; - - gridSplitter._hoverWrapper?.UpdateHoverElement(gridSplitter.CursorBehavior == - SplitterCursorBehavior.ChangeOnSplitterHover - ? gridSplitter - : gridSplitter.Element); - } - - private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var gridSplitter = (GridSplitter)d; - - gridSplitter._hoverWrapper?.UpdateHoverElement(gridSplitter.CursorBehavior == - SplitterCursorBehavior.ChangeOnSplitterHover - ? gridSplitter - : gridSplitter.Element); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.xaml deleted file mode 100644 index 451e054d16f..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.xaml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GripperHoverWrapper.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GripperHoverWrapper.cs deleted file mode 100644 index 6b22b204886..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GripperHoverWrapper.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Core; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Input; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - internal class GripperHoverWrapper - { - private readonly GridSplitter.GridResizeDirection _gridSplitterDirection; - - private CoreCursor _splitterPreviousPointer; - private CoreCursor _previousCursor; - private GridSplitter.GripperCursorType _gripperCursor; - private int _gripperCustomCursorResource; - private bool _isDragging; - private UIElement _element; - - internal GridSplitter.GripperCursorType GripperCursor - { - get - { - return _gripperCursor; - } - - set - { - _gripperCursor = value; - } - } - - internal int GripperCustomCursorResource - { - get - { - return _gripperCustomCursorResource; - } - - set - { - _gripperCustomCursorResource = value; - } - } - - /// - /// Initializes a new instance of the class that add cursor change on hover functionality for GridSplitter. - /// - /// UI element to apply cursor change on hover - /// GridSplitter resize direction - /// GridSplitter gripper on hover cursor type - /// GridSplitter gripper custom cursor resource number - internal GripperHoverWrapper(UIElement element, GridSplitter.GridResizeDirection gridSplitterDirection, GridSplitter.GripperCursorType gripperCursor, int gripperCustomCursorResource) - { - _gridSplitterDirection = gridSplitterDirection; - _gripperCursor = gripperCursor; - _gripperCustomCursorResource = gripperCustomCursorResource; - _element = element; - UnhookEvents(); - _element.PointerEntered += Element_PointerEntered; - _element.PointerExited += Element_PointerExited; - } - - internal void UpdateHoverElement(UIElement element) - { - UnhookEvents(); - _element = element; - _element.PointerEntered += Element_PointerEntered; - _element.PointerExited += Element_PointerExited; - } - - private void Element_PointerExited(object sender, PointerRoutedEventArgs e) - { - if (_isDragging) - { - // if dragging don't update the cursor just update the splitter cursor with the last window cursor, - // because the splitter is still using the arrow cursor and will revert to original case when drag completes - _splitterPreviousPointer = _previousCursor; - } - else - { - Window.Current.CoreWindow.PointerCursor = _previousCursor; - } - } - - private void Element_PointerEntered(object sender, PointerRoutedEventArgs e) - { - // if not dragging - if (!_isDragging) - { - _previousCursor = _splitterPreviousPointer = Window.Current.CoreWindow.PointerCursor; - UpdateDisplayCursor(); - } - - // if dragging - else - { - _previousCursor = _splitterPreviousPointer; - } - } - - private void UpdateDisplayCursor() - { - if (_gripperCursor == GridSplitter.GripperCursorType.Default) - { - if (_gridSplitterDirection == GridSplitter.GridResizeDirection.Columns) - { - Window.Current.CoreWindow.PointerCursor = GridSplitter.ColumnsSplitterCursor; - } - else if (_gridSplitterDirection == GridSplitter.GridResizeDirection.Rows) - { - Window.Current.CoreWindow.PointerCursor = GridSplitter.RowSplitterCursor; - } - } - else - { - var coreCursor = (CoreCursorType)((int)_gripperCursor); - if (_gripperCursor == GridSplitter.GripperCursorType.Custom) - { - if (_gripperCustomCursorResource > GridSplitter.GripperCustomCursorDefaultResource) - { - Window.Current.CoreWindow.PointerCursor = new CoreCursor(coreCursor, (uint)_gripperCustomCursorResource); - } - } - else - { - Window.Current.CoreWindow.PointerCursor = new CoreCursor(coreCursor, 1); - } - } - } - - internal void SplitterManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) - { - var splitter = sender as GridSplitter; - if (splitter == null) - { - return; - } - - _splitterPreviousPointer = splitter.PreviousCursor; - _isDragging = true; - } - - internal void SplitterManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) - { - var splitter = sender as GridSplitter; - if (splitter == null) - { - return; - } - - Window.Current.CoreWindow.PointerCursor = splitter.PreviousCursor = _splitterPreviousPointer; - _isDragging = false; - } - - internal void UnhookEvents() - { - if (_element == null) - { - return; - } - - _element.PointerEntered -= Element_PointerEntered; - _element.PointerExited -= Element_PointerExited; - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.Events.cs new file mode 100644 index 00000000000..18b797dda16 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.Events.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Events for . + /// + public partial class ContentSizer + { + /// + protected override void OnLoaded(RoutedEventArgs e) + { + if (TargetControl == null) + { + TargetControl = this.FindAscendant(); + } + } + + private double _currentSize; + + /// + protected override void OnDragStarting() + { + if (TargetControl != null) + { + _currentSize = + Orientation == Orientation.Vertical ? + TargetControl.ActualWidth : + TargetControl.ActualHeight; + } + } + + /// + protected override bool OnDragHorizontal(double horizontalChange) + { + if (TargetControl == null) + { + return true; + } + + horizontalChange = IsDragInverted ? -horizontalChange : horizontalChange; + + if (!IsValidWidth(TargetControl, _currentSize + horizontalChange, ActualWidth)) + { + return false; + } + + TargetControl.Width = _currentSize + horizontalChange; + + return true; + } + + /// + protected override bool OnDragVertical(double verticalChange) + { + if (TargetControl == null) + { + return false; + } + + verticalChange = IsDragInverted ? -verticalChange : verticalChange; + + if (!IsValidHeight(TargetControl, _currentSize + verticalChange, ActualHeight)) + { + return false; + } + + TargetControl.Height = _currentSize + verticalChange; + + return true; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.Properties.cs new file mode 100644 index 00000000000..f9f410d547d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.Properties.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Properties for . + /// + public partial class ContentSizer + { + /// + /// Gets or sets a value indicating whether the control is resizing in the opposite direction. + /// + public bool IsDragInverted + { + get { return (bool)GetValue(IsDragInvertedProperty); } + set { SetValue(IsDragInvertedProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsDragInvertedProperty = + DependencyProperty.Register(nameof(IsDragInverted), typeof(bool), typeof(ContentSizer), new PropertyMetadata(false)); + + /// + /// Gets or sets the control that the is resizing. Be default, this will be the visual ancestor of the . + /// + public FrameworkElement TargetControl + { + get { return (FrameworkElement)GetValue(TargetControlProperty); } + set { SetValue(TargetControlProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetControlProperty = + DependencyProperty.Register(nameof(TargetControl), typeof(FrameworkElement), typeof(ContentSizer), new PropertyMetadata(null, OnTargetControlChanged)); + + private static void OnTargetControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + // TODO: Should we do this after the TargetControl is Loaded? (And use ActualWidth?) + // Or should we just do it in the manipulation event if Width is null? + + // Check if our width can be manipulated + if (d is SizerBase splitterBase && e.NewValue is FrameworkElement element) + { + // TODO: For Auto ResizeDirection we might want to do detection logic (TBD) here first? + if (splitterBase.Orientation != Orientation.Horizontal && double.IsNaN(element.Width)) + { + // We need to set the Width or Height somewhere, + // as if it's NaN we won't be able to manipulate it. + element.Width = element.DesiredSize.Width; + } + + if (splitterBase.Orientation != Orientation.Vertical && double.IsNaN(element.Height)) + { + element.Height = element.DesiredSize.Height; + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.cs new file mode 100644 index 00000000000..bcaba6a0272 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/ContentSizer/ContentSizer.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// The is a control which can be used to resize any element, usually its parent. If you are using a , use instead. + /// + public partial class ContentSizer : SizerBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Data.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Data.cs new file mode 100644 index 00000000000..477640824a1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Data.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Represents the control that redistributes space between columns or rows of a Grid control. + /// + public partial class GridSplitter + { + /// + /// Enum to indicate whether GridSplitter resizes Columns or Rows + /// + public enum GridResizeDirection + { + /// + /// Determines whether to resize rows or columns based on its Alignment and + /// width compared to height + /// + Auto, + + /// + /// Resize columns when dragging Splitter. + /// + Columns, + + /// + /// Resize rows when dragging Splitter. + /// + Rows + } + + /// + /// Enum to indicate what Columns or Rows the GridSplitter resizes + /// + public enum GridResizeBehavior + { + /// + /// Determine which columns or rows to resize based on its Alignment. + /// + BasedOnAlignment, + + /// + /// Resize the current and next Columns or Rows. + /// + CurrentAndNext, + + /// + /// Resize the previous and current Columns or Rows. + /// + PreviousAndCurrent, + + /// + /// Resize the previous and next Columns or Rows. + /// + PreviousAndNext + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Events.cs new file mode 100644 index 00000000000..1ffab158fbd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Events.cs @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Represents the control that redistributes space between columns or rows of a Grid control. + /// + public partial class GridSplitter + { + /// + protected override void OnLoaded(RoutedEventArgs e) + { + _resizeDirection = GetResizeDirection(); + Orientation = _resizeDirection == GridResizeDirection.Rows ? + Orientation.Horizontal : Orientation.Vertical; + _resizeBehavior = GetResizeBehavior(); + } + + private double _currentSize; + private double _siblingSize; + + /// + protected override void OnDragStarting() + { + _resizeDirection = GetResizeDirection(); + Orientation = _resizeDirection == GridResizeDirection.Rows ? + Orientation.Horizontal : Orientation.Vertical; + _resizeBehavior = GetResizeBehavior(); + + // Record starting points + if (Orientation == Orientation.Horizontal) + { + _currentSize = CurrentRow?.ActualHeight ?? -1; + _siblingSize = SiblingRow?.ActualHeight ?? -1; + } + else + { + _currentSize = CurrentColumn?.ActualWidth ?? -1; + _siblingSize = SiblingColumn?.ActualWidth ?? -1; + } + } + + /// + protected override bool OnDragVertical(double verticalChange) + { + if (CurrentRow == null || SiblingRow == null) + { + return false; + } + + var currentChange = _currentSize + verticalChange; + var siblingChange = _siblingSize + (verticalChange * -1); // sibling moves opposite + + // if current row has fixed height then resize it + if (!IsStarRow(CurrentRow)) + { + // No need to check for the row Min height because it is automatically respected + return SetRowHeight(CurrentRow, currentChange, GridUnitType.Pixel); + } + + // if sibling row has fixed width then resize it + else if (!IsStarRow(SiblingRow)) + { + // Would adding to this column make the current column violate the MinWidth? + if (IsValidRowHeight(CurrentRow, currentChange) == false) + { + return false; + } + + return SetRowHeight(SiblingRow, siblingChange, GridUnitType.Pixel); + } + + // if both row haven't fixed height (auto *) + else + { + // change current row height to the new height with respecting the auto + // change sibling row height to the new height relative to current row + // respect the other star row height by setting it's height to it's actual height with stars + + // We need to validate current and sibling height to not cause any unexpected behavior + if (!IsValidRowHeight(CurrentRow, currentChange) || + !IsValidRowHeight(SiblingRow, siblingChange)) + { + return false; + } + + foreach (var rowDefinition in Resizable.RowDefinitions) + { + if (rowDefinition == CurrentRow) + { + SetRowHeight(CurrentRow, currentChange, GridUnitType.Star); + } + else if (rowDefinition == SiblingRow) + { + SetRowHeight(SiblingRow, siblingChange, GridUnitType.Star); + } + else if (IsStarRow(rowDefinition)) + { + rowDefinition.Height = new GridLength(rowDefinition.ActualHeight, GridUnitType.Star); + } + } + + return true; + } + } + + /// + protected override bool OnDragHorizontal(double horizontalChange) + { + if (CurrentColumn == null || SiblingColumn == null) + { + return false; + } + + var currentChange = _currentSize + horizontalChange; + var siblingChange = _siblingSize + (horizontalChange * -1); // sibling moves opposite + + // if current column has fixed width then resize it + if (!IsStarColumn(CurrentColumn)) + { + // No need to check for the Column Min width because it is automatically respected + return SetColumnWidth(CurrentColumn, currentChange, GridUnitType.Pixel); + } + + // if sibling column has fixed width then resize it + else if (!IsStarColumn(SiblingColumn)) + { + // Would adding to this column make the current column violate the MinWidth? + if (IsValidColumnWidth(CurrentColumn, currentChange) == false) + { + return false; + } + + return SetColumnWidth(SiblingColumn, siblingChange, GridUnitType.Pixel); + } + + // if both column haven't fixed width (auto *) + else + { + // change current column width to the new width with respecting the auto + // change sibling column width to the new width relative to current column + // respect the other star column width by setting it's width to it's actual width with stars + + // We need to validate current and sibling width to not cause any unexpected behavior + if (!IsValidColumnWidth(CurrentColumn, currentChange) || + !IsValidColumnWidth(SiblingColumn, siblingChange)) + { + return false; + } + + foreach (var columnDefinition in Resizable.ColumnDefinitions) + { + if (columnDefinition == CurrentColumn) + { + SetColumnWidth(CurrentColumn, currentChange, GridUnitType.Star); + } + else if (columnDefinition == SiblingColumn) + { + SetColumnWidth(SiblingColumn, siblingChange, GridUnitType.Star); + } + else if (IsStarColumn(columnDefinition)) + { + columnDefinition.Width = new GridLength(columnDefinition.ActualWidth, GridUnitType.Star); + } + } + + return true; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Helper.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Helpers.cs similarity index 94% rename from Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Helper.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Helpers.cs index 1b7496cded4..1f65b22f78c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.Helper.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Helpers.cs @@ -22,10 +22,8 @@ private static bool IsStarRow(RowDefinition definition) return ((GridLength)definition.GetValue(RowDefinition.HeightProperty)).IsStar; } - private bool SetColumnWidth(ColumnDefinition columnDefinition, double horizontalChange, GridUnitType unitType) + private bool SetColumnWidth(ColumnDefinition columnDefinition, double newWidth, GridUnitType unitType) { - var newWidth = columnDefinition.ActualWidth + horizontalChange; - var minWidth = columnDefinition.MinWidth; if (!double.IsNaN(minWidth) && newWidth < minWidth) { @@ -47,10 +45,8 @@ private bool SetColumnWidth(ColumnDefinition columnDefinition, double horizontal return false; } - private bool IsValidColumnWidth(ColumnDefinition columnDefinition, double horizontalChange) + private bool IsValidColumnWidth(ColumnDefinition columnDefinition, double newWidth) { - var newWidth = columnDefinition.ActualWidth + horizontalChange; - var minWidth = columnDefinition.MinWidth; if (!double.IsNaN(minWidth) && newWidth < minWidth) { @@ -71,10 +67,8 @@ private bool IsValidColumnWidth(ColumnDefinition columnDefinition, double horizo return true; } - private bool SetRowHeight(RowDefinition rowDefinition, double verticalChange, GridUnitType unitType) + private bool SetRowHeight(RowDefinition rowDefinition, double newHeight, GridUnitType unitType) { - var newHeight = rowDefinition.ActualHeight + verticalChange; - var minHeight = rowDefinition.MinHeight; if (!double.IsNaN(minHeight) && newHeight < minHeight) { @@ -96,10 +90,8 @@ private bool SetRowHeight(RowDefinition rowDefinition, double verticalChange, Gr return false; } - private bool IsValidRowHeight(RowDefinition rowDefinition, double verticalChange) + private bool IsValidRowHeight(RowDefinition rowDefinition, double newHeight) { - var newHeight = rowDefinition.ActualHeight + verticalChange; - var minHeight = rowDefinition.MinHeight; if (!double.IsNaN(minHeight) && newHeight < minHeight) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Properties.cs new file mode 100644 index 00000000000..1ffcdbb4f92 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.Properties.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Represents the control that redistributes space between columns or rows of a Grid control. + /// + public partial class GridSplitter + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ResizeDirectionProperty + = DependencyProperty.Register( + nameof(ResizeDirection), + typeof(GridResizeDirection), + typeof(GridSplitter), + new PropertyMetadata(GridResizeDirection.Auto, OnResizeDirectionPropertyChanged)); + + private static void OnResizeDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is GridSplitter splitter && e.NewValue is GridResizeDirection direction && + direction != GridResizeDirection.Auto) + { + // Update base classes property based on specific polyfill for GridSplitter + splitter.Orientation = + direction == GridResizeDirection.Rows ? + Orientation.Horizontal : + Orientation.Vertical; + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ResizeBehaviorProperty + = DependencyProperty.Register( + nameof(ResizeBehavior), + typeof(GridResizeBehavior), + typeof(GridSplitter), + new PropertyMetadata(GridResizeBehavior.BasedOnAlignment)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ParentLevelProperty + = DependencyProperty.Register( + nameof(ParentLevel), + typeof(int), + typeof(GridSplitter), + new PropertyMetadata(default(int))); + + /// + /// Gets or sets whether the Splitter resizes the Columns, Rows, or Both. + /// + public GridResizeDirection ResizeDirection + { + get { return (GridResizeDirection)GetValue(ResizeDirectionProperty); } + set { SetValue(ResizeDirectionProperty, value); } + } + + /// + /// Gets or sets which Columns or Rows the Splitter resizes. + /// + public GridResizeBehavior ResizeBehavior + { + get { return (GridResizeBehavior)GetValue(ResizeBehaviorProperty); } + set { SetValue(ResizeBehaviorProperty, value); } + } + + /// + /// Gets or sets the level of the parent grid to resize + /// + public int ParentLevel + { + get { return (int)GetValue(ParentLevelProperty); } + set { SetValue(ParentLevelProperty, value); } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.cs similarity index 52% rename from Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.cs index c6f807397aa..0a24643ae92 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter/GridSplitter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/GridSplitter/GridSplitter.cs @@ -2,33 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Automation; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; namespace Microsoft.Toolkit.Uwp.UI.Controls { /// /// Represents the control that redistributes space between columns or rows of a Grid control. /// - public partial class GridSplitter : Control + public partial class GridSplitter : SizerBase { - internal const int GripperCustomCursorDefaultResource = -1; - internal static readonly CoreCursor ColumnsSplitterCursor = new CoreCursor(CoreCursorType.SizeWestEast, 1); - internal static readonly CoreCursor RowSplitterCursor = new CoreCursor(CoreCursorType.SizeNorthSouth, 1); - - internal CoreCursor PreviousCursor { get; set; } - private GridResizeDirection _resizeDirection; private GridResizeBehavior _resizeBehavior; - private GripperHoverWrapper _hoverWrapper; - private TextBlock _gripperDisplay; - - private bool _pressed = false; - private bool _dragging = false; - private bool _pointerEntered = false; /// /// Gets the target parent grid from level @@ -156,91 +144,5 @@ private RowDefinition SiblingRow return null; } } - - /// - /// Initializes a new instance of the class. - /// - public GridSplitter() - { - DefaultStyleKey = typeof(GridSplitter); - Loaded += GridSplitter_Loaded; - string automationName = "WCT_GridSplitter_AutomationName".GetLocalized("Microsoft.Toolkit.Uwp.UI.Controls.Layout/Resources"); - AutomationProperties.SetName(this, automationName); - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - // Unhook registered events - Loaded -= GridSplitter_Loaded; - PointerEntered -= GridSplitter_PointerEntered; - PointerExited -= GridSplitter_PointerExited; - PointerPressed -= GridSplitter_PointerPressed; - PointerReleased -= GridSplitter_PointerReleased; - ManipulationStarted -= GridSplitter_ManipulationStarted; - ManipulationCompleted -= GridSplitter_ManipulationCompleted; - - _hoverWrapper?.UnhookEvents(); - - // Register Events - Loaded += GridSplitter_Loaded; - PointerEntered += GridSplitter_PointerEntered; - PointerExited += GridSplitter_PointerExited; - PointerPressed += GridSplitter_PointerPressed; - PointerReleased += GridSplitter_PointerReleased; - ManipulationStarted += GridSplitter_ManipulationStarted; - ManipulationCompleted += GridSplitter_ManipulationCompleted; - - _hoverWrapper?.UpdateHoverElement(Element); - - ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; - } - - private void GridSplitter_PointerReleased(object sender, PointerRoutedEventArgs e) - { - _pressed = false; - VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true); - } - - private void GridSplitter_PointerPressed(object sender, PointerRoutedEventArgs e) - { - _pressed = true; - VisualStateManager.GoToState(this, "Pressed", true); - } - - private void GridSplitter_PointerExited(object sender, PointerRoutedEventArgs e) - { - _pointerEntered = false; - - if (!_pressed && !_dragging) - { - VisualStateManager.GoToState(this, "Normal", true); - } - } - - private void GridSplitter_PointerEntered(object sender, PointerRoutedEventArgs e) - { - _pointerEntered = true; - - if (!_pressed && !_dragging) - { - VisualStateManager.GoToState(this, "PointerOver", true); - } - } - - private void GridSplitter_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) - { - _dragging = false; - _pressed = false; - VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true); - } - - private void GridSplitter_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) - { - _dragging = true; - VisualStateManager.GoToState(this, "Pressed", true); - } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.Events.cs new file mode 100644 index 00000000000..92f5bae1d83 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.Events.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Events for . + /// + public partial class PropertySizer + { + private double _currentSize; + + /// + protected override void OnDragStarting() + { + // We grab the current size of the bound value when we start a drag + // and we manipulate from that set point. + if (ReadLocalValue(BindingProperty) != DependencyProperty.UnsetValue) + { + _currentSize = Binding; + } + } + + /// + protected override bool OnDragHorizontal(double horizontalChange) + { + // We use a central function for both horizontal/vertical as + // a general property has no notion of direction when we + // manipulate it, so the logic is abstracted. + return ApplySizeChange(horizontalChange); + } + + /// + protected override bool OnDragVertical(double verticalChange) + { + return ApplySizeChange(verticalChange); + } + + private bool ApplySizeChange(double newSize) + { + newSize = IsDragInverted ? -newSize : newSize; + + // We want to be checking the modified final value for bounds checks. + newSize += _currentSize; + + // Check if we hit the min/max value, as we should use that if we're on the edge + if (ReadLocalValue(MinimumProperty) != DependencyProperty.UnsetValue && + newSize < Minimum) + { + // We use SetValue here as that'll update our bound property vs. overwriting the binding itself. + SetValue(BindingProperty, Minimum); + } + else if (ReadLocalValue(MaximumProperty) != DependencyProperty.UnsetValue && + newSize > Maximum) + { + SetValue(BindingProperty, Maximum); + } + else + { + // Otherwise, we use the value provided. + SetValue(BindingProperty, newSize); + } + + // We're always manipulating the value effectively. + return true; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.Properties.cs new file mode 100644 index 00000000000..85ab278da8a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.Properties.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Properties for . + /// + public partial class PropertySizer + { + /// + /// Gets or sets a value indicating whether the control is resizing in the opposite direction. + /// + public bool IsDragInverted + { + get { return (bool)GetValue(IsDragInvertedProperty); } + set { SetValue(IsDragInvertedProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsDragInvertedProperty = + DependencyProperty.Register(nameof(IsDragInverted), typeof(bool), typeof(PropertySizer), new PropertyMetadata(false)); + + /// + /// Gets or sets a two-way binding to a double value that the is manipulating. + /// + /// + /// Note that the binding should be configured to be a TwoWay binding in order for the control to notify the source of the changed value. + /// + /// + /// <controls:PropertySizer Binding="{Binding OpenPaneLength, ElementName=ViewPanel, Mode=TwoWay}"> + /// + public double Binding + { + get { return (double)GetValue(BindingProperty); } + set { SetValue(BindingProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BindingProperty = + DependencyProperty.Register(nameof(Binding), typeof(double), typeof(PropertySizer), new PropertyMetadata(null)); + + /// + /// Gets or sets the minimum allowed value for the to allow for the value. Ignored if not provided. + /// + public double Minimum + { + get { return (double)GetValue(MinimumProperty); } + set { SetValue(MinimumProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MinimumProperty = + DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(PropertySizer), new PropertyMetadata(0)); + + /// + /// Gets or sets the maximum allowed value for the to allow for the value. Ignored if not provided. + /// + public double Maximum + { + get { return (double)GetValue(MaximumProperty); } + set { SetValue(MaximumProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MaximumProperty = + DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(PropertySizer), new PropertyMetadata(0)); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.cs new file mode 100644 index 00000000000..3b951694314 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/PropertySizer/PropertySizer.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// The is a control which can be used to manipulate the value of another double based property. For instance manipulating the OpenPaneLength of a NavigationView control. If you are using a , use instead. + /// + public partial class PropertySizer : SizerBase + { + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerAutomationPeer.cs new file mode 100644 index 00000000000..c6e0170cd3e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerAutomationPeer.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Controls; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; + +namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers +{ + /// + /// Defines a framework element automation peer for the controls. + /// + public class SizerAutomationPeer : FrameworkElementAutomationPeer + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The that is associated with this . + /// + public SizerAutomationPeer(SizerBase owner) + : base(owner) + { + } + + private SizerBase OwningSizer + { + get + { + return Owner as SizerBase; + } + } + + /// + /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + /// + /// Called by GetName. + /// + /// + /// Returns the first of these that is not null or empty: + /// - Value returned by the base implementation + /// - Name of the owning ContentSizer + /// - ContentSizer class name + /// + protected override string GetNameCore() + { + string name = AutomationProperties.GetName(this.OwningSizer); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + + name = this.OwningSizer.Name; + if (!string.IsNullOrEmpty(name)) + { + return name; + } + + name = base.GetNameCore(); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + + return string.Empty; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Events.cs new file mode 100644 index 00000000000..f63cbe53765 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Events.cs @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Event implementations for . + /// + public partial class SizerBase + { + /// + protected override void OnKeyDown(KeyRoutedEventArgs e) + { + // If we're manipulating with mouse/touch, we ignore keyboard inputs. + if (_dragging) + { + return; + } + + //// TODO: Do we want Ctrl/Shift to be a small increment (kind of inverse to old GridSplitter logic)? + //// var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control); + //// if (ctrl.HasFlag(CoreVirtualKeyStates.Down)) + //// Note: WPF doesn't do anything here. + //// I think if we did anything, we'd create a SmallKeyboardIncrement property? + + // Initialize a drag event for this keyboard interaction. + OnDragStarting(); + + if (Orientation == Orientation.Vertical) + { + var horizontalChange = KeyboardIncrement; + + // Important: adjust for RTL language flow settings and invert horizontal axis + if (this.FlowDirection == FlowDirection.RightToLeft) + { + horizontalChange *= -1; + } + + if (e.Key == Windows.System.VirtualKey.Left) + { + OnDragHorizontal(-horizontalChange); + } + else if (e.Key == Windows.System.VirtualKey.Right) + { + OnDragHorizontal(horizontalChange); + } + } + else + { + if (e.Key == Windows.System.VirtualKey.Up) + { + OnDragVertical(-KeyboardIncrement); + } + else if (e.Key == Windows.System.VirtualKey.Down) + { + OnDragVertical(KeyboardIncrement); + } + } + } + + /// + protected override void OnManipulationStarting(ManipulationStartingRoutedEventArgs e) + { + base.OnManipulationStarting(e); + + OnDragStarting(); + } + + /// + protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e) + { + // We use Trancate here to provide 'snapping' points with the DragIncrement property + // It works for both our negative and positive values, as otherwise we'd need to use + // Ceiling when negative and Floor when positive to maintain the correct behavior. + var horizontalChange = + Math.Truncate(e.Cumulative.Translation.X / DragIncrement) * DragIncrement; + var verticalChange = + Math.Truncate(e.Cumulative.Translation.Y / DragIncrement) * DragIncrement; + + // Important: adjust for RTL language flow settings and invert horizontal axis + if (this.FlowDirection == FlowDirection.RightToLeft) + { + horizontalChange *= -1; + } + + if (Orientation == Orientation.Vertical) + { + if (!OnDragHorizontal(horizontalChange)) + { + return; + } + } + else if (Orientation == Orientation.Horizontal) + { + if (!OnDragVertical(verticalChange)) + { + return; + } + } + + base.OnManipulationDelta(e); + } + + // private helper bools for Visual States + private bool _pressed = false; + private bool _dragging = false; + private bool _pointerEntered = false; + + private void SizerBase_PointerReleased(object sender, PointerRoutedEventArgs e) + { + _pressed = false; + + if (IsEnabled) + { + VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true); + } + } + + private void SizerBase_PointerPressed(object sender, PointerRoutedEventArgs e) + { + _pressed = true; + + if (IsEnabled) + { + VisualStateManager.GoToState(this, "Pressed", true); + } + } + + private void SizerBase_PointerExited(object sender, PointerRoutedEventArgs e) + { + _pointerEntered = false; + + if (!_pressed && !_dragging && IsEnabled) + { + VisualStateManager.GoToState(this, "Normal", true); + } + } + + private void SizerBase_PointerEntered(object sender, PointerRoutedEventArgs e) + { + _pointerEntered = true; + + if (!_pressed && !_dragging && IsEnabled) + { + VisualStateManager.GoToState(this, "PointerOver", true); + } + } + + private void SizerBase_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + { + _dragging = false; + _pressed = false; + VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true); + } + + private void SizerBase_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) + { + _dragging = true; + VisualStateManager.GoToState(this, "Pressed", true); + } + + private void SizerBase_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (!IsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", true); + } + else + { + VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Helpers.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Helpers.cs new file mode 100644 index 00000000000..95eb3123ab6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Helpers.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Protected helper methods for and subclasses. + /// + public partial class SizerBase : Control + { + /// + /// Check for new requested vertical size is valid or not + /// + /// Target control being resized + /// The requested new height + /// The parent control's ActualHeight + /// Bool result if requested vertical change is valid or not + protected static bool IsValidHeight(FrameworkElement target, double newHeight, double parentActualHeight) + { + var minHeight = target.MinHeight; + if (newHeight < 0 || (!double.IsNaN(minHeight) && newHeight < minHeight)) + { + return false; + } + + var maxHeight = target.MaxHeight; + if (!double.IsNaN(maxHeight) && newHeight > maxHeight) + { + return false; + } + + if (newHeight <= parentActualHeight) + { + return false; + } + + return true; + } + + /// + /// Check for new requested horizontal size is valid or not + /// + /// Target control being resized + /// The requested new width + /// The parent control's ActualWidth + /// Bool result if requested horizontal change is valid or not + protected static bool IsValidWidth(FrameworkElement target, double newWidth, double parentActualWidth) + { + var minWidth = target.MinWidth; + if (newWidth < 0 || (!double.IsNaN(minWidth) && newWidth < minWidth)) + { + return false; + } + + var maxWidth = target.MaxWidth; + if (!double.IsNaN(maxWidth) && newWidth > maxWidth) + { + return false; + } + + if (newWidth <= parentActualWidth) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Properties.cs new file mode 100644 index 00000000000..bae430e628c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.Properties.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Properties for + /// + public partial class SizerBase : Control + { + /// + /// Gets or sets the cursor to use when hovering over the gripper bar. + /// + public CoreCursorType Cursor + { + get { return (CoreCursorType)GetValue(CursorProperty); } + set { SetValue(CursorProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CursorProperty = + DependencyProperty.Register(nameof(Cursor), typeof(CoreCursorType), typeof(SizerBase), new PropertyMetadata(CoreCursorType.SizeWestEast)); + + /// + /// Gets or sets the incremental amount of change for draging with the mouse or touch of a sizer control. Effectively a snapping increment for changes. The default is 1. + /// + /// + /// For instance, if the DragIncrement is set to 16. Then when a component is resized with the sizer, it will only increase or decrease in size in that increment. I.e. -16, 0, 16, 32, 48, etc... + /// + /// + /// This value is indepedent of the property. If you need to provide consistent snapping when moving regardless of input device, set these properties to the same value. + /// + public double DragIncrement + { + get { return (double)GetValue(DragIncrementProperty); } + set { SetValue(DragIncrementProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DragIncrementProperty = + DependencyProperty.Register(nameof(DragIncrement), typeof(double), typeof(SizerBase), new PropertyMetadata(1d)); + + /// + /// Gets or sets the distance each press of an arrow key moves a sizer control. The default is 8. + /// + /// + /// This value is independent of the setting when using mouse/touch. If you want a consistent behavior regardless of input device, set them to the same value if snapping is required. + /// + public double KeyboardIncrement + { + get { return (double)GetValue(KeyboardIncrementProperty); } + set { SetValue(KeyboardIncrementProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty KeyboardIncrementProperty = + DependencyProperty.Register(nameof(KeyboardIncrement), typeof(double), typeof(SizerBase), new PropertyMetadata(8d)); + + /// + /// Gets or sets the orientation the sizer will be and how it will interact with other elements. Defaults to . + /// + /// + /// Note if using , use the property instead. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(SizerBase), new PropertyMetadata(Orientation.Vertical, OnOrientationPropertyChanged)); + + private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is SizerBase gripper) + { + // TODO: For WinUI 3, we will just be setting the ProtectedCursor property directly. + gripper.Cursor = gripper.Orientation == Orientation.Vertical ? CoreCursorType.SizeWestEast : CoreCursorType.SizeNorthSouth; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.cs new file mode 100644 index 00000000000..1bff9578481 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Automation.Peers; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Base class for splitting/resizing type controls like and . Acts similar to an enlarged type control, but with keyboard support. Subclasses should override the various abstract methods here to implement their behavior. + /// + public abstract partial class SizerBase : Control + { + /// + /// Called when the control has been initialized. + /// + /// Loaded event args. + protected virtual void OnLoaded(RoutedEventArgs e) + { + } + + /// + /// Called when the control starts to be dragged by the user. + /// Implementor should record current state of manipulated target at this point in time. + /// They will receive the cumulative change in or + /// based on the property. + /// + /// + /// This method is also called at the start of a keyboard interaction. Keyboard strokes use the same pattern to emulate a mouse movement for a single change. The appropriate + /// or + /// method will also be called after when the keyboard is used. + /// + protected abstract void OnDragStarting(); + + /// + /// Method to process the requested horizontal resize. + /// + /// The horizontal change amount from the start in device-independent pixels DIP. + /// indicates if a change was made + /// + /// The value provided here is the cumulative change from the beginning of the + /// manipulation. This method will be used regardless of input device. It will already + /// be adjusted for RightToLeft of the containing + /// layout/settings. It will also already account for any settings such as + /// or . The implementor + /// just needs to use the provided value to manipulate their baseline stored + /// in to provide the desired change. + /// + protected abstract bool OnDragHorizontal(double horizontalChange); + + /// + /// Method to process the requested vertical resize. + /// + /// The vertical change amount from the start in device-independent pixels DIP. + /// indicates if a change was made + /// + /// The value provided here is the cumulative change from the beginning of the + /// manipulation. This method will be used regardless of input device. It will also + /// already account for any settings such as or + /// . The implementor just needs + /// to use the provided value to manipulate their baseline stored + /// in to provide the desired change. + /// + protected abstract bool OnDragVertical(double verticalChange); + + /// + /// Initializes a new instance of the class. + /// + public SizerBase() + { + this.DefaultStyleKey = typeof(SizerBase); + } + + /// + /// Creates AutomationPeer () + /// + /// An automation peer for this . + protected override AutomationPeer OnCreateAutomationPeer() + { + return new SizerAutomationPeer(this); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + // Unregister Events + Loaded -= SizerBase_Loaded; + PointerEntered -= SizerBase_PointerEntered; + PointerExited -= SizerBase_PointerExited; + PointerPressed -= SizerBase_PointerPressed; + PointerReleased -= SizerBase_PointerReleased; + ManipulationStarted -= SizerBase_ManipulationStarted; + ManipulationCompleted -= SizerBase_ManipulationCompleted; + IsEnabledChanged -= SizerBase_IsEnabledChanged; + + // Register Events + Loaded += SizerBase_Loaded; + PointerEntered += SizerBase_PointerEntered; + PointerExited += SizerBase_PointerExited; + PointerPressed += SizerBase_PointerPressed; + PointerReleased += SizerBase_PointerReleased; + ManipulationStarted += SizerBase_ManipulationStarted; + ManipulationCompleted += SizerBase_ManipulationCompleted; + IsEnabledChanged += SizerBase_IsEnabledChanged; + + // Trigger initial state transition based on if we're Enabled or not currently. + SizerBase_IsEnabledChanged(this, null); + } + + private void SizerBase_Loaded(object sender, RoutedEventArgs e) + { + Loaded -= SizerBase_Loaded; + + OnLoaded(e); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.xaml new file mode 100644 index 00000000000..cb172b31770 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Sizers/SizerBase.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Strings/en-US/Resources.resw b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Strings/en-US/Resources.resw index 49a13464e39..d9201f3ce36 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Strings/en-US/Resources.resw +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Strings/en-US/Resources.resw @@ -65,8 +65,8 @@ Expand Blade Narrator Resource for BladeView expanded status - - GridSplitter - Narrator Resource for GridSplitter control + + Sizer + Narrator Resource for SizerBase controls and similar \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Themes/Generic.xaml index fd84465abdd..e3c125d8774 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Themes/Generic.xaml @@ -4,11 +4,11 @@ - + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI/Converters/OrientationToObjectConverter.cs b/Microsoft.Toolkit.Uwp.UI/Converters/OrientationToObjectConverter.cs new file mode 100644 index 00000000000..65d152d168d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI/Converters/OrientationToObjectConverter.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Converters +{ + /// + /// This class returns a value depending on the of the value provided to the converter. In case of default will return the . + /// + public partial class OrientationToObjectConverter : DependencyObject, IValueConverter + { + /// + /// Identifies the property. + /// + public static readonly DependencyProperty HorizontalValueProperty = + DependencyProperty.Register(nameof(HorizontalValue), typeof(object), typeof(TypeToObjectConverter), new PropertyMetadata(null)); + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty VerticalValueProperty = + DependencyProperty.Register(nameof(VerticalValue), typeof(object), typeof(TypeToObjectConverter), new PropertyMetadata(null)); + + /// + /// Gets or sets the value to be returned when the of the provided value is . + /// + public object HorizontalValue + { + get { return GetValue(HorizontalValueProperty); } + set { SetValue(HorizontalValueProperty, value); } + } + + /// + /// Gets or sets the value to be returned when the of the provided value is . + /// + public object VerticalValue + { + get { return GetValue(VerticalValueProperty); } + set { SetValue(VerticalValueProperty, value); } + } + + /// + /// Convert the 's Orientation to an other object. + /// + /// The source data being passed to the target. + /// The type of the target property, as a type reference. + /// An optional parameter to be used to invert the converter logic. + /// The language of the conversion. + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + var isHorizontal = value != null && value is Orientation orientation && orientation == Orientation.Horizontal; + + // Negate if needed + if (ConverterTools.TryParseBool(parameter)) + { + isHorizontal = !isHorizontal; + } + + return ConverterTools.Convert(isHorizontal ? HorizontalValue : VerticalValue, targetType); + } + + /// + /// Not implemented. + /// + /// The source data being passed to the target. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used. + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/UITests/UITests.Tests.Shared/Controls/GridSplitterTestPage.xaml b/UITests/UITests.Tests.Shared/Controls/GridSplitterTestPage.xaml index 09236488742..c0e6da4750e 100644 --- a/UITests/UITests.Tests.Shared/Controls/GridSplitterTestPage.xaml +++ b/UITests/UITests.Tests.Shared/Controls/GridSplitterTestPage.xaml @@ -81,14 +81,6 @@ Grid.ColumnSpan="3" Height="16" VerticalAlignment="Top"> - - - diff --git a/UnitTests/UnitTests.UWP/Converters/Test_OrientationToObjectConverter.cs b/UnitTests/UnitTests.UWP/Converters/Test_OrientationToObjectConverter.cs new file mode 100644 index 00000000000..8b7806cd2c0 --- /dev/null +++ b/UnitTests/UnitTests.UWP/Converters/Test_OrientationToObjectConverter.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Converters; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace UnitTests.Converters +{ + [TestClass] + public class Test_OrientationToObjectConverter + { + [TestCategory("Converters")] + [UITestMethod] + public void Test_OrientationConvertNullToString() + { + var converter = new OrientationToObjectConverter + { + HorizontalValue = "Horizontal", + VerticalValue = "Vertical", + }; + + var result = converter.Convert(null, typeof(string), null, "en-us"); + Assert.AreEqual("Vertical", result); + } + + [TestCategory("Converters")] + [UITestMethod] + public void Test_OrientationConvertVerticalToVisibility() + { + var converter = new OrientationToObjectConverter + { + HorizontalValue = Visibility.Visible, + VerticalValue = Visibility.Collapsed, + }; + + var result = converter.Convert(Orientation.Vertical, typeof(Visibility), null, "en-us"); + Assert.AreEqual(Visibility.Collapsed, result); + } + + [TestCategory("Converters")] + [UITestMethod] + public void Test_OrientationConvertHorizontalToVisibility() + { + var converter = new OrientationToObjectConverter + { + HorizontalValue = Visibility.Visible, + VerticalValue = Visibility.Collapsed, + }; + + var result = converter.Convert(Orientation.Horizontal, typeof(Visibility), null, "en-us"); + Assert.AreEqual(Visibility.Visible, result); + } + + [TestCategory("Converters")] + [UITestMethod] + public void Test_OrientationConvertStringToVisibilityWithNegateTrue() + { + var converter = new OrientationToObjectConverter + { + HorizontalValue = Visibility.Visible, + VerticalValue = Visibility.Collapsed, + }; + + var result = converter.Convert("anything", typeof(Visibility), "true", "en-us"); + Assert.AreEqual(Visibility.Visible, result); + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_ContentSizer.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_ContentSizer.cs new file mode 100644 index 00000000000..0d9f500ab0f --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_ContentSizer.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Automation.Peers; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; + +namespace UnitTests.UWP.UI.Controls +{ + [TestClass] + [TestCategory("Test_ContentSizer")] + public class Test_ContentSizer + { + [UITestMethod] + public void ShouldConfigureContentSizerAutomationPeer() + { + const string automationName = "MyContentSizer"; + const string name = "ContentSizer"; + + var contentSizer = new ContentSizer(); + var contentSizerAutomationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(contentSizer) as SizerAutomationPeer; + + Assert.IsNotNull(contentSizerAutomationPeer, "Verify that the AutomationPeer is ContentSizerAutomationPeer."); + + contentSizer.Name = name; + Assert.IsTrue(contentSizerAutomationPeer.GetName().Contains(name), "Verify that the UIA name contains the given Name of the ContentSizer."); + + contentSizer.SetValue(AutomationProperties.NameProperty, automationName); + Assert.IsTrue(contentSizerAutomationPeer.GetName().Contains(automationName), "Verify that the UIA name contains the given AutomationProperties.Name of the ContentSizer."); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index d50b438032e..0cb3c0b4db0 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -169,6 +169,7 @@ + @@ -243,6 +244,7 @@ +