Skip to content

Commit 22c66ed

Browse files
puppetswyaira2
andauthored
Added rotate left/right button on toolbar when selecting images (#7832)
Co-authored-by: Yair Aichenbaum <39923744+yaichenbaum@users.noreply.github.com>
1 parent c01d483 commit 22c66ed

11 files changed

+136
-5
lines changed

src/Files/Helpers/BitmapHelper.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
using System;
22
using System.IO;
33
using System.Threading.Tasks;
4+
using Windows.Graphics.Imaging;
5+
using Windows.Storage;
6+
using Windows.Storage.Streams;
7+
using Files.Filesystem;
8+
using Files.Filesystem.StorageItems;
49
using Windows.UI.Xaml.Media.Imaging;
510

611
namespace Files.Helpers
@@ -19,5 +24,44 @@ public static async Task<BitmapImage> ToBitmapAsync(this byte[] data)
1924
await image.SetSourceAsync(ms.AsRandomAccessStream());
2025
return image;
2126
}
27+
28+
29+
/// <summary>
30+
/// Rotates the image at the specified file path.
31+
/// </summary>
32+
/// <param name="filePath">The file path to the image.</param>
33+
/// <param name="rotation">The rotation direction.</param>
34+
/// <remarks>
35+
/// https://docs.microsoft.com/en-us/uwp/api/windows.graphics.imaging.bitmapdecoder?view=winrt-22000
36+
/// https://docs.microsoft.com/en-us/uwp/api/windows.graphics.imaging.bitmapencoder?view=winrt-22000
37+
/// </remarks>
38+
public static async Task Rotate(string filePath, BitmapRotation rotation)
39+
{
40+
if (string.IsNullOrEmpty(filePath))
41+
{
42+
return;
43+
}
44+
45+
var file = await StorageHelpers.ToStorageItem<IStorageFile>(filePath);
46+
if (file == null)
47+
{
48+
return;
49+
}
50+
51+
using IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.ReadWrite);
52+
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
53+
54+
using var memStream = new InMemoryRandomAccessStream();
55+
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(memStream, decoder);
56+
57+
encoder.BitmapTransform.Rotation = rotation;
58+
59+
await encoder.FlushAsync();
60+
61+
memStream.Seek(0);
62+
fileStream.Seek(0);
63+
fileStream.Size = 0;
64+
await RandomAccessStream.CopyAsync(memStream, fileStream);
65+
}
2266
}
2367
}

src/Files/Interacts/BaseLayoutCommandImplementationModel.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Windows.ApplicationModel.DataTransfer.DragDrop;
2020
using Windows.Foundation;
2121
using Windows.Foundation.Collections;
22+
using Windows.Graphics.Imaging;
2223
using Windows.Storage;
2324
using Windows.System;
2425
using Windows.UI.Core;
@@ -786,6 +787,20 @@ public async void DecompressArchiveToChildFolder()
786787
}
787788
}
788789

790+
public async void RotateImageLeft()
791+
{
792+
await BitmapHelper.Rotate(PathNormalization.NormalizePath(SlimContentPage?.SelectedItems.First().ItemPath), BitmapRotation.Clockwise270Degrees);
793+
SlimContentPage?.ItemManipulationModel.RefreshItemsThumbnail();
794+
SlimContentPage?.PreviewPaneViewModel.UpdateSelectedItemPreview();
795+
}
796+
797+
public async void RotateImageRight()
798+
{
799+
await BitmapHelper.Rotate(PathNormalization.NormalizePath(SlimContentPage?.SelectedItems.First().ItemPath), BitmapRotation.Clockwise90Degrees);
800+
SlimContentPage?.ItemManipulationModel.RefreshItemsThumbnail();
801+
SlimContentPage?.PreviewPaneViewModel.UpdateSelectedItemPreview();
802+
}
803+
789804
public async void InstallFont()
790805
{
791806
foreach (ListedItem selectedItem in SlimContentPage.SelectedItems)

src/Files/Interacts/BaseLayoutCommandsViewModel.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ private void InitializeCommands()
7272
DecompressArchiveCommand = new RelayCommand(CommandsModel.DecompressArchive);
7373
DecompressArchiveHereCommand = new RelayCommand(CommandsModel.DecompressArchiveHere);
7474
DecompressArchiveToChildFolderCommand = new RelayCommand(CommandsModel.DecompressArchiveToChildFolder);
75+
RotateImageLeftCommand = new RelayCommand(CommandsModel.RotateImageLeft);
76+
RotateImageRightCommand = new RelayCommand(CommandsModel.RotateImageRight);
7577
InstallFontCommand = new RelayCommand(CommandsModel.InstallFont);
7678
}
7779

