Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
5851899
First Commit. WIP native WinUI Interface
blendermf May 28, 2023
08e9617
Created custom TitleBar control, will Animate height between pages.
blendermf May 28, 2023
afb74db
Removed Titlebar Height Animation (clickable controls there are buggy)
blendermf May 28, 2023
d5c3b52
Added back titlebar animation with plans for controls there
blendermf May 29, 2023
67161c1
Handle the window being inactive correctly in TitleBar
blendermf May 29, 2023
9ccfa97
Now will load images when the list is bigger than the cache.
blendermf May 30, 2023
4b229e3
Implemented (at least the visuals) of the Titlebars
blendermf May 31, 2023
cb84e44
Hooked up Previous/Next buttons to the slideshow logic.
blendermf Jun 1, 2023
f69e1a4
Updates styles
blendermf Jun 1, 2023
4d30a4d
Added Grayscale filter functionality
blendermf Jun 3, 2023
d6d9d5c
Added delay to titlebar layout changes that happen instantaneously
blendermf Jun 3, 2023
6350036
Added Slide Timer functionality
blendermf Jun 3, 2023
e6e81a7
Ability to Add Folders
blendermf Jun 12, 2023
7a7a51e
Adjusted padding in the ItemTemplate
blendermf Jun 12, 2023
fd45390
Fixed folder list loading after multiple page switches
blendermf Jun 12, 2023
a720c86
Various Fixes and Changes
blendermf Mar 17, 2025
ca86e9d
Removed unecessary style for MFFolderListViewItem that no longer exists
blendermf Mar 17, 2025
1ba77b2
Made image counting properly async
blendermf Mar 18, 2025
ae86b8e
Created custom ImageFolder control (that's used for listview items)
blendermf Mar 18, 2025
539635e
Changed how column width syncing works
blendermf Mar 19, 2025
2cb73cf
Moved data properly into models.
blendermf Mar 20, 2025
46e5a66
Updated to AppSDK 1.7 stable
blendermf Mar 21, 2025
8683a1e
Save folder selection state
blendermf Mar 21, 2025
cf904fe
Removed the NotifyCollectonChangedActon.Move handling
blendermf Mar 22, 2025
0cc4eac
Select/Deselect All functionality
blendermf Mar 22, 2025
10597db
Removed placeholder images in slide view, now actually loads them fro…
blendermf Mar 22, 2025
d949728
Set SlideShow timer interval back to the proper 1s
blendermf Mar 22, 2025
ebb7f68
Moved all image/folder getting/counting to MFImageFolderList model
blendermf Mar 22, 2025
766dc4a
Images are now shuffled properly
blendermf Mar 23, 2025
aa5922f
Made Next/Previous buttons properly reset the timer
blendermf Mar 23, 2025
0b50e08
Properly Set/Load/Use the duration slider value
blendermf Mar 23, 2025
8d43216
Delete folder button now works.
blendermf Mar 27, 2025
9300fef
Removed WinUIEx
blendermf Mar 27, 2025
2a01093
Removed WebView2 package reference (not sure why we ever had this in …
blendermf Mar 27, 2025
7ea4b21
Changed Window member of App to a static field
blendermf Mar 27, 2025
7bf25dc
Converted whole project to use nullable
blendermf Mar 27, 2025
373f961
Open Folder button is now functional
blendermf Mar 27, 2025
30ddc7b
Images now open in explorer when clicked in the slide view
blendermf Mar 27, 2025
a122585
Syncfusion license now gets embedded in the assembly
blendermf Mar 27, 2025
8702480
Hide the pause button if there is no time limit
blendermf Mar 27, 2025
f6f34cb
Refreshing folders now works
blendermf Mar 29, 2025
951424a
Decreased artificial delay on folder refreshes to a much snappier number
blendermf Mar 29, 2025
9fb5cb5
Initial work to properly use the MVVM pattern
blendermf Sep 11, 2025
f12d53f
Housekeeping
blendermf Sep 12, 2025
d429a06
Changed license file naming and location
blendermf Sep 12, 2025
3ff06da
Implemented the title bar user controls
blendermf Sep 16, 2025
aa87c81
Added ViewModels for MainPage, SlidePage and ImageFolder
blendermf Sep 16, 2025
6760688
Started work on revamped SlidePage
blendermf Sep 19, 2025
8fd7d93
Added option to select the navigation transition animation
blendermf Sep 19, 2025
203bcbc
Moved titlebar appearance initialization to the titlebar service
blendermf Sep 19, 2025
a81741b
Reimplemented minimum window size
blendermf Sep 19, 2025
83fe22e
Implemented back button on the slide page
blendermf Sep 19, 2025
bffda74
New Titlebar controls now have all the visual functionality of the old
blendermf Sep 19, 2025
afb7f3e
Fixed some titlebar bugs
blendermf Sep 19, 2025
6e02b8c
Implemented most of the new SlidePage / SlideViewModel
blendermf Sep 24, 2025
28d68f5
Simplified slide image drawing
blendermf Sep 30, 2025
c9143d2
Simplified the image load queue using Channel<T>
blendermf Sep 30, 2025
56b56d3
Properly clean up events
blendermf Sep 30, 2025
5fbbb50
Moved the StartTimer call outside of a call started from CreateResources
blendermf Sep 30, 2025
a85ed3e
Updated SDKs
blendermf Oct 18, 2025
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
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,14 @@ project.lock.json

WebSrc/language-server-log.txt

._*
._*


## Dev Licence Files

**/devlics
**/devlics/*
*.devlic
*.license
language-server-log.txt
QuickDrawWindows/Properties/launchSettings.json
27 changes: 27 additions & 0 deletions QuickDraw.Core/Models/ImageFolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace QuickDraw.Core.Models;

public class ImageFolder
{
public string Path { get; }
public int ImageCount { get; set; }

[JsonIgnore]
public bool IsLoading { get; set; } = false;

public bool Selected { get; set; } = false;

public ImageFolder(string path, int imageCount = 0, bool selected = false, bool isLoading = false)
{
Path = path;
ImageCount = imageCount;
Selected = selected;
IsLoading = isLoading;
}
}
142 changes: 142 additions & 0 deletions QuickDraw.Core/Models/ImageFolderList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System.Collections.Concurrent;
using System.Collections.Specialized;

namespace QuickDraw.Core.Models;

public class ImageFolderList : INotifyCollectionChanged
{
public List<ImageFolder> ImageFolders { get; set; } = [];

public event NotifyCollectionChangedEventHandler? CollectionChanged;

private static async Task<IEnumerable<string>> GetImagesForFolder(string filepath)
{
return await Task.Run(() =>
{
var enumerationOptions = new EnumerationOptions
{
IgnoreInaccessible = true,
RecurseSubdirectories = true,
AttributesToSkip = System.IO.FileAttributes.Hidden | System.IO.FileAttributes.System | System.IO.FileAttributes.ReparsePoint
};

IEnumerable<string> files = Directory.EnumerateFiles(filepath, "*.*", enumerationOptions)
.Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)
|| s.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)
|| s.EndsWith(".png", StringComparison.OrdinalIgnoreCase));

return files;
});
}

public static async Task<IEnumerable<string>> GetImagesForFolders(IEnumerable<string> folders)
{
ConcurrentBag<string> images = [];

await Parallel.ForEachAsync<string>(folders, async (folder, ct) =>
{
IEnumerable<string> files = await GetImagesForFolder(folder);

foreach (var file in files)
{
images.Add(file);
}
});

return images;
}

public void UpdateFolderCount(ImageFolder existingFolder)
{
var folder = new ImageFolder(existingFolder.Path, existingFolder.ImageCount, existingFolder.Selected, true);
var folderIndex = ImageFolders.IndexOf(existingFolder);

if (folderIndex == -1)
{
return; // Folder isn't in list, TODO: should log
}

ImageFolders[folderIndex] = folder;
CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Replace, folder, existingFolder, folderIndex));

LoadFolderCount(folder);
}

public void UpdateFolderCounts()
{
List<ImageFolder> folders = [.. ImageFolders];
foreach(var folder in folders)
{
UpdateFolderCount(folder);
}
}

private void LoadFolderCount(ImageFolder folder)
{
Task.Run(async () =>
{
await Task.Delay(200);
return await GetImagesForFolder(folder.Path);
}).ContinueWith((t) =>
{
if (t.IsFaulted)
{
// Log error
}
else
{
folder.ImageCount = t.Result.Count();
folder.IsLoading = false;

var existingFolder = ImageFolders.FirstOrDefault<ImageFolder>((f) => f.Path == folder.Path);
var folderIndex = existingFolder != null ? ImageFolders.IndexOf(existingFolder) : -1;

if (folderIndex != -1)
{
ImageFolders[folderIndex] = folder;
CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Replace, folder, existingFolder, folderIndex));
}
}
});
}

public void AddFolderPath(string path)
{

var (folderIndex, existingFolder) = ImageFolders.Index().FirstOrDefault((ft) => ft.Item.Path == path);
ImageFolder? folder = null;

if (existingFolder != null)
{
folder = new ImageFolder(existingFolder.Path, existingFolder.ImageCount, existingFolder.Selected, true);
ImageFolders[folderIndex] = folder;
CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Replace, folder, existingFolder, folderIndex));
}
else
{
folder = new ImageFolder(path, 0, false, true);
ImageFolders.Add(folder);
CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Add, folder));
}

if (folder != null)
{
LoadFolderCount(folder);
}
}

public void AddFolderPaths(IEnumerable<string> paths)
{
// TODO: Add paths in order, then load image counts in parallel
foreach (var path in paths)
{
AddFolderPath(path);
}
}

public void RemoveFolder(ImageFolder folder)
{
ImageFolders.Remove(folder);
CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Remove, folder));
}
}
52 changes: 52 additions & 0 deletions QuickDraw.Core/Models/Settings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

namespace QuickDraw.Core.Models;

public enum TimerEnum
{
[Display(Name = "30s")]
T30s,
[Display(Name = "1m")]
T1m,
[Display(Name = "2m")]
T2m,
[Display(Name = "5m")]
T5m,
[Display(Name = "No Limit")]
NoLimit
};

public static class TimerEnumExtension
{
private static Dictionary<TimerEnum, uint> TimerEnumToSeconds { get; } = new()
{
{ TimerEnum.T30s, 30 },
{ TimerEnum.T1m, 60 },
{ TimerEnum.T2m, 120 },
{ TimerEnum.T5m, 300 },
{ TimerEnum.NoLimit, 0 }
};

public static uint ToSeconds(this TimerEnum e)
{
return TimerEnumToSeconds[e];
}

public static double ToSliderValue(this TimerEnum e)
{
return (double)((int)e);
}

public static TimerEnum ToTimerEnum(this double e)
{
return (TimerEnum)(Math.Clamp((int)e,0, 4));
}
}

public class Settings
{
public ImageFolderList ImageFolderList { get; set; } = new ImageFolderList();

public TimerEnum SlideTimerDuration { get; set; }
}
9 changes: 9 additions & 0 deletions QuickDraw.Core/QuickDraw.Core.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
58 changes: 50 additions & 8 deletions QuickDraw.sln
Original file line number Diff line number Diff line change
@@ -1,25 +1,67 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31025.218
# Visual Studio Version 18
VisualStudioVersion = 18.0.11010.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickDraw", "QuickDrawWindows\QuickDraw.csproj", "{31B19DB2-2047-44DC-8C27-A34C113171CF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickDraw", "QuickDrawWindows\QuickDraw.csproj", "{CBC43D79-6F32-480A-BC3A-54383C19E4DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickDraw.Core", "QuickDraw.Core\QuickDraw.Core.csproj", "{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{31B19DB2-2047-44DC-8C27-A34C113171CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31B19DB2-2047-44DC-8C27-A34C113171CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31B19DB2-2047-44DC-8C27-A34C113171CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31B19DB2-2047-44DC-8C27-A34C113171CF}.Release|Any CPU.Build.0 = Release|Any CPU
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|Any CPU.ActiveCfg = Debug|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|Any CPU.Build.0 = Debug|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|ARM64.Build.0 = Debug|ARM64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|ARM64.Deploy.0 = Debug|ARM64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x64.ActiveCfg = Debug|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x64.Build.0 = Debug|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x64.Deploy.0 = Debug|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x86.ActiveCfg = Debug|x86
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x86.Build.0 = Debug|x86
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x86.Deploy.0 = Debug|x86
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|Any CPU.ActiveCfg = Release|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|Any CPU.Build.0 = Release|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|ARM64.ActiveCfg = Release|ARM64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|ARM64.Build.0 = Release|ARM64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|ARM64.Deploy.0 = Release|ARM64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x64.ActiveCfg = Release|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x64.Build.0 = Release|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x64.Deploy.0 = Release|x64
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x86.ActiveCfg = Release|x86
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x86.Build.0 = Release|x86
{CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x86.Deploy.0 = Release|x86
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|ARM64.Build.0 = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|x64.ActiveCfg = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|x64.Build.0 = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|x86.ActiveCfg = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Debug|x86.Build.0 = Debug|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|Any CPU.Build.0 = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|ARM64.ActiveCfg = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|ARM64.Build.0 = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|x64.ActiveCfg = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|x64.Build.0 = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|x86.ActiveCfg = Release|Any CPU
{DC397900-3CE2-426A-B15B-A7CA3C2EEE2C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03EDC6A8-A6C7-4B32-B248-CB1593722EC5}
SolutionGuid = {9BC0E5F9-D71B-443D-90F5-DBF6C0A50453}
EndGlobalSection
EndGlobal
17 changes: 17 additions & 0 deletions QuickDrawWindows/Activation/ActivationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Threading.Tasks;

namespace QuickDraw.Activation;

public abstract class ActivationHandler<T> : IActivationHandler
where T : class
{
// Override this method to add the logic for whether to handle the activation.
protected virtual bool CanHandleInternal(T args) => true;

// Override this method to add the logic for your activation handler.
protected abstract Task HandleInternalAsync(T args);

public bool CanHandle(object args) => args is T && CanHandleInternal((args as T)!);

public async Task HandleAsync(object args) => await HandleInternalAsync((args as T)!);
}
26 changes: 26 additions & 0 deletions QuickDrawWindows/Activation/DefaultActivationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml;
using QuickDraw.Contracts.Services;
using QuickDraw.ViewModels;
using System.Threading.Tasks;

namespace QuickDraw.Activation;

public class DefaultActivationHandler : ActivationHandler<LaunchActivatedEventArgs>
{
private readonly INavigationService _navigationService;

public DefaultActivationHandler(INavigationService navigationService) => _navigationService = navigationService;

protected override bool CanHandleInternal(LaunchActivatedEventArgs args)
{
// None of the ActivationHandlers has handled the activation.
return _navigationService.Frame?.Content == null;
}

protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
{
_navigationService.NavigateTo(typeof(MainViewModel).FullName!, args.Arguments);

await Task.CompletedTask;
}
}
10 changes: 10 additions & 0 deletions QuickDrawWindows/Activation/IActivationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;

namespace QuickDraw.Activation;

public interface IActivationHandler
{
bool CanHandle(object args);

Task HandleAsync(object args);
}
Loading