Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions CompactGUI/Components/CompactGuiNavigationIcon.vb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Imports System.Windows.Media
Imports System.Windows.Shapes

Imports Wpf.Ui.Controls

Public NotInheritable Class CompactGuiNavigationIcon
Inherits IconElement

Private _northWestArrow As Path
Private _southEastArrow As Path

Protected Overrides Function InitializeChildren() As UIElement
Dim canvas As New Canvas With {
.Width = 20,
.Height = 20,
.SnapsToDevicePixels = True
}

'CompactGUI's mark is two opposing arrows compacting toward the centre.
'Keep them as simple open outlines so the navigation glyph reads cleanly at 20 px.
_northWestArrow = CreateArrowPath("M 2.7,2.7 L 9.0,9.0 M 9.0,9.0 L 9.0,5.6 M 9.0,9.0 L 5.6,9.0")
_southEastArrow = CreateArrowPath("M 17.3,17.3 L 11.0,11.0 M 11.0,11.0 L 11.0,14.4 M 11.0,11.0 L 14.4,11.0")

canvas.Children.Add(_northWestArrow)
canvas.Children.Add(_southEastArrow)
Return canvas
End Function

Private Function CreateArrowPath(data As String) As Path
Return New Path With {
.Data = Geometry.Parse(data),
.Stroke = Foreground,
.StrokeThickness = 1.55,
.StrokeStartLineCap = PenLineCap.Round,
.StrokeEndLineCap = PenLineCap.Round,
.StrokeLineJoin = PenLineJoin.Round,
.Fill = Brushes.Transparent
}
End Function

Protected Overrides Sub OnForegroundChanged(args As DependencyPropertyChangedEventArgs)
MyBase.OnForegroundChanged(args)

Dim brush = TryCast(args.NewValue, Brush)
If _northWestArrow IsNot Nothing Then _northWestArrow.Stroke = brush
If _southEastArrow IsNot Nothing Then _southEastArrow.Stroke = brush
End Sub
End Class
16 changes: 16 additions & 0 deletions CompactGUI/MainWindow.xaml.vb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged
_SettingsService = settingsService
DataContext = viewmodel

ConfigureCompressNavigationItem()

NotifyIconTrayMenu.DataContext = viewmodel

AddHandler Application.GetService(Of HomeViewModel)().PropertyChanged, AddressOf HVPropertyChanged
Expand All @@ -54,6 +56,20 @@ Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged
End Sub


Private Sub ConfigureCompressNavigationItem()
If NavigationView.MenuItems.Count = 0 Then Return

Dim compressItem = TryCast(NavigationView.MenuItems(0), NavigationViewItem)
If compressItem Is Nothing Then Return

compressItem.Content = "Compress"
compressItem.Icon = New CompactGuiNavigationIcon With {
.Width = 20,
.Height = 20
}
End Sub


Private _isOnHomePage As Boolean

Private Sub OnNavigated(sender As NavigationView, args As NavigatedEventArgs)
Expand Down
28 changes: 24 additions & 4 deletions CompactGUI/ViewModels/FolderViewModel.vb
Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,38 @@ Public NotInheritable Class FolderViewModel : Inherits ObservableObject : Implem
End Function

<RelayCommand>
Private Sub ApplyToAll()
Private Async Function ApplyToAll() As Task
Dim allFolders = Application.GetService(Of HomeViewModel)().Folders

For Each fl In allFolders.Where(Function(f) f.FolderActionState <> ActionState.Analysing AndAlso f.FolderActionState <> ActionState.Working AndAlso f.FolderActionState <> ActionState.Paused)
Dim targetFolders = allFolders.Where(
Function(f) f.FolderActionState <> ActionState.Analysing AndAlso
f.FolderActionState <> ActionState.Working AndAlso
f.FolderActionState <> ActionState.Paused).ToList()