@@ -169,6 +171,10 @@ private void InitializeCommands()
169171

170172
public ICommand DecompressArchiveToChildFolderCommand { get; private set; }
171173

174+
public ICommand RotateImageLeftCommand { get; private set; }
175+
176+
public ICommand RotateImageRightCommand { get; private set; }
177+
172178
public ICommand InstallFontCommand { get; private set; }
173179

174180
#endregion Commands

src/Files/Interacts/IBaseLayoutCommandImplementationModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ public interface IBaseLayoutCommandImplementationModel : IDisposable
9898

9999
void DecompressArchiveToChildFolder();
100100

101+
void RotateImageLeft();
102+
103+
void RotateImageRight();
104+
101105
void InstallFont();
102106
}
103107
}

src/Files/Interacts/ItemManipulationModel.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class ItemManipulationModel
2828

2929
public event EventHandler RefreshItemsOpacityInvoked;
3030

31+
public event EventHandler RefreshItemsThumbnailInvoked;
32+
3133
public void FocusFileList()
3234
{
3335
FocusFileListInvoked?.Invoke(this, EventArgs.Empty);
@@ -110,5 +112,10 @@ public void RefreshItemsOpacity()
110112
{
111113
RefreshItemsOpacityInvoked?.Invoke(this, EventArgs.Empty);
112114
}
115+
116+
public void RefreshItemsThumbnail()
117+
{
118+
RefreshItemsThumbnailInvoked?.Invoke(this, EventArgs.Empty);
119+
}
113120
}
114121
}

src/Files/Strings/en-US/Resources.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,6 +2729,12 @@ We use App Center to track which settings are being used, find bugs, and fix cra
27292729
<data name="ShowFolderSizesWarning" xml:space="preserve">
27302730
<value>Calculating folder sizes is resource intensive and may cause your CPU usage to increase.</value>
27312731
</data>
2732+
<data name="RotateLeft" xml:space="preserve">
2733+
<value>Rotate left</value>
2734+
</data>
2735+
<data name="RotateRight" xml:space="preserve">
2736+
<value>Rotate right</value>
2737+
</data>
27322738
<data name="Install" xml:space="preserve">
27332739
<value>Install</value>
27342740
</data>

