diff --git a/Directory.Packages.props b/Directory.Packages.props index 9f1533ae6..bd7c0dc3a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,6 +22,7 @@ + diff --git a/src/Wpf.Ui.Gallery/Models/AppConfig.cs b/src/Wpf.Ui.Gallery/Models/AppConfig.cs new file mode 100644 index 000000000..e95403291 --- /dev/null +++ b/src/Wpf.Ui.Gallery/Models/AppConfig.cs @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +namespace Wpf.Ui.Gallery.Models; + +/// +/// Application configuration model. +/// +public class AppConfig +{ + /// + /// Gets or sets the configurations folder. + /// + public string? ConfigurationsFolder { get; set; } + + /// + /// Gets or sets the app properties file name. + /// + public string? AppPropertiesFileName { get; set; } + + /// + /// Gets or sets the navigation pane width. + /// + public double NavigationPaneWidth { get; set; } = 310.0; +} + diff --git a/src/Wpf.Ui.Gallery/Services/AppConfigService.cs b/src/Wpf.Ui.Gallery/Services/AppConfigService.cs new file mode 100644 index 000000000..189507426 --- /dev/null +++ b/src/Wpf.Ui.Gallery/Services/AppConfigService.cs @@ -0,0 +1,75 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Text.Json; +using Wpf.Ui.Gallery.Models; + +namespace Wpf.Ui.Gallery.Services; + +/// +/// Application configuration service. +/// +public class AppConfigService +{ + private readonly string _configFilePath; + private readonly AppConfig _config; + + public AppConfigService() + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + string appFolder = Path.Combine(appDataPath, "Wpf.Ui.Gallery"); + + if (!Directory.Exists(appFolder)) + { + _ = Directory.CreateDirectory(appFolder); + } + + _configFilePath = Path.Combine(appFolder, "config.json"); + _config = LoadConfig(); + } + + public AppConfig Config => _config; + + private AppConfig LoadConfig() + { + if (File.Exists(_configFilePath)) + { + try + { + string json = File.ReadAllText(_configFilePath); + return JsonSerializer.Deserialize(json) ?? new AppConfig(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error loading config: {ex.Message}"); + return new AppConfig(); + } + } + return new AppConfig(); + } + + private void SaveConfig(AppConfig config) + { + try + { + string json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(_configFilePath, json); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error saving config: {ex.Message}"); + } + } + + /// + /// Updates the navigation pane width and saves the configuration + /// + /// The new width value + public void UpdateNavigationPaneWidth(double newWidth) + { + _config.NavigationPaneWidth = newWidth; + SaveConfig(_config); + } +} diff --git a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml index a6a9607a2..08e5c0d5f 100644 --- a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml +++ b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml @@ -40,7 +40,7 @@ BreadcrumbBar="{Binding ElementName=BreadcrumbBar}" EnableDebugMessages="True" FooterMenuItemsSource="{Binding ViewModel.FooterMenuItems, Mode=OneWay}" - FrameMargin="0" + FrameMargin="0,100" IsBackButtonVisible="Visible" IsPaneToggleVisible="True" MenuItemsSource="{Binding ViewModel.MenuItems, Mode=OneWay}" @@ -50,11 +50,37 @@ PaneOpened="NavigationView_OnPaneOpened" SelectionChanged="OnNavigationSelectionChanged" TitleBar="{Binding ElementName=TitleBar, Mode=OneWay}" - Transition="FadeInWithSlide"> + Transition="FadeInWithSlide" + IsPaneResizable="True" + PaneResizeMinWidth="200" + PaneResizeMaxWidth="500"> + + + + + + + + + + + + + + + + diff --git a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs index cf2543f77..21ad5fa45 100644 --- a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs +++ b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs @@ -4,6 +4,7 @@ // All Rights Reserved. using Wpf.Ui.Controls; +using Wpf.Ui.Gallery.Services; using Wpf.Ui.Gallery.Services.Contracts; using Wpf.Ui.Gallery.ViewModels.Windows; using Wpf.Ui.Gallery.Views.Pages; @@ -12,6 +13,8 @@ namespace Wpf.Ui.Gallery.Views.Windows; public partial class MainWindow : IWindow { + private readonly AppConfigService _configService; + public MainWindow( MainWindowViewModel viewModel, INavigationService navigationService, @@ -25,11 +28,21 @@ IContentDialogService contentDialogService ViewModel = viewModel; DataContext = this; + // Initialize configuration service + _configService = new AppConfigService(); + InitializeComponent(); + // Restore saved navigation pane width + RestoreNavigationPaneWidth(); + + SetPageService(serviceProvider.GetService()!); snackbarService.SetSnackbarPresenter(SnackbarPresenter); - navigationService.SetNavigationControl(NavigationView); - contentDialogService.SetDialogHost(RootContentDialog); + contentDialogService.Set = RootContentDialog; + + NavigationView.Set + NavigationView.SetCurrentValue(NavigationView.MenuItemsSourceProperty, ViewModel.MenuItems); + NavigationView.SetCurrentValue(NavigationView.FooterMenuItemsSourceProperty, ViewModel.FooterMenuItems); } public MainWindowViewModel ViewModel { get; } @@ -38,6 +51,31 @@ IContentDialogService contentDialogService private bool _isPaneOpenedOrClosedFromCode; + /// + /// Handles the value change event of the navigation pane width slider + /// + private void PaneWidthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (NavigationView != null && e.NewValue > 0) + { + NavigationView.SetCurrentValue(NavigationView.OpenPaneLengthProperty, e.NewValue); + _configService.UpdateNavigationPaneWidth(e.NewValue); + } + } + + /// + /// Restores the saved navigation pane width + /// + private void RestoreNavigationPaneWidth() + { + var savedWidth = _configService.Config.NavigationPaneWidth; + if (savedWidth >= 200 && savedWidth <= 500) + { + NavigationView.SetCurrentValue(NavigationView.OpenPaneLengthProperty, savedWidth); + PaneWidthSlider.SetCurrentValue(System.Windows.Controls.Primitives.RangeBase.ValueProperty, savedWidth); + } + } + private void OnNavigationSelectionChanged(object sender, RoutedEventArgs e) { if (sender is not Wpf.Ui.Controls.NavigationView navigationView) diff --git a/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj b/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj index 6ecc4de7a..9e4233965 100644 --- a/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj +++ b/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs index 47aa0758f..34baf6e13 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs @@ -243,6 +243,30 @@ public partial class NavigationView new FrameworkPropertyMetadata(default(Thickness)) ); + /// Identifies the dependency property. + public static readonly DependencyProperty IsPaneResizableProperty = DependencyProperty.Register( + nameof(IsPaneResizable), + typeof(bool), + typeof(NavigationView), + new FrameworkPropertyMetadata(true) + ); + + /// Identifies the dependency property. + public static readonly DependencyProperty PaneResizeMinWidthProperty = DependencyProperty.Register( + nameof(PaneResizeMinWidth), + typeof(double), + typeof(NavigationView), + new FrameworkPropertyMetadata(200.0) + ); + + /// Identifies the dependency property. + public static readonly DependencyProperty PaneResizeMaxWidthProperty = DependencyProperty.Register( + nameof(PaneResizeMaxWidth), + typeof(double), + typeof(NavigationView), + new FrameworkPropertyMetadata(500.0) + ); + /// /// Gets or sets a value indicating whether debugging messages for this control are enabled /// @@ -450,6 +474,33 @@ public Thickness FrameMargin set => SetValue(FrameMarginProperty, value); } + /// + /// Gets or sets a value indicating whether the navigation pane is resizable. + /// + public bool IsPaneResizable + { + get => (bool)GetValue(IsPaneResizableProperty); + set => SetValue(IsPaneResizableProperty, value); + } + + /// + /// Gets or sets the minimum width for resizing the navigation pane. + /// + public double PaneResizeMinWidth + { + get => (double)GetValue(PaneResizeMinWidthProperty); + set => SetValue(PaneResizeMinWidthProperty, value); + } + + /// + /// Gets or sets the maximum width for resizing the navigation pane. + /// + public double PaneResizeMaxWidth + { + get => (double)GetValue(PaneResizeMaxWidthProperty); + set => SetValue(PaneResizeMaxWidthProperty, value); + } + private void OnMenuItemsSource_CollectionChanged( object? sender, IList collection, diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml index 03a7524a3..9fd8af31e 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml @@ -6,6 +6,7 @@ + @@ -421,6 +422,16 @@ + + + +/// NavigationView resize handle control +/// +public class NavigationViewResizeHandle : Control +{ + static NavigationViewResizeHandle() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(NavigationViewResizeHandle), + new FrameworkPropertyMetadata(typeof(NavigationViewResizeHandle)) + ); + } + + /// + /// Minimum resizable width + /// + public static readonly DependencyProperty MinWidthProperty = DependencyProperty.Register( + nameof(MinWidth), + typeof(double), + typeof(NavigationViewResizeHandle), + new FrameworkPropertyMetadata(200.0) + ); + + /// + /// Maximum resizable width + /// + public static readonly DependencyProperty MaxWidthProperty = DependencyProperty.Register( + nameof(MaxWidth), + typeof(double), + typeof(NavigationViewResizeHandle), + new FrameworkPropertyMetadata(500.0) + ); + + /// + /// Current width + /// + public static readonly DependencyProperty CurrentWidthProperty = DependencyProperty.Register( + nameof(CurrentWidth), + typeof(double), + typeof(NavigationViewResizeHandle), + new FrameworkPropertyMetadata(320.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault) + ); + + /// + /// Gets or sets the minimum resizable width + /// + public double MinWidth + { + get => (double)GetValue(MinWidthProperty); + set => SetValue(MinWidthProperty, value); + } + + /// + /// Gets or sets the maximum resizable width + /// + public double MaxWidth + { + get => (double)GetValue(MaxWidthProperty); + set => SetValue(MaxWidthProperty, value); + } + + /// + /// Gets or sets the current width + /// + public double CurrentWidth + { + get => (double)GetValue(CurrentWidthProperty); + set => SetValue(CurrentWidthProperty, value); + } + + /// + /// Gets a value indicating whether the mouse button is pressed + /// + public bool IsPressed + { + get => (bool)GetValue(IsPressedProperty); + private set => SetValue(IsPressedProperty, value); + } + + /// Identifies the dependency property. + public static readonly DependencyProperty IsPressedProperty = DependencyProperty.Register( + nameof(IsPressed), + typeof(bool), + typeof(NavigationViewResizeHandle), + new FrameworkPropertyMetadata(false) + ); + + private bool _isResizing; + private Point _startPoint; + private double _startWidth; + private double _lastWidth; + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (e.ButtonState == MouseButtonState.Pressed) + { + _isResizing = true; + IsPressed = true; + // Get position relative to parent element (NavigationView) + _startPoint = e.GetPosition(this.Parent as FrameworkElement ?? this); + _startWidth = CurrentWidth; + _lastWidth = _startWidth; + CaptureMouse(); + e.Handled = true; + } + + base.OnMouseLeftButtonDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (_isResizing) + { + // Get position relative to parent element (NavigationView) + Point currentPoint = e.GetPosition(this.Parent as FrameworkElement ?? this); + double deltaX = currentPoint.X - _startPoint.X; + double newWidth = _startWidth + deltaX; + + // Apply minimum and maximum width constraints + newWidth = Math.Max(MinWidth, Math.Min(MaxWidth, newWidth)); + + // Only update if width change is at least 1px to prevent flickering + if (Math.Abs(newWidth - _lastWidth) >= 1.0) + { + SetCurrentValue(CurrentWidthProperty, newWidth); + _lastWidth = newWidth; + } + + e.Handled = true; + } + else + { + // Change mouse cursor to resize cursor + SetCurrentValue(CursorProperty, Cursors.SizeWE); + } + + base.OnMouseMove(e); + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (_isResizing) + { + _isResizing = false; + IsPressed = false; + ReleaseMouseCapture(); + e.Handled = true; + } + + base.OnMouseLeftButtonUp(e); + } + + protected override void OnMouseLeave(MouseEventArgs e) + { + if (!_isResizing) + { + SetCurrentValue(CursorProperty, Cursors.Arrow); + } + + base.OnMouseLeave(e); + } + + protected override void OnLostMouseCapture(MouseEventArgs e) + { + _isResizing = false; + IsPressed = false; + base.OnLostMouseCapture(e); + } +} + diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewResizeHandle.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewResizeHandle.xaml new file mode 100644 index 000000000..8254055d9 --- /dev/null +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewResizeHandle.xaml @@ -0,0 +1,59 @@ + + + + + + +