Dim yesText = LanguageHelper.GetString("UniYes")
Dim noText = "No"
Dim options = Folder.CompressionOptions
Dim message =
$"Apply the settings currently shown for {Folder.DisplayName} to all added folders?{Environment.NewLine}{Environment.NewLine}" &
$"{LanguageHelper.GetString("CompressionMode")}: {options.SelectedCompressionMode}{Environment.NewLine}" &
$"{LanguageHelper.GetString("CompressionConfiguration_SkipFileTypes")}: {If(options.SkipPoorlyCompressedFileTypes, yesText, noText)}{Environment.NewLine}" &
$"{LanguageHelper.GetString("CompressionConfiguration_SkipFileTypesLikelyPoorly")}: {If(options.SkipUserSubmittedFiletypes, yesText, noText)}{Environment.NewLine}" &
$"{LanguageHelper.GetString("CompressionConfiguration_WatchFolderChanges")}: {If(options.WatchFolderForChanges, yesText, noText)}{Environment.NewLine}{Environment.NewLine}" &
"Folders that are currently busy will be left unchanged."

Dim confirmed = Await Application.GetService(Of IWindowService)().ShowMessageBox(
LanguageHelper.GetString("CompressionConfiguration_ApplyAll"),
message)
If Not confirmed Then Return

For Each fl In targetFolders
If fl IsNot Folder Then
fl.CompressionOptions = Folder.CompressionOptions.Clone
fl.FolderActionState = ActionState.Idle
End If
Next

_snackbarService.ShowAppliedToAllFolders()
End Sub
End Function

<RelayCommand>
Private Sub Pause()
Expand Down
210 changes: 208 additions & 2 deletions CompactGUI/Views/Pages/FolderView.xaml.vb
Original file line number Diff line number Diff line change
@@ -1,9 +1,215 @@
Public Class FolderView
Imports System.Windows.Controls.Primitives
Imports System.Windows.Media
Imports System.Windows.Shapes
Imports System.Windows.Threading

Public Class FolderView

Private Shared ReadOnly AutoFollowDebounce As TimeSpan = TimeSpan.FromSeconds(5)
Private Shared ReadOnly ColumnSeparatorBrush As Brush = New SolidColorBrush(Color.FromArgb(&H48, &HFF, &HFF, &HFF))
Private Const MinimumScrollThumbHeight As Double = 28

Private _compressionDetailsGrid As DataGrid
Private _scrollThumb As Thumb
Private _stopButton As Wpf.Ui.Controls.Button
Private _isAutoFollowing As Boolean
Private _isScrollThumbDragging As Boolean
Private _suppressAutoFollowUntil As DateTime = DateTime.MinValue

Public Sub New()
InitializeComponent()
AddHandler LayoutUpdated, AddressOf FolderView_LayoutUpdated
End Sub

Private Sub FolderView_LayoutUpdated(sender As Object, e As EventArgs)
ConfigureCompressionDetailsGrid()
ConfigureStopButton()
End Sub

Private Sub ConfigureCompressionDetailsGrid()
Dim grid = FindVisualDescendant(Of DataGrid)(Me)
If grid Is Nothing Then Return

If Not Object.ReferenceEquals(_compressionDetailsGrid, grid) Then
If _compressionDetailsGrid IsNot Nothing Then
_compressionDetailsGrid.RemoveHandler(
ScrollViewer.ScrollChangedEvent,
New ScrollChangedEventHandler(AddressOf CompressionDetailsGrid_ScrollChanged))
End If

_compressionDetailsGrid = grid
_compressionDetailsGrid.AddHandler(
ScrollViewer.ScrollChangedEvent,
New ScrollChangedEventHandler(AddressOf CompressionDetailsGrid_ScrollChanged),
True)
End If

'The details table is designed to fit its available width. Do not expose a horizontal
'scrollbar just because the DataGrid template reserves one for overflow scenarios.
ScrollViewer.SetHorizontalScrollBarVisibility(grid, ScrollBarVisibility.Disabled)
grid.CanUserResizeColumns = True

ConfigureColumnHeaderSeparators(grid)
ConfigureScrollThumb(grid)
End Sub