src/Files/UserControls/InnerNavigationToolbar.xaml

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<Grid BorderBrush="{ThemeResource ControlStrokeColorDefault}" BorderThickness="0,0,0,1">
3939
<Grid.ColumnDefinitions>
4040
<ColumnDefinition Width="*" />
41-
<ColumnDefinition Width="*" />
41+
<ColumnDefinition Width="Auto" />
4242
</Grid.ColumnDefinitions>
4343
<CommandBar
4444
x:Name="ContextCommandBar"
@@ -203,12 +203,12 @@
203203
<AppBarSeparator x:Name="AdditionalActionSeparator" x:Load="{x:Bind ViewModel.HasAdditionalAction, Mode=OneWay}" />
204204
<AppBarButton
205205
x:Name="EmptyRecycleBinButton"
206+
x:Load="{x:Bind ViewModel.InstanceViewModel.IsPageTypeRecycleBin, Mode=OneWay, FallbackValue=False}"
206207
AccessKey="B"
207208
Command="{x:Bind ViewModel.EmptyRecycleBinCommand, Mode=OneWay}"
208209
IsEnabled="{x:Bind ViewModel.CanEmptyRecycleBin, Mode=OneWay}"
209210
Label="{helpers:ResourceString Name=EmptyRecycleBin}"
210-
ToolTipService.ToolTip="{helpers:ResourceString Name=EmptyRecycleBin}"
211-
Visibility="{x:Bind ViewModel.InstanceViewModel.IsPageTypeRecycleBin, Mode=OneWay, FallbackValue=Collapsed}">
211+
ToolTipService.ToolTip="{helpers:ResourceString Name=EmptyRecycleBin}">
212212
<AppBarButton.Icon>
213213
<FontIcon FontFamily="{StaticResource RecycleBinIcons}" Glyph="&#xEF88;" />
214214
</AppBarButton.Icon>
@@ -217,11 +217,11 @@
217217
x:Name="ExtractButton"
218218
Width="Auto"
219219
MinWidth="40"
220+
x:Load="{x:Bind ViewModel.CanExtract, Mode=OneWay, FallbackValue=False}"
220221
AccessKey="Z"
221222
IsEnabled="{x:Bind ViewModel.CanExtract, Mode=OneWay, FallbackValue=False}"
222223
Label="{helpers:ResourceString Name=Extract}"
223-
LabelPosition="Default"
224-
Visibility="{x:Bind ViewModel.CanExtract, Mode=OneWay, FallbackValue=Collapsed}">
224+
LabelPosition="Default">
225225
<AppBarButton.Content>
226226
<FontIcon FontFamily="{StaticResource CustomGlyph}" Glyph="&#xF11A;" />
227227
</AppBarButton.Content>
@@ -263,6 +263,8 @@
263263
</AppBarButton>
264264
<AppBarButton
265265
x:Name="RunWithPowerShellButton"
266+
Width="Auto"
267+
MinWidth="40"
266268
x:Load="{x:Bind ViewModel.IsPowerShellScript, Mode=OneWay, FallbackValue=False}"
267269
AutomationProperties.Name="RunWithPowerShell"
268270
Command="{x:Bind ViewModel.RunWithPowerShellCommand, Mode=OneWay}"
@@ -275,6 +277,8 @@
275277
</AppBarButton>
276278
<AppBarButton
277279
x:Name="SetAsBackgroundButton"
280+
Width="Auto"
281+
MinWidth="40"
278282
x:Load="{x:Bind ViewModel.IsImage, Mode=OneWay, FallbackValue=False}"
279283
Command="{x:Bind ViewModel.SetAsBackgroundCommand, Mode=OneWay}"
280284
Label="{helpers:ResourceString Name=SetAsBackground}"
@@ -284,6 +288,28 @@
284288
<FontIcon Glyph="&#xE91B;" />
285289
</AppBarButton.Icon>
286290
</AppBarButton>
291+
<AppBarButton
292+
x:Name="RotateImageLeftButton"
293+
x:Load="{x:Bind ViewModel.IsImage, Mode=OneWay, FallbackValue=False}"
294+
Command="{x:Bind ViewModel.RotateImageLeftCommand, Mode=OneWay}"
295+
Label="{helpers:ResourceString Name=RotateLeft}"
296+
LabelPosition="Default"
297+
ToolTipService.ToolTip="{helpers:ResourceString Name=RotateLeft}">
298+
<AppBarButton.Icon>
299+
<FontIcon Glyph="&#xE7A7;" />
300+
</AppBarButton.Icon>
301+
</AppBarButton>
302+
<AppBarButton
303+
x:Name="RotateImageRightButton"
304+
x:Load="{x:Bind ViewModel.IsImage, Mode=OneWay, FallbackValue=False}"
305+
Command="{x:Bind ViewModel.RotateImageRightCommand, Mode=OneWay}"
306+
Label="{helpers:ResourceString Name=RotateRight}"
307+
LabelPosition="Default"
308+
ToolTipService.ToolTip="{helpers:ResourceString Name=RotateRight}">
309+
<AppBarButton.Icon>
310+
<FontIcon Glyph="&#xE7A6;" />
311+
</AppBarButton.Icon>
312+
</AppBarButton>
287313
<AppBarButton
288314
x:Name="InstallFontButton"
289315
x:Load="{x:Bind ViewModel.IsFont, Mode=OneWay, FallbackValue=False}"

src/Files/ViewModels/NavToolbarViewModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,10 @@ public void SearchRegion_LostFocus(object sender, RoutedEventArgs e)
808808

809809
public ICommand SetAsBackgroundCommand { get; set; }
810810

