Skip to content

Commit 82e1890

Browse files
committed
Do some work porting the ImagesView
1 parent 261a9db commit 82e1890

11 files changed

Lines changed: 318 additions & 13 deletions

src/ImageSort.Avalonia/App.axaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
33
x:Class="ImageSort.Avalonia.App"
44
xmlns:local="using:ImageSort.Avalonia"
5+
xmlns:converters="using:ImageSort.Avalonia.Converters"
56
RequestedThemeVariant="Default">
67
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
78

9+
<Application.Resources>
10+
<converters:PathToBitmapConverter x:Key="PathToBitmapConverter"/>
11+
<converters:PathToFilenameConverter x:Key="PathToFilenameConverter"/>
12+
</Application.Resources>
13+
814
<Application.DataTemplates>
915
<local:ViewLocator/>
1016
</Application.DataTemplates>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Avalonia.Data.Converters;
2+
using Avalonia.Media.Imaging;
3+
using System;
4+
using System.Globalization;
5+
using System.IO;
6+
7+
namespace ImageSort.Avalonia.Converters
8+
{
9+
public class PathToBitmapConverter : IValueConverter
10+
{
11+
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
12+
{
13+
if (value is string path && !string.IsNullOrEmpty(path) && File.Exists(path))
14+
{
15+
try
16+
{
17+
return new Bitmap(path);
18+
}
19+
catch (Exception ex)
20+
{
21+
System.Diagnostics.Debug.WriteLine($"Error loading image {path}: {ex.Message}");
22+
return null;
23+
}
24+
}
25+
return null;
26+
}
27+
28+
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
29+
{
30+
throw new NotImplementedException();
31+
}
32+
}
33+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Avalonia.Data.Converters;
2+
using System;
3+
using System.Globalization;
4+
using System.IO;
5+
6+
namespace ImageSort.Avalonia.Converters
7+
{
8+
public class PathToFilenameConverter : IValueConverter
9+
{
10+
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
11+
{
12+
if (value is string path && !string.IsNullOrEmpty(path))
13+
{
14+
try
15+
{
16+
return Path.GetFileName(path);
17+
}
18+
catch (Exception ex)
19+
{
20+
System.Diagnostics.Debug.WriteLine($"Error getting filename from {path}: {ex.Message}");
21+
return path; // Fallback to full path on error
22+
}
23+
}
24+
return string.Empty;
25+
}
26+
27+
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
28+
{
29+
throw new NotImplementedException();
30+
}
31+
}
32+
}

src/ImageSort.Avalonia/ImageSort.Avalonia.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
3131
<PackageReference Include="System.Reactive" Version="6.0.1" />
3232
<PackageReference Include="BinToss.GroupBox.Avalonia" Version="1.0.0" />
33+
<PackageReference Include="MessageBox.Avalonia" Version="3.1.0" />
3334
<!-- Add AdonisUI packages if you plan to use them -->
3435
<!-- <PackageReference Include="AdonisUI.Avalonia" Version="X.Y.Z" /> -->
3536
<!-- <PackageReference Include="AdonisUI.ClassicTheme.Avalonia" Version="X.Y.Z" /> -->

src/ImageSort.Avalonia/ViewModels/MainWindowViewModel.cs

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
using Avalonia.Controls.ApplicationLifetimes; // Added for IClassicDesktopStyleApplicationLifetime
1010
using Application = Avalonia.Application; // Added for Application.Current
1111
using System.Linq; // Added for .Any()
12+
using MsBox.Avalonia; // For message boxes
13+
using MsBox.Avalonia.Enums; // For message box button/icon enums
14+
using System.Threading.Tasks; // For Task
15+
using ImageSort.Avalonia.Views; // For InputDialog
1216

1317
namespace ImageSort.Avalonia.ViewModels;
1418

@@ -67,9 +71,79 @@ public MainWindowViewModel(FoldersViewModel foldersViewModel, ImagesViewModel im
6771
interaction.SetOutput(null);
6872
}
6973
});
70-
}
7174