Private Shared Sub ConfigureColumnHeaderSeparators(grid As DataGrid)
ForEachVisualDescendant(Of DataGridColumnHeader)(
grid,
Sub(header)
'WPF-UI makes the resize boundary obvious only on hover. Keep a subtle line
'visible at all times while preserving the normal resize gripper hit target.
header.BorderBrush = ColumnSeparatorBrush
header.BorderThickness = New Thickness(0, 0, 1, 0)
End Sub)
End Sub

Private Sub ConfigureScrollThumb(grid As DataGrid)
Dim verticalScrollBar = FindVisualDescendant(Of ScrollBar)(
grid,
Function(scrollBar) scrollBar.Orientation = Orientation.Vertical)
If verticalScrollBar Is Nothing Then Return

Dim thumb = FindVisualDescendant(Of Thumb)(verticalScrollBar)
If thumb Is Nothing Then Return

thumb.MinHeight = MinimumScrollThumbHeight

If Object.ReferenceEquals(_scrollThumb, thumb) Then Return

If _scrollThumb IsNot Nothing Then
RemoveHandler _scrollThumb.DragStarted, AddressOf CompressionScrollThumb_DragStarted
RemoveHandler _scrollThumb.DragDelta, AddressOf CompressionScrollThumb_DragDelta
RemoveHandler _scrollThumb.DragCompleted, AddressOf CompressionScrollThumb_DragCompleted
End If

_scrollThumb = thumb
AddHandler _scrollThumb.DragStarted, AddressOf CompressionScrollThumb_DragStarted
AddHandler _scrollThumb.DragDelta, AddressOf CompressionScrollThumb_DragDelta
AddHandler _scrollThumb.DragCompleted, AddressOf CompressionScrollThumb_DragCompleted
End Sub

Private Sub CompressionScrollThumb_DragStarted(sender As Object, e As DragStartedEventArgs)
_isScrollThumbDragging = True
SuppressAutoFollow()
End Sub

Private Sub CompressionScrollThumb_DragDelta(sender As Object, e As DragDeltaEventArgs)
SuppressAutoFollow()
End Sub

Private Sub CompressionScrollThumb_DragCompleted(sender As Object, e As DragCompletedEventArgs)
_isScrollThumbDragging = False
SuppressAutoFollow()
End Sub

Private Sub CompressionDetailsGrid_ScrollChanged(sender As Object, e As ScrollChangedEventArgs)
If _isAutoFollowing Then Return
If e.VerticalChange = 0 Then Return

SuppressAutoFollow()
End Sub

Private Sub SuppressAutoFollow()
_suppressAutoFollowUntil = DateTime.UtcNow.Add(AutoFollowDebounce)
End Sub

Private Sub CompressionDetailsGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
Dim grid = TryCast(sender, DataGrid)
If grid?.SelectedItem Is Nothing Then Return

grid.ScrollIntoView(grid.SelectedItem)
ConfigureCompressionDetailsGrid()

If _isScrollThumbDragging OrElse DateTime.UtcNow < _suppressAutoFollowUntil Then Return

_isAutoFollowing = True
Try
grid.ScrollIntoView(grid.SelectedItem)
grid.UpdateLayout()
Finally
grid.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
New Action(Sub() _isAutoFollowing = False))
End Try
End Sub

Private Sub ConfigureStopButton()
Dim viewModel = TryCast(DataContext, FolderViewModel)
If viewModel Is Nothing Then Return

Dim stopButton = FindVisualDescendant(Of Wpf.Ui.Controls.Button)(
Me,
Function(button) Object.ReferenceEquals(button.Command, viewModel.CancelCommand))
If stopButton Is Nothing OrElse Object.ReferenceEquals(_stopButton, stopButton) Then Return

_stopButton = stopButton
_stopButton.Content = CreateStopIcon()
End Sub

Private Shared Function CreateStopIcon() As FrameworkElement
Dim redBrush = TryCast(Application.Current.TryFindResource("PaletteRedBrush"), Brush)
If redBrush Is Nothing Then redBrush = Brushes.IndianRed

Dim icon As New Grid With {
.Width = 18,
.Height = 18
}