811+
public ICommand RotateImageLeftCommand { get; set; }
812+
813+
public ICommand RotateImageRightCommand { get; set; }
814+
811815
public ICommand InstallFontCommand { get; set; }
812816

813817
public async Task SetPathBoxDropDownFlyoutAsync(MenuFlyout flyout, PathBoxItem pathItem, IShellPage shellPage)

src/Files/Views/ColumnShellPage.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ private void InitToolbarCommands()
225225
NavToolbarViewModel.ExtractCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.DecompressArchiveCommand.Execute(null));
226226
NavToolbarViewModel.ExtractHereCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.DecompressArchiveHereCommand.Execute(null));
227227
NavToolbarViewModel.ExtractToCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.DecompressArchiveToChildFolderCommand.Execute(null));
228+
NavToolbarViewModel.RotateImageLeftCommand = new RelayCommand(async () => SlimContentPage?.CommandsViewModel.RotateImageLeftCommand.Execute(null));
229+
NavToolbarViewModel.RotateImageRightCommand = new RelayCommand(async () => SlimContentPage?.CommandsViewModel.RotateImageRightCommand.Execute(null));
228230
NavToolbarViewModel.InstallFontCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.InstallFontCommand.Execute(null));
229231
}
230232

src/Files/Views/LayoutModes/GridViewBrowser.xaml.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.Toolkit.Uwp.UI;
99
using System;
1010
using System.Collections.Generic;
11+
using System.IO;
1112
using System.Linq;
1213
using System.Threading.Tasks;
1314
using Windows.Storage;
@@ -51,6 +52,13 @@ protected override void HookEvents()
5152
ItemManipulationModel.FocusSelectedItemsInvoked += ItemManipulationModel_FocusSelectedItemsInvoked;
5253
ItemManipulationModel.StartRenameItemInvoked += ItemManipulationModel_StartRenameItemInvoked;
5354
ItemManipulationModel.ScrollIntoViewInvoked += ItemManipulationModel_ScrollIntoViewInvoked;
55+
ItemManipulationModel.RefreshItemsThumbnailInvoked += ItemManipulationModel_RefreshItemThumbnail;
56+
57+
}
58+
59+
private void ItemManipulationModel_RefreshItemThumbnail(object sender, EventArgs args)
60+
{
61+
ReloadSelectedItemIcon();
5462
}
5563

5664
private void ItemManipulationModel_ScrollIntoViewInvoked(object sender, ListedItem e)
@@ -496,6 +504,13 @@ private async void ReloadItemIcons()
496504
}
497505
}
498506

507+
private async void ReloadSelectedItemIcon()
508+
{
509+
ParentShellPageInstance.FilesystemViewModel.CancelExtendedPropertiesLoading();
510+
ParentShellPageInstance.SlimContentPage.SelectedItem.ItemPropertiesInitialized = false;
511+
await ParentShellPageInstance.FilesystemViewModel.LoadExtendedItemProperties(ParentShellPageInstance.SlimContentPage.SelectedItem, currentIconSize);
512+
}
513+
499514
private void FileList_ItemTapped(object sender, TappedRoutedEventArgs e)
500515
{
501516
var ctrlPressed = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);

src/Files/Views/ModernShellPage.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ private void InitToolbarCommands()
209209
NavToolbarViewModel.ExtractCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.DecompressArchiveCommand.Execute(null));
210210
NavToolbarViewModel.ExtractHereCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.DecompressArchiveHereCommand.Execute(null));
211211
NavToolbarViewModel.ExtractToCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.DecompressArchiveToChildFolderCommand.Execute(null));
212+
NavToolbarViewModel.RotateImageLeftCommand = new RelayCommand(async () => SlimContentPage?.CommandsViewModel.RotateImageLeftCommand.Execute(null));
213+
NavToolbarViewModel.RotateImageRightCommand = new RelayCommand(async () => SlimContentPage?.CommandsViewModel.RotateImageRightCommand.Execute(null));
212214
NavToolbarViewModel.InstallFontCommand = new RelayCommand(() => SlimContentPage?.CommandsViewModel.InstallFontCommand.Execute(null));
213215
}
214216

0 commit comments

Comments
 (0)