72-
// Remove placeholder properties like Greeting and commands,
73-
// as they are now inherited from ImageSort.ViewModels.MainViewModel
74-
// e.g., public ReactiveCommand<Unit, Unit> OpenFolder { get; } is in MainViewModel
75+
// Handler for the FoldersViewModel.SelectFolder interaction (used by Pin command)
76+
this.Folders.SelectFolder.RegisterHandler(async interaction =>
77+
{
78+
var topLevel = TopLevel.GetTopLevel(null);
79+
if (topLevel == null)
80+
{
81+
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
82+
{
83+
topLevel = desktopLifetime.MainWindow;
84+
}
85+
86+
if (topLevel == null)
87+
{
88+
interaction.SetOutput(null);
89+
return;
90+
}
91+
}
92+
93+
var result = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
94+
{
95+
Title = "Select Folder to Pin",
96+
AllowMultiple = false
97+
});
98+
99+
if (result.Any())
100+
{
101+
interaction.SetOutput(result[0].Path.LocalPath);
102+
}
103+
else
104+
{
105+
interaction.SetOutput(null);
106+
}
107+
});
108+
109+
// Handler for ImagesViewModel.PromptForNewFileName
110+
this.Images.PromptForNewFileName.RegisterHandler(async interaction =>
111+
{
112+
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
113+
if (mainWindow == null)
114+
{
115+
interaction.SetOutput(null);
116+
return;
117+
}
118+
119+
var dialog = new InputDialog // Assuming we'll create this view
120+
{
121+
Title = "Rename File",
122+
// We can pass the current name as a default or placeholder if needed
123+
};
124+
125+
var result = await dialog.ShowDialog<string>(mainWindow);
126+
127+
interaction.SetOutput(result);
128+
});
129+
130+
// Handler for ImagesViewModel.NotifyUserOfError
131+
this.Images.NotifyUserOfError.RegisterHandler(async interaction =>
132+
{
133+
var message = interaction.Input;
134+
var box = MessageBoxManager.GetMessageBoxStandard("Error", message, ButtonEnum.Ok, Icon.Error);
135+
136+
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
137+
if (mainWindow != null)
138+
{
139+
await box.ShowWindowDialogAsync(mainWindow);
140+
}
141+
else
142+
{
143+
await box.ShowAsync(); // Show as a standalone window if main window not found
144+
}
145+
146+
interaction.SetOutput(Unit.Default);
147+
});
148+
}
75149
}