Dim octagon As New Polygon With {
.Points = New PointCollection From {
New Point(6, 1), New Point(12, 1),
New Point(17, 6), New Point(17, 12),
New Point(12, 17), New Point(6, 17),
New Point(1, 12), New Point(1, 6)
},
.Fill = Brushes.Transparent,
.Stroke = redBrush,
.StrokeThickness = 1.7,
.StrokeLineJoin = PenLineJoin.Round
}

Dim stopSquare As New Rectangle With {
.Width = 6,
.Height = 6,
.HorizontalAlignment = HorizontalAlignment.Center,
.VerticalAlignment = VerticalAlignment.Center,
.Fill = redBrush,
.RadiusX = 1,
.RadiusY = 1
}

icon.Children.Add(octagon)
icon.Children.Add(stopSquare)
Return icon
End Function

Private Shared Sub ForEachVisualDescendant(Of T As DependencyObject)(root As DependencyObject, action As Action(Of T))
If root Is Nothing OrElse action Is Nothing Then Return

For index = 0 To VisualTreeHelper.GetChildrenCount(root) - 1
Dim child = VisualTreeHelper.GetChild(root, index)
Dim candidate = TryCast(child, T)
If candidate IsNot Nothing Then action(candidate)

ForEachVisualDescendant(child, action)
Next
End Sub

Private Shared Function FindVisualDescendant(Of T As DependencyObject)(root As DependencyObject, Optional predicate As Func(Of T, Boolean) = Nothing) As T
If root Is Nothing Then Return Nothing

For index = 0 To VisualTreeHelper.GetChildrenCount(root) - 1
Dim child = VisualTreeHelper.GetChild(root, index)
Dim candidate = TryCast(child, T)

If candidate IsNot Nothing AndAlso (predicate Is Nothing OrElse predicate(candidate)) Then
Return candidate
End If

Dim nested = FindVisualDescendant(child, predicate)
If nested IsNot Nothing Then Return nested
Next

Return Nothing
End Function
End Class
41 changes: 40 additions & 1 deletion CompactGUI/Views/Pages/HomePage.xaml.vb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
Class HomePage
Imports System.ComponentModel
Imports System.Windows.Media
Imports System.Windows.Threading

Class HomePage

Private _viewModel As HomeViewModel

Expand All @@ -12,10 +16,45 @@

ScrollViewer.SetCanContentScroll(Me, False)

AddHandler Loaded, AddressOf HomePage_Loaded
AddHandler _viewModel.PropertyChanged, AddressOf ViewModel_PropertyChanged

' Add any initialization after the InitializeComponent() call.

End Sub

Private Sub HomePage_Loaded(sender As Object, e As RoutedEventArgs)
UpdateCompressButtonText()
End Sub

Private Sub ViewModel_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
If e.PropertyName <> NameOf(HomeViewModel.HomeViewModelState) Then Return
Dispatcher.BeginInvoke(AddressOf UpdateCompressButtonText, DispatcherPriority.Loaded)
End Sub

Private Sub UpdateCompressButtonText()
Dim compressButton = FindVisualDescendant(Of Button)(
Me,
Function(button) Object.ReferenceEquals(button.Command, _viewModel.CompressAllCommand))

If compressButton IsNot Nothing Then compressButton.Content = "Compress Now"
End Sub

Private Shared Function FindVisualDescendant(Of T As DependencyObject)(root As DependencyObject, predicate As Func(Of T, Boolean)) As T
If root Is Nothing Then Return Nothing

For index = 0 To VisualTreeHelper.GetChildrenCount(root) - 1
Dim child = VisualTreeHelper.GetChild(root, index)
Dim candidate = TryCast(child, T)
If candidate IsNot Nothing AndAlso predicate(candidate) Then Return candidate

Dim nested = FindVisualDescendant(child, predicate)
If nested IsNot Nothing Then Return nested
Next

Return Nothing
End Function

Private Async Sub AddFolderButton_Click(sender As Object, e As RoutedEventArgs) Handles BtnAddFolder1.Click, BtnAddFolder2.Click
Dim folderBrowser As New Microsoft.Win32.OpenFolderDialog With {
.Title = "Select a folder to compress",
Expand Down
Loading