src/ImageSort.Avalonia/Views/FoldersView.axaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
</Design.DataContext>
1313
<DockPanel>
1414
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
15+
<Button Command="{Binding Pin}">Pin Folder...</Button> <!-- Added this button -->
1516
<Button Command="{Binding PinSelected}">Pin Selected</Button>
1617
<Button Command="{Binding UnpinSelected}">Unpin Selected</Button>
1718
<Button Command="{Binding CreateFolderUnderSelected}">Create Folder in Selected</Button>
Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,50 @@
1-
<UserControl xmlns="https://github.com/avaloniaui"
1+
<UserControl x:Class="ImageSort.Avalonia.Views.ImagesView"
2+
xmlns="https://github.com/avaloniaui"
23
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3-
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
56
xmlns:vm="using:ImageSort.ViewModels"
6-
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
7-
x:Class="ImageSort.Avalonia.Views.ImagesView"
8-
x:DataType="vm:ImagesViewModel">
9-
<Design.DataContext>
10-
<vm:ImagesViewModel/>
11-
</Design.DataContext>
12-
<TextBlock Text="Images View Content (Placeholder)" HorizontalAlignment="Center" VerticalAlignment="Center"/>
7+
xmlns:metadata="using:ImageSort.Avalonia.Views.Metadata"
8+
x:DataType="vm:ImagesViewModel"
9+
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
10+
<Design.DataContext>
11+
<vm:ImagesViewModel />
12+
</Design.DataContext>
13+
14+
<Grid RowDefinitions="*, auto" ColumnDefinitions="*, auto, auto">
15+
<Image x:Name="SelectedImageDisplay" Source="{Binding SelectedImage, Converter={StaticResource PathToBitmapConverter}}" Grid.Column="0" Margin="0,0,0,4" />
16+
17+
<GridSplitter Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" Background="Gray"/>
18+
19+
<metadata:MetadataView Grid.Column="2" DataContext="{Binding Metadata}" MinWidth="150"/>
20+
21+
<Grid Grid.Row="1" Grid.ColumnSpan="3" ColumnDefinitions="auto, *, auto" Margin="0,4,0,0">
22+
<Button x:Name="GoLeftButton" Content="" Command="{Binding GoLeft}" Grid.Column="0" VerticalAlignment="Stretch" MinWidth="40"/>
23+
24+
<Grid Grid.Column="1" RowDefinitions="auto, *" Margin="4,0">
25+
<Grid ColumnDefinitions="*, auto">
26+
<TextBox x:Name="SearchTermTextBox" Text="{Binding SearchTerm, Mode=TwoWay}" Watermark="Search..." Grid.Column="0" VerticalAlignment="Center"/>
27+
<Button x:Name="RenameButton" Content="Rename" Command="{Binding RenameImage}" Grid.Column="1" Margin="4,0,0,0"/>
28+
</Grid>
29+
30+
<ListBox x:Name="ImagesListBox" ItemsSource="{Binding Images}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" Grid.Row="1" Margin="0,4,0,0">
31+
<ListBox.ItemsPanel>
32+
<ItemsPanelTemplate>
33+
<VirtualizingStackPanel Orientation="Horizontal" />
34+
</ItemsPanelTemplate>
35+
</ListBox.ItemsPanel>
36+
<ListBox.ItemTemplate>
37+
<DataTemplate DataType="{x:Type x:String}">
38+
<StackPanel Orientation="Vertical" Height="100" Width="120" Spacing="2" ToolTip.Tip="{Binding}">
39+
<Image Source="{Binding Converter={StaticResource PathToBitmapConverter}}" MaxWidth="118" MaxHeight="78" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center"/>
40+
<TextBlock Text="{Binding Converter={StaticResource PathToFilenameConverter}}" HorizontalAlignment="Center" TextTrimming="CharacterEllipsis" MaxWidth="118" TextAlignment="Center"/>
41+
</StackPanel>
42+
</DataTemplate>
43+
</ListBox.ItemTemplate>
44+
</ListBox>
45+
</Grid>
46+
47+
<Button x:Name="GoRightButton" Content="" Command="{Binding GoRight}" Grid.Column="2" VerticalAlignment="Stretch" MinWidth="40"/>
48+
</Grid>
49+
</Grid>
1350
</UserControl>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Window xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="150"
6+
x:Class="ImageSort.Avalonia.Views.InputDialog"
7+
Title="Input" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterOwner">
8+
<StackPanel Spacing="10" Margin="15">
9+
<TextBlock x:Name="MessageTextBlock" Text="Enter value:"/>
10+
<TextBox x:Name="InputTextBox" MinWidth="250"/>
11+
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
12+
<Button x:Name="OkButton" Content="OK" IsDefault="True"/>
13+
<Button x:Name="CancelButton" Content="Cancel" IsCancel="True"/>
14+
</StackPanel>
15+
</StackPanel>
16+
</Window>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Input;
3+
using Avalonia.Interactivity;
4+
using System.Threading.Tasks;
5+
6+
namespace ImageSort.Avalonia.Views;
7+
8+
public partial class InputDialog : Window
9+
{
10+
public string InputText { get; private set; }
11+
12+
public InputDialog()
13+
{
14+
InitializeComponent();
15+
OkButton.Click += (_, __) => CloseDialog(true);
16+
CancelButton.Click += (_, __) => CloseDialog(false);
17+
InputTextBox.KeyDown += (s, e) =>
18+
{
19+
if (e.Key == Key.Enter)
20+
{
21+
CloseDialog(true);
22+
}
23+
else if (e.Key == Key.Escape)
24+
{
25+
CloseDialog(false);
26+
}
27+
};
28+
}
29+
30+
private void CloseDialog(bool success)
31+
{
32+
if (success)
33+
{
34+
InputText = InputTextBox.Text;
35+
Close(InputText);
36+
}
37+
else
38+
{
39+
Close(null);
40+
}
41+
}
42+
43+
// Optional: Method to set initial text or message
44+
public void SetParameters(string title, string message, string defaultInput = null)
45+
{
46+
Title = title;
47+
MessageTextBlock.Text = message;
48+
if (defaultInput != null)
49+
{
50+
InputTextBox.Text = defaultInput;
51+
}
52+
InputTextBox.Focus();
53+
InputTextBox.SelectAll();
54+
}
55+
56+
// Static method to show the dialog easily
57+
public static async Task<string> ShowAsync(Window parent, string title, string message, string defaultInput = null)
58+
{
59+
var dialog = new InputDialog();
60+
dialog.SetParameters(title, message, defaultInput);
61+
return await dialog.ShowDialog<string>(parent);
62+
}
63+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<UserControl x:Class="ImageSort.Avalonia.Views.Metadata.MetadataView"
2+
xmlns="https://github.com/avaloniaui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:vm="clr-namespace:ImageSort.ViewModels.Metadata;assembly=ImageSort"
7+
xmlns:text="clr-namespace:ImageSort.Localization;assembly=ImageSort.Localization"
8+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
9+
x:DataType="vm:MetadataViewModel">
10+
<Grid ColumnDefinitions="Auto,*">
11+
<ToggleButton x:Name="ShowMetadataButton" Grid.Column="0">
12+
<TextBlock Text="{x:Static text:Text.MetadataPanelHeader}"
13+
VerticalAlignment="Center">
14+
<TextBlock.RenderTransform>
15+
<RotateTransform Angle="90" />
16+
</TextBlock.RenderTransform>
17+
</TextBlock>
18+
</ToggleButton>
19+
<ScrollViewer x:Name="MetadataArea" Grid.Column="1"
20+
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
21+
<ItemsControl x:Name="Directories" ItemsSource="{Binding Directories}"/>
22+
</ScrollViewer>
23+
</Grid>
24+
</UserControl>

0 commit comments

Comments
 (0)