Browse Source

Merge with origin

ArtemK123 4 years ago
parent
commit
bd7b318205
75 changed files with 3619 additions and 2094 deletions
  1. 68 7
      Installer/installer-setup-x64-light.iss
  2. 0 63
      Installer/installer-setup-x64.iss
  3. 69 8
      Installer/installer-setup-x86-light.iss
  4. 0 63
      Installer/installer-setup-x86.iss
  5. 49 0
      Installer/scripts/isxdl/chinese.ini
  6. 53 0
      Installer/scripts/isxdl/czech.ini
  7. 49 0
      Installer/scripts/isxdl/dutch.ini
  8. 49 0
      Installer/scripts/isxdl/english.ini
  9. 45 0
      Installer/scripts/isxdl/french.ini
  10. 45 0
      Installer/scripts/isxdl/french2.ini
  11. 46 0
      Installer/scripts/isxdl/french3.ini
  12. 49 0
      Installer/scripts/isxdl/german.ini
  13. BIN
      Installer/scripts/isxdl/isxdl.dll
  14. 12 0
      Installer/scripts/isxdl/isxdl.iss
  15. 49 0
      Installer/scripts/isxdl/italian.ini
  16. 49 0
      Installer/scripts/isxdl/japanese.ini
  17. 49 0
      Installer/scripts/isxdl/korean.ini
  18. 47 0
      Installer/scripts/isxdl/norwegian.ini
  19. 45 0
      Installer/scripts/isxdl/polish.ini
  20. 45 0
      Installer/scripts/isxdl/portugues.ini
  21. 46 0
      Installer/scripts/isxdl/portuguese.ini
  22. 49 0
      Installer/scripts/isxdl/russian.ini
  23. 46 0
      Installer/scripts/isxdl/spanish.ini
  24. 48 0
      Installer/scripts/isxdl/swedish.ini
  25. 18 0
      Installer/scripts/lang/chinese.iss
  26. 18 0
      Installer/scripts/lang/dutch.iss
  27. 15 0
      Installer/scripts/lang/english.iss
  28. 18 0
      Installer/scripts/lang/french.iss
  29. 18 0
      Installer/scripts/lang/german.iss
  30. 18 0
      Installer/scripts/lang/italian.iss
  31. 18 0
      Installer/scripts/lang/japanese.iss
  32. 18 0
      Installer/scripts/lang/polish.iss
  33. 18 0
      Installer/scripts/lang/russian.iss
  34. 4 0
      Installer/scripts/products.iss
  35. 324 0
      Installer/scripts/products.pas
  36. 92 0
      Installer/scripts/products/dotnetfxversion.iss
  37. 27 0
      Installer/scripts/products/netcore31.iss
  38. 27 0
      Installer/scripts/products/netcore31desktop.iss
  39. 32 0
      Installer/scripts/products/netcorecheck.iss
  40. 60 0
      Installer/scripts/products/stringversion.iss
  41. 47 0
      Installer/scripts/products/winversion.iss
  42. BIN
      Installer/src/netcorecheck.exe
  43. BIN
      Installer/src/netcorecheck_x64.exe
  44. 1 0
      PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller.csproj
  45. 28 22
      PixiEditor.UpdateInstaller/ViewModelMain.cs
  46. 76 0
      PixiEditor.UpdateInstaller/app.manifest
  47. 14 6
      PixiEditor.UpdateModule/UpdateDownloader.cs
  48. 20 19
      PixiEditor.UpdateModule/UpdateInstaller.cs
  49. 136 135
      PixiEditor/Helpers/GlobalMouseHook.cs
  50. BIN
      PixiEditor/Images/MoveViewportImage.png
  51. 12 9
      PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs
  52. 274 285
      PixiEditor/Models/Controllers/BitmapManager.cs
  53. 26 12
      PixiEditor/Models/Controllers/MouseMovementController.cs
  54. 8 5
      PixiEditor/Models/Controllers/MouseMovementEventArgs.cs
  55. 96 87
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  56. 25 0
      PixiEditor/Models/Processes/ProcessHelper.cs
  57. 12 21
      PixiEditor/Models/Tools/Tool.cs
  58. 1 0
      PixiEditor/Models/Tools/ToolType.cs
  59. 81 95
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  60. 23 36
      PixiEditor/Models/Tools/Tools/FloodFill.cs
  61. 192 212
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  62. 50 0
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  63. 88 91
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  64. 71 69
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  65. 4 2
      PixiEditor/PixiEditor.csproj
  66. 22 20
      PixiEditor/Properties/AssemblyInfo.cs
  67. 241 511
      PixiEditor/ViewModels/ViewModelMain.cs
  68. 2 1
      PixiEditor/Views/MainDrawingPanel.xaml
  69. 251 187
      PixiEditor/Views/MainDrawingPanel.xaml.cs
  70. 49 48
      PixiEditor/Views/MainWindow.xaml
  71. 24 8
      PixiEditor/Views/MainWindow.xaml.cs
  72. 5 5
      PixiEditorTests/PixiEditorTests.csproj
  73. 1 1
      PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs
  74. 4 33
      windows-x64-release.yml
  75. 3 33
      windows-x86-release.yml

+ 68 - 7
Installer/installer-setup-x64-light.iss

@@ -1,5 +1,5 @@
-; Script generated by the Inno Setup Script Wizard.
-; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+// contribute: https://github.com/domgho/InnoDependencyInstaller
+// official article: https://www.codeproject.com/Articles/20868/Inno-Setup-Dependency-Installer
 
 #define MyAppName "PixiEditor"
 #define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x64-light\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
@@ -28,16 +28,59 @@ LicenseFile=..\LICENSE
 ; Uncomment the following line to run in non administrative install mode (install for current user only.)
 ;PrivilegesRequired=lowest
 OutputDir=Assets\PixiEditor-{#TargetPlatform}
-OutputBaseFilename=PixiEditor-{#MyAppVersion}-setup-{#TargetPlatform}
+OutputBaseFilename=PixiEditor-{#MyAppVersion}-setup-x64
 SetupIconFile=..\icon.ico
 Compression=lzma
 SolidCompression=yes
 WizardStyle=modern
 ChangesAssociations = yes
 
-[Languages]
-Name: "english"; MessagesFile: "compiler:Default.isl"
+PrivilegesRequired=admin
+ArchitecturesAllowed=x86 x64 ia64
+ArchitecturesInstallIn64BitMode=x64 ia64
 
+// downloading and installing dependencies will only work if the memo/ready page is enabled (default and current behaviour)
+DisableReadyPage=no
+DisableReadyMemo=no
+
+// requires netcorecheck.exe and netcorecheck_x64.exe in src dir
+#define use_netcorecheck
+#define use_netcore31
+#define use_netcore31desktop
+// supported languages
+#include "scripts\lang\english.iss"
+#include "scripts\lang\german.iss"
+#include "scripts\lang\french.iss"
+#include "scripts\lang\italian.iss"
+#include "scripts\lang\dutch.iss"
+
+#ifdef UNICODE
+#include "scripts\lang\chinese.iss"
+#include "scripts\lang\polish.iss"
+#include "scripts\lang\russian.iss"
+#include "scripts\lang\japanese.iss"
+#endif
+
+// shared code for installing the products
+#include "scripts\products.iss"
+
+// helper functions
+#include "scripts\products\stringversion.iss"
+#include "scripts\products\winversion.iss"
+#include "scripts\products\dotnetfxversion.iss"
+
+#ifdef use_netcorecheck
+#include "scripts\products\netcorecheck.iss"
+#endif
+#ifdef use_netcore31
+#include "scripts\products\netcore31.iss"
+#endif
+#ifdef use_netcore31desktop
+#include "scripts\products\netcore31desktop.iss"
+#endif
+
+// content
+[Tasks]
 [Tasks]
 Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
 Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
@@ -45,7 +88,6 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip
 [Files]
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\PixiEditor.exe"; DestDir: "{app}"; Flags: ignoreversion
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
-; NOTE: Don't use "Flags: ignoreversion" on any shared system files
 
 [Icons]
 Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
@@ -60,4 +102,23 @@ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang
 Root: HKCR; Subkey: ".pixi";                             ValueData: "{#MyAppName}";          Flags: uninsdeletevalue; ValueType: string;  ValueName: ""
 Root: HKCR; Subkey: "{#MyAppName}";                     ValueData: "Program {#MyAppName}";  Flags: uninsdeletekey;   ValueType: string;  ValueName: ""
 Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon";             ValueData: "{app}\{#MyAppExeName},0";               ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""
+Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""
+
+[CustomMessages]
+DependenciesDir=MyProgramDependencies
+WindowsServicePack=Windows %1 Service Pack %2
+
+[Code]
+function InitializeSetup(): Boolean;
+begin
+	// initialize windows version
+	initwinversion();
+
+#ifdef use_netcore31
+	netcore31();
+#endif
+#ifdef use_netcore31desktop
+	netcore31desktop();
+#endif
+	Result := true;
+end;

+ 0 - 63
Installer/installer-setup-x64.iss

@@ -1,63 +0,0 @@
-; Script generated by the Inno Setup Script Wizard.
-; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
-
-#define MyAppName "PixiEditor"
-#define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x64\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
-#define MyAppPublisher "PixiEditor"
-#define MyAppURL "https://github.com/PixiEditor/PixiEditor"
-#define MyAppExeName "PixiEditor.exe"
-#define TargetPlatform "x64"
-
-[Setup]
-; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
-; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
-AppId={{83DE4F2A-1F75-43AE-9546-3184F1C44517}
-AppName={#MyAppName}
-AppVersion={#MyAppVersion}
-AppVerName={#MyAppName} {#MyAppVersion}
-VersionInfoVersion={#MyAppVersion}
-AppPublisher={#MyAppPublisher}
-AppPublisherURL={#MyAppURL}
-AppSupportURL={#MyAppURL}
-AppUpdatesURL={#MyAppURL}
-DefaultDirName={autopf}\{#MyAppName}
-DisableProgramGroupPage=yes
-; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
-UsedUserAreasWarning=no
-LicenseFile=..\LICENSE
-; Uncomment the following line to run in non administrative install mode (install for current user only.)
-;PrivilegesRequired=lowest
-OutputDir=Assets\PixiEditor-{#TargetPlatform}
-OutputBaseFilename=PixiEditor-{#MyAppVersion}-setup-{#TargetPlatform}
-SetupIconFile=..\icon.ico
-Compression=lzma
-SolidCompression=yes
-WizardStyle=modern
-ChangesAssociations = yes
-
-[Languages]
-Name: "english"; MessagesFile: "compiler:Default.isl"
-
-[Tasks]
-Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
-Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
-
-[Files]
-Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\PixiEditor.exe"; DestDir: "{app}"; Flags: ignoreversion
-Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
-; NOTE: Don't use "Flags: ignoreversion" on any shared system files
-
-[Icons]
-Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
-Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
-Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
-
-[Run]
-Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
-
-[Registry]
-
-Root: HKCR; Subkey: ".pixi";                             ValueData: "{#MyAppName}";          Flags: uninsdeletevalue; ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}";                     ValueData: "Program {#MyAppName}";  Flags: uninsdeletekey;   ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon";             ValueData: "{app}\{#MyAppExeName},0";               ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""

+ 69 - 8
Installer/installer-setup-x86-light.iss

@@ -1,8 +1,8 @@
-; Script generated by the Inno Setup Script Wizard.
-; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+// contribute: https://github.com/domgho/InnoDependencyInstaller
+// official article: https://www.codeproject.com/Articles/20868/Inno-Setup-Dependency-Installer
 
 #define MyAppName "PixiEditor"
-#define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x86-light\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
+#define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x64-light\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
 #define MyAppPublisher "PixiEditor"
 #define MyAppURL "https://github.com/PixiEditor/PixiEditor"
 #define MyAppExeName "PixiEditor.exe"
@@ -28,16 +28,59 @@ LicenseFile=..\LICENSE
 ; Uncomment the following line to run in non administrative install mode (install for current user only.)
 ;PrivilegesRequired=lowest
 OutputDir=Assets\PixiEditor-{#TargetPlatform}
-OutputBaseFilename=PixiEditor-{#MyAppVersion}-setup-{#TargetPlatform}
+OutputBaseFilename=PixiEditor-{#MyAppVersion}-setup-x86
 SetupIconFile=..\icon.ico
 Compression=lzma
 SolidCompression=yes
 WizardStyle=modern
 ChangesAssociations = yes
 
-[Languages]
-Name: "english"; MessagesFile: "compiler:Default.isl"
+PrivilegesRequired=admin
+ArchitecturesAllowed=x86
+ArchitecturesInstallIn64BitMode=x64 ia64
 
+// downloading and installing dependencies will only work if the memo/ready page is enabled (default and current behaviour)
+DisableReadyPage=no
+DisableReadyMemo=no
+
+// requires netcorecheck.exe and netcorecheck_x64.exe in src dir
+#define use_netcorecheck
+#define use_netcore31
+#define use_netcore31desktop
+// supported languages
+#include "scripts\lang\english.iss"
+#include "scripts\lang\german.iss"
+#include "scripts\lang\french.iss"
+#include "scripts\lang\italian.iss"
+#include "scripts\lang\dutch.iss"
+
+#ifdef UNICODE
+#include "scripts\lang\chinese.iss"
+#include "scripts\lang\polish.iss"
+#include "scripts\lang\russian.iss"
+#include "scripts\lang\japanese.iss"
+#endif
+
+// shared code for installing the products
+#include "scripts\products.iss"
+
+// helper functions
+#include "scripts\products\stringversion.iss"
+#include "scripts\products\winversion.iss"
+#include "scripts\products\dotnetfxversion.iss"
+
+#ifdef use_netcorecheck
+#include "scripts\products\netcorecheck.iss"
+#endif
+#ifdef use_netcore31
+#include "scripts\products\netcore31.iss"
+#endif
+#ifdef use_netcore31desktop
+#include "scripts\products\netcore31desktop.iss"
+#endif
+
+// content
+[Tasks]
 [Tasks]
 Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
 Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
@@ -45,7 +88,6 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip
 [Files]
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\PixiEditor.exe"; DestDir: "{app}"; Flags: ignoreversion
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
-; NOTE: Don't use "Flags: ignoreversion" on any shared system files
 
 [Icons]
 Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
@@ -60,4 +102,23 @@ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang
 Root: HKCR; Subkey: ".pixi";                             ValueData: "{#MyAppName}";          Flags: uninsdeletevalue; ValueType: string;  ValueName: ""
 Root: HKCR; Subkey: "{#MyAppName}";                     ValueData: "Program {#MyAppName}";  Flags: uninsdeletekey;   ValueType: string;  ValueName: ""
 Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon";             ValueData: "{app}\{#MyAppExeName},0";               ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""
+Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""
+
+[CustomMessages]
+DependenciesDir=MyProgramDependencies
+WindowsServicePack=Windows %1 Service Pack %2
+
+[Code]
+function InitializeSetup(): Boolean;
+begin
+	// initialize windows version
+	initwinversion();
+
+#ifdef use_netcore31
+	netcore31();
+#endif
+#ifdef use_netcore31desktop
+	netcore31desktop();
+#endif
+	Result := true;
+end;

+ 0 - 63
Installer/installer-setup-x86.iss

@@ -1,63 +0,0 @@
-; Script generated by the Inno Setup Script Wizard.
-; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
-
-#define MyAppName "PixiEditor"
-#define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x86\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
-#define MyAppPublisher "PixiEditor"
-#define MyAppURL "https://github.com/PixiEditor/PixiEditor"
-#define MyAppExeName "PixiEditor.exe"
-#define TargetPlatform "x86"
-
-[Setup]
-; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
-; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
-AppId={{83DE4F2A-1F75-43AE-9546-3184F1C44517}
-AppName={#MyAppName}
-AppVersion={#MyAppVersion}
-AppVerName={#MyAppName} {#MyAppVersion}
-VersionInfoVersion={#MyAppVersion}
-AppPublisher={#MyAppPublisher}
-AppPublisherURL={#MyAppURL}
-AppSupportURL={#MyAppURL}
-AppUpdatesURL={#MyAppURL}
-DefaultDirName={autopf}\{#MyAppName}
-DisableProgramGroupPage=yes
-; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
-UsedUserAreasWarning=no
-LicenseFile=..\LICENSE
-; Uncomment the following line to run in non administrative install mode (install for current user only.)
-;PrivilegesRequired=lowest
-OutputDir=Assets\PixiEditor-{#TargetPlatform}
-OutputBaseFilename=PixiEditor-{#MyAppVersion}-setup-{#TargetPlatform}
-SetupIconFile=..\icon.ico
-Compression=lzma
-SolidCompression=yes
-WizardStyle=modern
-ChangesAssociations = yes
-
-[Languages]
-Name: "english"; MessagesFile: "compiler:Default.isl"
-
-[Tasks]
-Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
-Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
-
-[Files]
-Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\PixiEditor.exe"; DestDir: "{app}"; Flags: ignoreversion
-Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
-; NOTE: Don't use "Flags: ignoreversion" on any shared system files
-
-[Icons]
-Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
-Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
-Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
-
-[Run]
-Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
-
-[Registry]
-
-Root: HKCR; Subkey: ".pixi";                             ValueData: "{#MyAppName}";          Flags: uninsdeletevalue; ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}";                     ValueData: "Program {#MyAppName}";  Flags: uninsdeletekey;   ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon";             ValueData: "{app}\{#MyAppExeName},0";               ValueType: string;  ValueName: ""
-Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""

+ 49 - 0
Installer/scripts/isxdl/chinese.ini

@@ -0,0 +1,49 @@
+[strings]
+; General
+100=文件下载
+101=要取消下载吗?
+102=%1 (%2 / %3)
+103=%1 KB
+104=%1 KB / %2 KB (%3%)
+
+; Status information
+110=获取文件信息
+111=正在重定向到 %1
+112=正在发送请求...
+113=正在响应 %1
+114=已连接到 %1
+115=接收...
+116=正在连接到 %1
+
+; Error messages
+120=网络连接错误\n\n%1
+121=访问出错 %1.\n\n服务器返回的状态码为 %2.
+122=URL 读取错误.\n\n%1
+123=文件写入错误 %1.\n\n%2
+124=文件打开错误 %1.\n\n%2
+125='%1' 是无效的 URL.
+126=打开出错 %1.\n\n%2
+127=发送请求出错.\n\n%1
+128=不支持的协议类型. 只支持 HTTP 和 FTP 类型.
+129=无法连接到 %1.\n\n%2
+130=查询状态码失败.\n\n%1
+131=文件请求错误.\n\n%1
+
+; Other
+144=关于...
+146=下载
+147=安装程序正在下载附加文件到计算机上.
+
+; labels
+160=文件:
+161=速度:
+162=状态:
+163=已用时间:
+164=剩余时间:
+165=当前文件:
+166=总进度:
+167=取消
+168=确定
+169=用户名和密码
+170=用户名:
+171=密码:

+ 53 - 0
Installer/scripts/isxdl/czech.ini

@@ -0,0 +1,53 @@
+[strings]
+
+; Translation (c) 2005 Martin Kozák ([email protected])
+
+; General
+100=Stažení souboru
+101=Přejete si přerušit stahování?
+102=%1 (%2 z %3)
+103=%1 KB
+104=%1 KB z %2 KB (%3%)
+
+; Status information
+110=Získávání informací o souboru...
+111=Přesměrování na %1
+112=Odesílání požadavku...
+113=Zpracování %1
+114=Spojení s %1 navázáno
+115=Přijímání...
+116=Připojování k %1
+
+; Error messages
+120=Chyba při připojování k síti Internet.\n\n%1
+121=Chyba při otevírání %1.\n\nServer nastavil návratový kód %2.
+122=Chyba při čtení URL.\n\n%1
+123=Chyba při zápisu do souboru %1.\n\n%2
+124=Chyba při otevírání souboru %1.\n\n%2
+125='%1' není platné URL.
+126=Chyba při otevírání %1.\n\n%2
+127=Chyba při zasílání požadavku.\n\n%1
+128=Nepodporovaný protokol. Podporovány jsou pouze protokoly HTTP a FTP.
+129=Pokus o připojení k %1 selhalo.\n\n%2
+130=Pokus o získání návratového kódu serveru selhal.\n\n%1
+131=Chyba při zadávání požadavku na soubor.\n\n%1
+
+; Other
+144=O knihovně...
+146=Stažení komponent
+147=Průvodce instalací stahuje přídavné komponenty do vašeho počítače.
+
+; labels
+160=Soubor:
+161=Přenosová rychlost:
+162=Stav:
+163=Uplynulý čas:
+164=Zbývající čas:
+165=Zpracovávaný soubor:
+166=Celkový průběh:
+167=Zrušit
+168=OK
+169=Uživatelské jméno a heslo
+170=Uživatelské jméno:
+171=Heslo:
+

+ 49 - 0
Installer/scripts/isxdl/dutch.ini

@@ -0,0 +1,49 @@
+[strings]
+; Algemeen
+100=Bestand downloaden
+101=Wilt u de download annuleren?
+102=1% (%2 van %3)
+103=%1 KB
+104=%1 KB van %2 KB (%3%)
+
+; Status informatie
+110=Bestandsinformatie ophalen ...
+111=omgeleid naar %1
+112=Verzoek verzenden ...
+113=oplossen %1
+114=Verbonden met 1%
+115=Het ontvangen ...
+116=Verbinden met %1
+
+; foutmeldingen
+120=Fout bij het verbinden met Internet. \n\n%1
+121=Fout bij het openen van %1.\n\nDe server terug statuscode %2.
+122=Fout bij het lezen URL.\n\n%1
+123=Fout bij het schrijven bestand %1.\n\n%2
+124=Fout bij openen bestand %1.\n\n%2
+125='%1' is een ongeldige URL.
+126=Fout bij openen %1.\n\n%2
+127=Fout bij het verzenden verzoek.\n\n%1
+128=Niet ondersteund protocol. Alleen HTTP en FTP-protocollen worden ondersteund.
+129=Kan geen verbinding maken %1.\n\n%2
+130=Kan de status code opvragen.\n\n%1
+131=Fout bij het aanvragen van het bestand.\n\n%1
+
+; anders
+144=Over ...
+146=Download
+147=Setup is nu het downloaden van extra bestanden naar uw computer.
+
+; etiket
+160=Bestand:
+161=Speed:
+162=Status:
+163=Verstreken tijd:
+164=Resterende tijd:
+165=Huidige File:
+166=Algemeen Voortgang:
+167=Annuleren
+168=OK
+169=gebruikersnaam en wachtwoord
+170=Gebruikersnaam:
+171=Wachtwoord:

+ 49 - 0
Installer/scripts/isxdl/english.ini

@@ -0,0 +1,49 @@
+[strings]
+; General
+100=File download
+101=Do you want to cancel the download?
+102=%1 (%2 of %3)
+103=%1 KB
+104=%1 KB of %2 KB (%3%)
+
+; Status information
+110=Getting file information...
+111=Redirecting to %1
+112=Sending request...
+113=Resolving %1
+114=Connected to %1
+115=Receiving...
+116=Connecting to %1
+
+; Error messages
+120=Error connecting to the internet.\n\n%1
+121=Error opening %1.\n\nThe server returned status code %2.
+122=Error reading URL.\n\n%1
+123=Error writing file %1.\n\n%2
+124=Error opening file %1.\n\n%2
+125='%1' is an invalid URL.
+126=Error opening %1.\n\n%2
+127=Error sending request.\n\n%1
+128=Unsupported protocol. Only HTTP and FTP protocols are supported.
+129=Failed to connect to %1.\n\n%2
+130=Failed to query status code.\n\n%1
+131=Error requesting file.\n\n%1
+
+; Other
+144=About...
+146=Download
+147=Setup is now downloading additional files to your computer.
+
+; labels
+160=File:
+161=Speed:
+162=Status:
+163=Elapsed Time:
+164=Remaining Time:
+165=Current File:
+166=Overall Progress:
+167=Cancel
+168=OK
+169=User Name and Password
+170=User Name:
+171=Password:

+ 45 - 0
Installer/scripts/isxdl/french.ini

@@ -0,0 +1,45 @@
+[strings]
+; General
+100=Téléchargement des fichiers
+101=Souhaitez-vous annuler le téléchargement ?
+102=%1 (%2 / %3)
+103=%1 Ko
+104=%1 Ko / %2 Ko (%3%)
+
+; Etat du téléchargement
+110=Accès au fichier...
+111=Redirection vers %1
+112=Envoi de la requête...
+113=Recherche %1
+114=Connecté à %1
+115=Réception...
+116=Connexion à %1
+
+; Messages d'erreur
+120=Impossible de se connecter à internet.\n\n%1
+121=Impossible d'ouvrir %1.\n\nLe serveur a renvoyé le code d'erreur %2.
+122=Impossible de lire l'adresse.\n\n%1
+123=Impossible de créer le fichier %1.\n\n%2
+124=Impossible d'ouvrir le fichier %1.\n\n%2
+125='%1' est une adresse incorrecte.
+126=Impossible d'ouvrir %1.\n\n%2
+127=Impossible d'accéder au serveur.\n\n%1
+128=Protocole non supporté. Seuls les protocoles HTTP et FTP sont pris en charge.
+129=Impossible de se connecter à %1.\n\n%2
+130=Impossible de récupérer le code d'état.\n\n%1
+131=Impossible de récupérer le fichier.\n\n%1
+
+; Autre
+144=A propos...
+146=Téléchargement
+147=Certains fichiers requis vont être téléchargés.
+
+; Labels
+160=Fichier :
+161=Vitesse :
+162=Etat :
+163=Temps écoulé :
+164=Temps restant :
+165=Fichier courant :
+166=Tous les fichiers :
+167=Annuler

+ 45 - 0
Installer/scripts/isxdl/french2.ini

@@ -0,0 +1,45 @@
+[strings]
+; General
+100=Téléchargement de fichier
+101=Voulez vous annuler le téléchargement ?
+102=%1 (%2 de %3)
+103=%1 Ko
+104=%1 Ko de %2 Ko (%3%)
+
+; Status information
+110=Réception des informations du fichier...
+111=Redirection vers %1
+112=envoie de la demande...
+113=Résolution %1
+114=Connecté a %1
+115=Réception...
+116=Connexion à %1
+
+; Error messages
+120=Erreur de connexion à Internet.\n\n%1
+121=Erreur d'ouverture%1.\n\nLe Serveur à répondu par le code d'état %2.
+122=Erreur de lecture de l'URL.\n\n%1
+123=Erreur d'écriture du fichier %1.\n\n%2
+124=Erreur d'ouverture du fichier %1.\n\n%2
+125='%1' est une URL invalide.
+126=Erreur d’ouverture %1.\n\n%2
+127=Erreur pendant l'envoi de la demande.\n\n%1
+128=Protocole non supporté. Seuls les protocoles HTTP et FTP sont acceptés.
+129=Echec de connexion à %1.\n\n%2
+130=Echec d'obtention du code d'état.\n\n%1
+131=Erreur lors de la demande du fichier.\n\n%1
+
+; Other
+144=A Propos...
+146=Téléchargement
+147=LiveUpdate télécharge maintenant des fichiers complémentaires sur votre ordinateur.
+
+; labels
+160=Fichier:
+161=Vitesse:
+162=Etat:
+163=Temps écoulé:
+164=Temps restant:
+165=Fichier en cours:
+166=Avancement global:
+167=Annuler

+ 46 - 0
Installer/scripts/isxdl/french3.ini

@@ -0,0 +1,46 @@
+; By Fabien ILLIDE ([email protected])
+[strings]
+; General
+100=Téléchargement de fichier
+101=Voulez-vous annuler le téléchargement ?
+102=%1 (%2 de %3)
+103=%1 Ko
+104=%1 Ko de %2 Ko (%3%)
+
+; Status information
+110=Obtention des informations du fichier...
+111=Redirection vers %1
+112=Envoi de la requête...
+113=Résolution de %1
+114=Connecté à %1
+115=Réception...
+116=Connexion à %1
+
+; Error messages
+120=Erreur de connexion à Internet.\n\n%1
+121=Erreur en ouvrant %1.\n\nLe serveur à retourné le code d'état %2.
+122=Erreur de lecture d'URL.\n\n%1
+123=Erreur d'écriture pour %1.\n\n%2
+124=Erreur en ouvrant le fichier %1.\n\n%2
+125='%1' est une URL invalide.
+126=Erreur en ouvrant %1.\n\n%2
+127=Erreur d'envoi de requête.\n\n%1
+128=Protocole non supporté. Seuls les protocoles HTTP et FTP sont supportés.
+129=Echec de connexion à %1.\n\n%2
+130=Echec de demande du code d'état.\n\n%1
+131=Erreur en demandant le fichier.\n\n%1
+
+; Other
+144=A propos...
+146=Télécharger
+147=L'installateur télécharge maintenant les fichiers additionnels sur votre ordinateur.
+
+; labels
+160=Fichier :
+161=Vitesse :
+162=Etat :
+163=Temps écoulé :
+164=Temps restant :
+165=Fichier en cours :
+166=Avancement global :
+167=Annuler

+ 49 - 0
Installer/scripts/isxdl/german.ini

@@ -0,0 +1,49 @@
+[strings]
+; General
+100=Datei herunterladen
+101=Möchten Sie das Herunterladen der Datei abbrechen?
+102=%1 (%2 von %3)
+103=%1 KB
+104=%1 KB von %2 KB (%3%)
+
+; Status information
+110=Dateiinformationen werden ermittelt...
+111=Weiterleitung zu %1
+112=Anforderung wird gesendet...
+113=Auflösen von %1
+114=Verbunden mit %1
+115=Empfange...
+116=Verbinden mit %1
+
+; Error messages
+120=Fehler beim Verbinden mit dem Internet.\n\n%1
+121=Fehler beim Öffnen von %1.\n\nDer Server meldet Statuscode %2.
+122=Fehler beim Lesen der URL.\n\n%1
+123=Fehler beim Schreiben der Datei %1.\n\n%2
+124=Fehler beim Öffnen der Datei %1.\n\n%2
+125='%1' ist eine ungültige URL.
+126=Fehler beim Öffnen von %1.\n\n%2
+127=Fehler beim Senden der Anforderung.\n\n%1
+128=Protokoll wird nicht unterstützt. Nur HTTP und FTP werden unterstützt.
+129=Verbindung zu %1 fehlgeschlagen.\n\n%2
+130=Fehler bei der Abfrage des Statuscodes.\n\n%1
+131=Fehler bei der Anforderung der Datei.\n\n%1
+
+; Other
+144=Über...
+146=Download
+147=Das Setup lädt nun zusätzliche Dateien auf Ihren Computer.
+
+; labels
+160=Datei:
+161=Geschwindigkeit:
+162=Status:
+163=Bisherige Zeit:
+164=Verbleibende Zeit:
+165=Aktuelle Datei:
+166=Gesamter Vorgang:
+167=Abbrechen
+168=OK
+169=Benutzername und Kennwort
+170=Benutzername:
+171=Kennwort:

BIN
Installer/scripts/isxdl/isxdl.dll


+ 12 - 0
Installer/scripts/isxdl/isxdl.iss

@@ -0,0 +1,12 @@
+[Files]
+Source: "scripts\isxdl\isxdl.dll"; Flags: dontcopy noencryption
+
+[Code]
+procedure isxdl_AddFile(URL, Filename: PAnsiChar);
+external 'isxdl_AddFile@files:isxdl.dll stdcall';
+
+function isxdl_DownloadFiles(hWnd: Integer): Integer;
+external 'isxdl_DownloadFiles@files:isxdl.dll stdcall';
+
+function isxdl_SetOption(Option, Value: PAnsiChar): Integer;
+external 'isxdl_SetOption@files:isxdl.dll stdcall';

+ 49 - 0
Installer/scripts/isxdl/italian.ini

@@ -0,0 +1,49 @@
+[strings]
+; Generale
+100=Download del file
+101=Vuoi annullare il download?
+102=%1 (%2 di %3)
+103=%1 KB
+104=%1 KB di %2 KB (%3%)
+
+; Informazioni di servizio
+110=Raccolta informazioni sul file...
+111=Reindirizzamento a %1
+112=Invio richiesta...
+113=Risoluzione %1
+114=Connesso al %1
+115=Ricezione...
+116=Collegamento a %1
+
+; Messaggi di errore
+120=Errore nel collegamento a Internet.\n\n%1
+121=Errore nell'apertura di %1.\n\nIl server ha restituito il codice %2.
+122=Errore nella lettura dell'URL.\n\n%1
+123=Errore nella scrittura del file %1.\n\n%2
+124=Errore nell'apertura del file %1.\n\n%2
+125='%1' è un URL non valido.
+126=Errore nell'apertura di %1.\n\n%2
+127=Errore durante l'invio della richiesta.\n\n%1
+128=Protocollo non supportato. Sono supportati solo i protocolli HTTP e FTP.
+129=Impossibile connettersi a %1.\n\n%2
+130=Impossibile risolvere il codice di servizio.\n\n%1
+131=Errore nella richiesta del file.\n\n%1
+
+; Altro
+144=Informazioni su...
+146=Download
+147=Il programma d'installazione sta scaricando sul computer i files aggiuntivi.
+
+; Etichette
+160=File:
+161=Velocità:
+162=Stato:
+163=Tempo trascorso:
+164=Tempo rimanente:
+165=File attuale:
+166=Avanzamento generale:
+167=Annulla
+168=OK
+169=Nome utente e password
+170=Nome utente:
+171=Password:

+ 49 - 0
Installer/scripts/isxdl/japanese.ini

@@ -0,0 +1,49 @@
+[strings]
+; General
+100=ファイルダウンロード
+101=ダウンロードをキャンセルしますか?
+102=%1 (%2 of %3)
+103=%1 KB
+104=%1 KB of %2 KB (%3%)
+
+; Status information
+110=ファイル情報を取得中...
+111=リダイレクト %1
+112=リクエスト送信...
+113=解決中 %1
+114=接続完了 %1
+115=受信中...
+116=接続中 %1
+
+; Error messages
+120=インターネット接続エラー.\n\n%1
+121=開始エラー %1.\n\nサーバーのステータスコード %2.
+122=URL読み取りエラー.\n\n%1
+123=ファイル書込みエラー %1.\n\n%2
+124=ファイルオープンエラー %1.\n\n%2
+125='%1' は不正なURLです.
+126=オープンエラー %1.\n\n%2
+127=リクエスト送信エラー.\n\n%1
+128=サポートされていないプロトコルです. HTTPとFTPプロトコルだけがサポートされています.
+129=接続に失敗しました %1.\n\n%2
+130=ステータスコードの問い合わせ失敗.\n\n%1
+131=ファイルリクエストエラー.\n\n%1
+
+; Other
+144=About...
+146=ダウンロード
+147=セットアップはコンピューターに追加のファイルをダウンロードしています.
+
+; labels
+160=ファイル:
+161=速度:
+162=状態:
+163=経過時間:
+164=残り時間:
+165=現在のファイル:
+166=全体の進捗状況:
+167=キャンセル
+168=OK
+169=ユーザーとパスワード
+170=ユーザー:
+171=パスワード:

+ 49 - 0
Installer/scripts/isxdl/korean.ini

@@ -0,0 +1,49 @@
+[strings]
+; General
+100=파일 다운로드
+101=다운로드를 취소하시겠습니까?
+102=%1 (%2 중 %3)
+103=%1 KB
+104=%1 KB 중 %2 KB (%3%)
+
+; Status information
+110=파일 정보를 받고 있습니다...
+111=Redirecting to %1
+112=요청을 보내고 있습니다...
+113= %1
+114=%1(으)로 연결되었습니다.
+115=받는중...
+116=%1(으)로 연결중입니다.
+
+; Error messages
+120=인터넷 연결중 문제가 발생했습니다.\n\n%1
+121=여는도중 애러 발생 %1.\n\n서버 상태 메시지 %2.
+122=URL을 읽는 도중 문제가 발생했습니다.\n\n%1
+123=파일을 쓰는 도중 문제가 발생했습니다 %1.\n\n%2
+124=파일을 여는 도중 문제가 발생했습니다 %1.\n\n%2
+125='%1' 은 잘못된 URL입니다.
+126=여는 도중 문제가 발생했습니다 %1.\n\n%2
+127=요청을 보내는 도중 문제가 발생했습니다\n\n%1
+128=지원하지 않는 프로토콜입니다. HTTP 와 FTP 만을 지원합니다.
+129=%1로 연결이 실패하였습니다.\n\n%2
+130=쿼리가 실패했습니다. 상태 코드는 다음과 같습니다.\n\n%1
+131=파일 요청중 문제가 발생했습니다.\n\n%1
+
+; Other
+144=About...
+146=다운로드
+147=이 설치 프로그램은 추가적으로 이 파일들을 다운로드 하게 됩니다.
+
+; labels
+160=파일:
+161=속도:
+162=상태:
+163=경과 시간:
+164=남은 시간:
+165=현재 파일:
+166=전체 진행도:
+167=취소
+168=확인
+169=사용자명과 패스워드
+170=사용자명:
+171=패스워드:

+ 47 - 0
Installer/scripts/isxdl/norwegian.ini

@@ -0,0 +1,47 @@
+[strings]
+; General
+100=Nedlasting
+101=Vil du avbryte nedlastinga?
+102=%1 (%2 av %3)
+103=%1 KB
+104=%1 KB av %2 KB (%3%)
+
+; Status information
+110=Henter filinformasjon...
+111=Omdirigerer til %1
+112=Sender forespørsel...
+113=Resolving %1
+114=Oppkoblet mot %1
+115=Mottar...
+116=Kobler til %1
+; Error messages
+120=Feil ved oppkobling til Internett.\n\n%1
+121=Feil ved åpning av %1.\n\nTjeneren returnerte statuskode %2.
+122=Feil ved lesing av URL.\n\n%1
+123=Feil ved skriving til '%1'.\n\n%2
+124=Feil ved åpning av '%1'.\n\n%2
+125='%1' er ikke en gyldig url.
+126=Feil ved åpning av %1.\n\n%2
+127=Feil ved sending av forespørsel.\n\n%1
+128=Ikke støttet protokoll. Bare HTTP og FTP protokoller er støttet.
+129=Klarte ikke å koble til %1.\n\n%2
+130=Klarte ikke å lese statuskode.\n\n%1
+131=Feil ved forespørsel av fil.\n\n%1
+; Other
+144=Om...
+146=Nedlasting
+147=Setup laster nå ned flere filer til din datamaskin.
+
+; labels
+160=Fil:
+161=Hastighet:
+162=Status:
+163=Brukt tid:
+164=Gjenstående tid:
+165=Gjeldende fil:
+166=Total fremgang:
+167=Avbryt
+168=OK
+169=Brukernavn og passord
+170=Brukernavn:
+171=Passord:

+ 45 - 0
Installer/scripts/isxdl/polish.ini

@@ -0,0 +1,45 @@
+[strings]
+; General
+100=Pobieranie pliku
+101=Czy chcesz przerwać pobieranie?
+102=%1 (%2 z %3)
+103=%1 KB
+104=%1 KB z %2 KB (%3%)
+
+; Status information
+110=Pobieranie informacji o pliku...
+111=Przekierowywanie do %1
+112=Wysyłanie żądania...
+113=Rozwiązywanie %1
+114=Połączony z %1
+115=Pobieranie...
+116=Podłączanie do %1
+
+; Error messages
+120=Nie można podłączyć się do Internetu.\n\n%1
+121=Błąd otwierania %1.\n\nSerwer zwrócił kod błędu %2.
+122=Błąd czytania URL.\n\n%1
+123=Błąd zapisu pliku %1.\n\n%2
+124=Błąd otwarcia pliku %1.\n\n%2
+125='%1' nie jest prawidłowym URL.
+126=Błąd otwarcia %1.\n\n%2
+127=Błąd wysłania żądania.\n\n%1
+128=Nieznany protokół. Tylko protokoły HTTP i FTP są obsługiwane.
+129=Nie udało się podłączenie do %1.\n\n%2
+130=Nie udało się zapytać o kod stanu.\n\n%1
+131=Błąd żądania pliku.\n\n%1
+
+; Other
+144=O pobieraniu...
+146=Pobieranie
+147=W tej chwili Instalator pobiera dodatkowe pliki do Twojego komputera.
+
+; labels
+160=Plik:
+161=Prędkość:
+162=Stan:
+163=Upłynęło:
+164=Pozostały czas:
+165=Aktualny plik:
+166=Całkowity postęp:
+167=Anuluj

+ 45 - 0
Installer/scripts/isxdl/portugues.ini

@@ -0,0 +1,45 @@
+[strings]
+; General
+100=Donwload de ficheiro
+101=Deseja cancelar o donwload?
+102=%1 (%2 de %3)
+103=%1 KB
+104=%1 KB de %2 KB (%3%)
+
+; Status information
+110=A receber informação do ficheiro...
+111=A redirecionar para %1
+112=A enviar pedido...
+113=A resolver %1
+114=Ligado a %1
+115=A receber...
+116=A ligar a %1
+
+; Error messages
+120=Erro na ligação à internet.\n\n%1
+121=Erro na abertura de %1.\n\nO servidor retornou o código de erro %2.
+122=Erro a ler o URL.\n\n%1
+123=Erro na escrita do ficheiro %1.\n\n%2
+124=Erro na abertura do ficheiro %1.\n\n%2
+125='%1' é um URL inválido.
+126=Erro na abertura de %1.\n\n%2
+127=Erro no envio do pedido.\n\n%1
+128=Protocolo não suportado.
+129=Falha na ligação a %1.\n\n%2
+130=Falha na tentativa de retirar o código de status.\n\n%1
+131=Erro no pedido do ficheiro.\n\n%1
+
+; Other
+144=Acerca...
+146=Download
+147=A instalação está a efectuar o download de ficheiros adicionais necessários.
+
+; labels
+160=Ficheiro:
+161=Velocidade:
+162=Estado:
+163=Tempo usado:
+164=Tempo em falta:
+165=Ficheiro:
+166=Progresso:
+167=Cancelar

+ 46 - 0
Installer/scripts/isxdl/portuguese.ini

@@ -0,0 +1,46 @@
+; By António Pinto (AP SoftWare)
+[strings]
+; Geral
+100=Download
+101=Deseja cancelar o download?
+102=%1 (%2 de %3)
+103=%1 KB
+104=%1 KB de %2 KB (%3%)
+
+; Status information
+110=A receber informação do ficheiro...
+111=Redireccionando a %1
+112=A enviar petição...
+113=Resolvendo %1
+114=Ligado a %1
+115=A receber...
+116=A efectuar ligação a %1
+
+; Error messages
+120=Erro a ligar à Internet.\n\n%1
+121=Erro ao abrir %1.\n\nO servidor devolveu o código %2.
+122=Erro ao ler URL.\n\n%1
+123=Erro ao escrever o ficheiro %1.\n\n%2
+124=Erro ao abrir o ficheiro %1.\n\n%2
+125='%1' é uma URL inválida.
+126=Erro ao abrir %1.\n\n%2
+127=Erro ao enviar a petição.\n\n%1
+128=Erro de protocolo. Só os protocolos HTTP ou FTP são aceites.
+129=Erro ao ligar a %1.\n\n%2
+130=Erro ao processar o código.\n\n%1
+131=Erro ao procurar o ficheiro.\n\n%1
+
+; Other
+144=Acerca...
+146=Descarregar
+147=O assistente está a descarregar ficheiros para o seu computador.
+
+; labels
+160=Ficheiro:
+161=Velocidade:
+162=Estado:
+163=Tempo transcorrido:
+164=Tempo estimado:
+165=Ficheiro actual:
+166=Progressão geral:
+167=Cancelar

+ 49 - 0
Installer/scripts/isxdl/russian.ini

@@ -0,0 +1,49 @@
+[strings]
+; General
+100=Загрузка файла
+101=Вы действительно хотите прервать загрузку?
+102=%1 (%2 из %3)
+103=%1 KB
+104=%1 KB из %2 KB (%3%)
+
+; Status information
+110=Получение информации о файле...
+111=Переход на %1
+112=Отправка запроса...
+113=Подключение к %1
+114=Подключен к %1
+115=Получение...
+116=Соединение с %1
+
+; Error messages
+120=Ошибка подключения к интернету.\n\n%1
+121=Ошибка открытия %1.\n\nСервер вернул код ошибки %2.
+122=Ошибка чтения адреса.\n\n%1
+123=Ошибка записи файла %1.\n\n%2
+124=Ошибка открытия файла %1.\n\n%2
+125='%1' неправильный интернет адрес.
+126=Ошибка открытия %1.\n\n%2
+127=Ошибка при отправке запроса.\n\n%1
+128=Протокол не поддерживается. Программой поддерживаются только протоколы HTTP и FTP.
+129=Невозможно подключиться к %1.\n\n%2
+130=Невозможно получить код статуса.\n\n%1
+131=Ошибка при запросе файла.\n\n%1
+
+; Other
+144=О программе...
+146=Загрузка
+147=Сейчас происходит загрузка дополнительных файлов на ваш компьютер.
+
+; labels
+160=Файл:
+161=Скорость:
+162=Статус:
+163=Прошло времени:
+164=Осталось времени:
+165=Текущий файл:
+166=Всего закачено:
+167=Отмена
+168=OK
+169=Имя пользователя и пароль
+170=Имя пользователя:
+171=Пароль:

+ 46 - 0
Installer/scripts/isxdl/spanish.ini

@@ -0,0 +1,46 @@
+; By Lobo Lunar
+[strings]
+; General
+100=Descarga
+101=¿Deseas cancelar la descarga?
+102=%1 (%2 de %3)
+103=%1 KB
+104=%1 KB de %2 KB (%3%)
+
+; Status information
+110=Recibiendo información del archivo...
+111=Redireccionando a %1
+112=Enviando petición...
+113=Resolviendo %1
+114=Conectando a %1
+115=Descargando...
+116=Conectando a %1
+
+; Error messages
+120=Error al conectar a Internet.\n\n%1
+121=Error al abrir %1.\n\nEl servidor regresó %2.
+122=Error al leer URL.\n\n%1
+123=Error al escribir el archivo %1.\n\n%2
+124=Error al abrir archivo %1.\n\n%2
+125='%1' es URL inválido.
+126=Error al abrir %1.\n\n%2
+127=Error al mandar petición.\n\n%1
+128=Error de protocolo. Sólo HTTP o FTP son aceptados.
+129=Error al conectar a %1.\n\n%2
+130=Error al procesar el código.\n\n%1
+131=Error al buscar archivo.\n\n%1
+
+; Other
+144=Acerca...
+146=Descargar
+147=El asistente está descargando archivos.
+
+; labels
+160=Archivo:
+161=Velocidad:
+162=Estado:
+163=Tiempo transcurrido:
+164=Tiempo estimado:
+165=Archivo actual:
+166=Progreso general:
+167=Cancelar

+ 48 - 0
Installer/scripts/isxdl/swedish.ini

@@ -0,0 +1,48 @@
+[strings]
+; General
+100=Fil Nedladdning
+101=Vill du avbruta nedladdningen?
+102=%1 (%2 av %3)
+103=%1 KB
+104=%1 KB av %2 KB (%3%)
+
+; Status information
+110=Hämtar fil information...
+111=Omdirigerar till %1
+112=Sänder förfrågan...
+113=Delar upp %1
+114=Ansluten %1
+115=Tar emot...
+116=Ansluter till %1
+
+; Error messages
+120=Fel vid anslutning till Internet.\n\n%1
+121=Fel vid öppning av %1.\n\nServern returnerade felkod %2.
+122=Fel vid läsninga av URL.\n\n%1
+123=Fel vid skrivning av fil %1.\n\n%2
+124=Fel vid öppning av fil %1.\n\n%2
+125='%1' är en felaktig URL.
+126=Kan inte öppna %1.\n\n%2
+127=Fel vid sändning av förfrågan.\n\n%1
+128=Stöd saknas för protokollet. Endast HTTP och FTP protokollen understöds.
+129=Misslyckades att ansluta till %1.\n\n%2
+130=Misslyckades att kontrollera status kod.\n\n%1
+131=Fel vid förfrågan efter fil.\n\n%1
+
+; Other
+144=Om...
+146=Ladd ner
+147=Setup laddar nu ner tilläggsfiler till din dator
+; labels
+160=Fil:
+161=Hastighet:
+162=Status:
+163=Förfluten Time:
+164=Återstående Time:
+165=Nuvarande Fil:
+166=Totalt Förlopp:
+167=Avbryt
+168=OK
+169=Användarnamn och Lösenord
+170=Användarnamn:
+171=Lösenord:

+ 18 - 0
Installer/scripts/lang/chinese.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "chs"; MessagesFile: "compiler:Default.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+chs.lcid=2052
+chs.depdownload_memo_title=下载依赖组建
+chs.depinstall_memo_title=安装依赖组建
+chs.depinstall_title=安装依赖组建
+chs.depinstall_description=安装程序正在安装所需的依赖组建,请稍后。
+chs.depinstall_status=正在安装 %1...
+chs.depinstall_missing=必须安装 %1 之后才能继续本安装程序。请先安装 %1,然后在重新运行本安装程序。
+chs.depinstall_error=安装依赖组建时出错。请重新启动计算机并再次运行安装程序,或手动安装下列依赖组建:%n
+
+chs.isxdl_langfile=chinese.ini
+
+[Files]
+Source: "scripts\isxdl\chinese.ini"; Flags: dontcopy noencryption

+ 18 - 0
Installer/scripts/lang/dutch.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+nl.lcid=1043
+nl.depdownload_memo_title=Download afhankelijkheden
+nl.depinstall_memo_title=Installeer afhankelijkheden
+nl.depinstall_title=Installeer afhankelijkheden
+nl.depinstall_description=Een moment geduld aub Setup installeert afhankelijkheden op uw computer.
+nl.depinstall_status=Installeren %1...
+nl.depinstall_missing=%1 moet worden geïnstalleerd vóór de installatie kan worden voortgezet. Installeer %1 en voer Setup opnieuw uit.
+nl.depinstall_error=Er is een fout opgetreden tijdens het installeren van de afhankelijkheden. Gelieve de computer opnieuw op en voer de installatie opnieuw uit of de volgende afhankelijkheden handmatig installeren:%n
+
+nl.isxdl_langfile=dutch.ini
+
+[Files]
+Source: "scripts\isxdl\dutch.ini"; Flags: dontcopy noencryption

+ 15 - 0
Installer/scripts/lang/english.iss

@@ -0,0 +1,15 @@
+[Languages]
+Name: "en"; MessagesFile: "compiler:Default.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+en.lcid=1033
+en.depdownload_memo_title=Download dependencies
+en.depinstall_memo_title=Install dependencies
+en.depinstall_title=Installing dependencies
+en.depinstall_description=Please wait while Setup installs dependencies on your computer.
+en.depinstall_status=Installing %1...
+en.depinstall_missing=%1 must be installed before setup can continue. Please install %1 and run Setup again.
+en.depinstall_error=An error occured while installing the dependencies. Please restart the computer and run the setup again or install the following dependencies manually:%n
+
+en.isxdl_langfile=

+ 18 - 0
Installer/scripts/lang/french.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "fr"; MessagesFile: "compiler:Languages\French.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+fr.lcid=1036
+fr.depdownload_memo_title=Télécharger les dépendances
+fr.depinstall_memo_title=Installez les dépendances
+fr.depinstall_title=Installation des dépendances
+fr.depinstall_description=Veuillez patienter pendant que les dépendances sont installées sur votre ordinateur.
+fr.depinstall_status=Installation de %1...
+fr.depinstall_missing=%1 doit être installé avant de pouvoir continuer. Veuillez installer %1 et exécutez à nouveau le programme d'installation.
+fr.depinstall_error=Une erreur est survenue lors de l'installation des dépendances. Veuillez redémarrer l'ordinateur, et exécutez à nouveau le programme d'installation, ou installez les dépendances suivantes manuellement :%n
+
+fr.isxdl_langfile=french3.ini
+
+[Files]
+Source: "scripts\isxdl\french3.ini"; Flags: dontcopy noencryption

+ 18 - 0
Installer/scripts/lang/german.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "de"; MessagesFile: "compiler:Languages\German.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+de.lcid=1031
+de.depdownload_memo_title=Abhängigkeiten downloaden
+de.depinstall_memo_title=Abhängigkeiten installieren
+de.depinstall_title=Installiere Abhängigkeiten
+de.depinstall_description=Warten Sie bitte während Abhängigkeiten auf Ihrem Computer installiert wird.
+de.depinstall_status=Installiere %1...
+de.depinstall_missing=%1 muss installiert werden bevor das Setup fortfahren kann. Bitte installieren Sie %1 und starten Sie das Setup erneut.
+de.depinstall_error=Ein Fehler ist während der Installation der Abghängigkeiten aufgetreten. Bitte starten Sie den Computer neu und führen Sie das Setup erneut aus oder installieren Sie die folgenden Abhängigkeiten per Hand:%n
+
+de.isxdl_langfile=german.ini
+
+[Files]
+Source: "scripts\isxdl\german.ini"; Flags: dontcopy noencryption

+ 18 - 0
Installer/scripts/lang/italian.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "it"; MessagesFile: "compiler:Languages\Italian.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+it.lcid=1040
+it.depdownload_memo_title=Dipendenze da scaricare
+it.depinstall_memo_title=Dipendenze da installare
+it.depinstall_title=Installazione delle dipendenze
+it.depinstall_description=Si prega di attendere mentre vengono installate le dipendenze necessarie sul computer.
+it.depinstall_status=Installazione %1...
+it.depinstall_missing=%1 deve essere installato per poter continuare. Si prega di installare %1 ed eseguire nuovamente il programma d'installazione.
+it.depinstall_error=Si è verificato un errore durante l'installazione delle dipendenze. Si prega di riavviare il computer ed eseguire nuovamente il programma d'installazione oppure di installare manualmente le seguenti applicazioni:%n
+
+it.isxdl_langfile=italian.ini
+
+[Files]
+Source: "scripts\isxdl\italian.ini"; Flags: dontcopy noencryption

+ 18 - 0
Installer/scripts/lang/japanese.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+ja.lcid=1041
+ja.depdownload_memo_title=ダウンロードする依存ファイル
+ja.depinstall_memo_title=インストールする依存ファイル
+ja.depinstall_title=依存ファイルのインストール
+ja.depinstall_description=セットアップが依存ファイルをインストールするまでお待ちください
+ja.depinstall_status=インストール中 %1...
+ja.depinstall_missing=セットアップを継続するために %1 をインストールする必要があります. %1 をインストールし、再度セットアップを実行してください.
+ja.depinstall_error=依存ファイルのインストール中にエラーが発生しました. コンピューターを再起動しセットアップを再度実行するか、依存ファイルを手動でインストールしてください:%n
+
+ja.isxdl_langfile=japanese.ini
+
+[Files]
+Source: "scripts\isxdl\japanese.ini"; Flags: dontcopy noencryption

+ 18 - 0
Installer/scripts/lang/polish.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+pl.lcid=1045
+pl.depdownload_memo_title=Pobierz zależności
+pl.depinstall_memo_title=Zainstaluj zależności
+pl.depinstall_title=Instalowanie zależności
+pl.depinstall_description=Instalator instaluje zależności na komputerze, proszę czekać.
+pl.depinstall_status=Instalowanie %1....
+pl.depinstall_missing=%1 musi być zainstalowany przed instalacją, aby mogła ona być kontynuowana. Zainstaluj %1 i ponownie uruchom program instalacyjny.
+pl.depinstall_error=Wystąpił błąd podczas instalowania zależności. Uruchom ponownie komputer, a następnie ponownie uruchom program instalacyjny lub ręcznie zainstaluj następujące programy:%n
+
+pl.isxdl_langfile=polish.ini
+
+[Files]
+Source: "scripts\isxdl\polish.ini"; Flags: dontcopy noencryption

+ 18 - 0
Installer/scripts/lang/russian.iss

@@ -0,0 +1,18 @@
+[Languages]
+Name: "ru"; MessagesFile: "compiler:Languages\Russian.isl"
+
+[CustomMessages]
+// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
+ru.lcid=1049
+ru.depdownload_memo_title=Загрузить зависимости
+ru.depinstall_memo_title=Установить зависимости
+ru.depinstall_title=Установка зависимостей
+ru.depinstall_description=Пожалуйста, подождите, пока зависимости будут установлены.
+ru.depinstall_status=Установка %1...
+ru.depinstall_missing=%1 должен быть установлен прежде чем установка может быть продолжена. Пожалуйста, установите %1 и повторите установку.
+ru.depinstall_error=В процессе установки зависимостей произошла ошибка. Пожалуйста, перезапустите компьютер и повторите установку, либо установите следующие зависимости вручную:%n
+
+ru.isxdl_langfile=russian.ini
+
+[Files]
+Source: "scripts\isxdl\russian.ini"; Flags: dontcopy noencryption

+ 4 - 0
Installer/scripts/products.iss

@@ -0,0 +1,4 @@
+#include "isxdl\isxdl.iss"
+
+[Code]
+#include "products.pas"

+ 324 - 0
Installer/scripts/products.pas

@@ -0,0 +1,324 @@
+{
+	--- TYPES AND VARIABLES ---
+}
+type
+	TProduct = record
+		File: String;
+		Title: String;
+		Parameters: String;
+		ForceSuccess: Boolean;
+		InstallClean: Boolean;
+		MustRebootAfter: Boolean;
+	end;
+
+	InstallResult = (InstallSuccessful, InstallRebootRequired, InstallError);
+
+var
+	installMemo, downloadMemo: String;
+	products: array of TProduct;
+	delayedReboot, isForcedX86: Boolean;
+	DependencyPage: TOutputProgressWizardPage;
+
+procedure AddProduct(filename, parameters, title, size, url: String; forceSuccess, installClean, mustRebootAfter: Boolean);
+{
+	Adds a product to the list of products to download.
+	Parameters:
+		filename: the file name under which to save the file
+		parameters: the parameters with which to run the file
+		title: the product title
+		size: the file size
+		url: the URL to download from
+		forceSuccess: whether to continue in case of setup failure
+		installClean: whether the product needs a reboot before installing
+		mustRebootAfter: whether the product needs a reboot after installing
+}
+var
+	path: String;
+	i: Integer;
+begin
+	path := ExpandConstant('{src}{\}') + CustomMessage('DependenciesDir') + '\' + filename;
+	if not FileExists(path) then begin
+		path := ExpandConstant('{tmp}{\}') + filename;
+
+		if not FileExists(path) then begin
+			isxdl_AddFile(url, path);
+
+			downloadMemo := downloadMemo + '%1' + title + ' (' + size + ')' + #13;
+		end else begin
+			installMemo := installMemo + '%1' + title + #13;
+		end;
+	end else begin
+		installMemo := installMemo + '%1' + title + #13;
+	end;
+
+	i := GetArrayLength(products);
+	SetArrayLength(products, i + 1);
+	products[i].File := path;
+	products[i].Title := title;
+	products[i].Parameters := parameters;
+	products[i].ForceSuccess := forceSuccess;
+	products[i].InstallClean := installClean;
+	products[i].MustRebootAfter := mustRebootAfter;
+end;
+
+function SmartExec(product: TProduct; var resultCode: Integer): Boolean;
+{
+	Executes a product and returns the exit code.
+	Parameters:
+		product: the product to install
+		resultCode: the exit code
+}
+begin
+	Result := ShellExec('', product.File, product.Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, resultCode);
+end;
+
+function PendingReboot: Boolean;
+{
+	Checks whether the machine has a pending reboot.
+}
+var
+	names: String;
+begin
+	if (RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'PendingFileRenameOperations', names)) then begin
+		Result := true;
+	end else if ((RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'SetupExecute', names)) and (names <> ''))  then begin
+		Result := true;
+	end else begin
+		Result := false;
+	end;
+end;
+
+function InstallProducts: InstallResult;
+{
+	Installs the downloaded products
+}
+var
+	resultCode, i, productCount, finishCount: Integer;
+begin
+	Result := InstallSuccessful;
+	productCount := GetArrayLength(products);
+
+	if productCount > 0 then begin
+		DependencyPage := CreateOutputProgressPage(CustomMessage('depinstall_title'), CustomMessage('depinstall_description'));
+		DependencyPage.Show;
+
+		for i := 0 to productCount - 1 do begin
+			if (products[i].InstallClean and (delayedReboot or PendingReboot())) then begin
+				Result := InstallRebootRequired;
+				break;
+			end;
+
+			DependencyPage.SetText(FmtMessage(CustomMessage('depinstall_status'), [products[i].Title]), '');
+			DependencyPage.SetProgress(i, productCount);
+
+			while true do begin
+				// set 0 as used code for shown error if SmartExec fails
+				resultCode := 0;
+				if SmartExec(products[i], resultCode) then begin
+					// setup executed; resultCode contains the exit code
+					if (products[i].MustRebootAfter) then begin
+						// delay reboot after install if we installed the last dependency anyways
+						if (i = productCount - 1) then begin
+							delayedReboot := true;
+						end else begin
+							Result := InstallRebootRequired;
+						end;
+						break;
+					end else if (resultCode = 0) or (products[i].ForceSuccess) then begin
+						finishCount := finishCount + 1;
+						break;
+					end else if (resultCode = 3010) then begin
+						// Windows Installer resultCode 3010: ERROR_SUCCESS_REBOOT_REQUIRED
+						delayedReboot := true;
+						finishCount := finishCount + 1;
+						break;
+					end;
+				end;
+
+				case MsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [products[i].Title, IntToStr(resultCode)]), mbError, MB_ABORTRETRYIGNORE) of
+					IDABORT: begin
+						Result := InstallError;
+						break;
+					end;
+					IDIGNORE: begin
+						break;
+					end;
+				end;
+			end;
+
+			if Result <> InstallSuccessful then begin
+				break;
+			end;
+		end;
+
+		// only leave not installed products for error message
+		for i := 0 to productCount - finishCount - 1 do begin
+			products[i] := products[i+finishCount];
+		end;
+		SetArrayLength(products, productCount - finishCount);
+
+		DependencyPage.Hide;
+	end;
+end;
+
+{
+	--------------------
+	INNO EVENT FUNCTIONS
+	--------------------
+}
+
+function PrepareToInstall(var NeedsRestart: Boolean): String;
+{
+	Before the "preparing to install" page.
+	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
+}
+var
+	i: Integer;
+	s: String;
+begin
+	delayedReboot := false;
+
+	case InstallProducts() of
+		InstallError: begin
+			s := CustomMessage('depinstall_error');
+
+			for i := 0 to GetArrayLength(products) - 1 do begin
+				s := s + #13 + '	' + products[i].Title;
+			end;
+
+			Result := s;
+			end;
+		InstallRebootRequired: begin
+			Result := products[0].Title;
+			NeedsRestart := true;
+
+			// write into the registry that the installer needs to be executed again after restart
+			RegWriteStringValue(HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InstallBootstrap', ExpandConstant('{srcexe}'));
+			end;
+	end;
+end;
+
+function NeedRestart: Boolean;
+{
+	Checks whether a restart is needed at the end of install
+	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
+}
+begin
+	Result := delayedReboot;
+end;
+
+function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
+{
+	Just before the "ready" page.
+	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
+}
+var
+	s: String;
+begin
+	if downloadMemo <> '' then
+		s := s + CustomMessage('depdownload_memo_title') + ':' + NewLine + FmtMessage(downloadMemo, [Space]) + NewLine;
+	if installMemo <> '' then
+		s := s + CustomMessage('depinstall_memo_title') + ':' + NewLine + FmtMessage(installMemo, [Space]) + NewLine;
+
+	if MemoDirInfo <> '' then
+		s := s + MemoDirInfo + NewLine + NewLine;
+	if MemoGroupInfo <> '' then
+		s := s + MemoGroupInfo + NewLine + NewLine;
+	if MemoTasksInfo <> '' then
+		s := s + MemoTasksInfo;
+
+	Result := s
+end;
+
+function NextButtonClick(CurPageID: Integer): Boolean;
+{
+	At each "next" button click
+	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
+}
+begin
+	Result := true;
+
+	if CurPageID = wpReady then begin
+		if downloadMemo <> '' then begin
+			// change isxdl language only if it is not english because isxdl default language is already english
+			if (ActiveLanguage() <> 'en') then begin
+				ExtractTemporaryFile(CustomMessage('isxdl_langfile'));
+				isxdl_SetOption('language', ExpandConstant('{tmp}{\}') + CustomMessage('isxdl_langfile'));
+			end;
+
+			if isxdl_DownloadFiles(StrToInt(ExpandConstant('{wizardhwnd}'))) = 0 then
+				Result := false;
+		end;
+	end;
+end;
+
+{
+	-----------------------------
+	ARCHITECTURE HELPER FUNCTIONS
+	-----------------------------
+}
+
+function IsX86: Boolean;
+{
+	Gets whether the computer is x86 (32 bits).
+}
+begin
+	Result := isForcedX86 or (ProcessorArchitecture = paX86) or (ProcessorArchitecture = paUnknown);
+end;
+
+function IsX64: Boolean;
+{
+	Gets whether the computer is x64 (64 bits).
+}
+begin
+	Result := (not isForcedX86) and Is64BitInstallMode and (ProcessorArchitecture = paX64);
+end;
+
+function IsIA64: Boolean;
+{
+	Gets whether the computer is IA64 (Itanium 64 bits).
+}
+begin
+	Result := (not isForcedX86) and Is64BitInstallMode and (ProcessorArchitecture = paIA64);
+end;
+
+function GetString(x86, x64, ia64: String): String;
+{
+	Gets a string depending on the computer architecture.
+	Parameters:
+		x86: the string if the computer is x86
+		x64: the string if the computer is x64
+		ia64: the string if the computer is IA64
+}
+begin
+	if IsX64() and (x64 <> '') then begin
+		Result := x64;
+	end else if IsIA64() and (ia64 <> '') then begin
+		Result := ia64;
+	end else begin
+		Result := x86;
+	end;
+end;
+
+function GetArchitectureString(): String;
+{
+	Gets the "standard" architecture suffix string.
+	Returns either _x64, _ia64 or nothing.
+}
+begin
+	if IsX64() then begin
+		Result := '_x64';
+	end else if IsIA64() then begin
+		Result := '_ia64';
+	end else begin
+		Result := '';
+	end;
+end;
+
+procedure SetForceX86(value: Boolean);
+{
+	Forces the setup to use X86 products
+}
+begin
+	isForcedX86 := value;
+end;

+ 92 - 0
Installer/scripts/products/dotnetfxversion.iss

@@ -0,0 +1,92 @@
+[Code]
+type
+	NetFXType = (NetFx10, NetFx11, NetFx20, NetFx30, NetFx35, NetFx40Client, NetFx40Full, NetFx4x);
+
+const
+	netfx11plus_reg = 'Software\Microsoft\NET Framework Setup\NDP\';
+
+function dotnetfxinstalled(version: NetFXType; lcid: String): Boolean;
+var
+	regVersion: Cardinal;
+	regVersionString: String;
+begin
+	if (lcid <> '') then
+		lcid := '\' + lcid;
+
+	case version of
+		NetFx10:
+			Result := RegQueryStringValue(HKLM, 'Software\Microsoft\.NETFramework\Policy\v1.0\3705', 'Install', regVersionString) and (regVersionString <> '');
+		NetFx11:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v1.1.4322' + lcid, 'Install', regVersion) and (regVersion <> 0);
+		NetFx20:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v2.0.50727' + lcid, 'Install', regVersion) and (regVersion <> 0);
+		NetFx30:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.0\Setup' + lcid, 'InstallSuccess', regVersion) and (regVersion <> 0);
+		NetFx35:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.5' + lcid, 'Install', regVersion) and (regVersion <> 0);
+		NetFx40Client:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Client' + lcid, 'Install', regVersion) and (regVersion <> 0);
+		NetFx40Full:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Install', regVersion) and (regVersion <> 0);
+		NetFx4x:
+			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Release', regVersion) and (regVersion >= 378389); // 4.5.0+
+	end;
+end;
+
+function dotnetfxspversion(version: NetFXType; lcid: String): Integer;
+var
+	regVersion: Cardinal;
+begin
+	if (lcid <> '') then
+		lcid := '\' + lcid;
+
+	case version of
+		NetFx10:
+			// not supported
+			regVersion := -1;
+		NetFx11:
+			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v1.1.4322' + lcid, 'SP', regVersion)) then
+				regVersion := -1;
+		NetFx20:
+			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v2.0.50727' + lcid, 'SP', regVersion)) then
+				regVersion := -1;
+		NetFx30:
+			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.0' + lcid, 'SP', regVersion)) then
+				regVersion := -1;
+		NetFx35:
+			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.5' + lcid, 'SP', regVersion)) then
+				regVersion := -1;
+		NetFx40Client:
+			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Client' + lcid, 'Servicing', regVersion)) then
+				regVersion := -1;
+		NetFx40Full:
+			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Servicing', regVersion)) then
+				regVersion := -1;
+		NetFx4x:
+			if (RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Release', regVersion)) then begin
+				if (regVersion >= 528040) then
+					regVersion := 80 // 4.8.0+ 
+				else if (regVersion >= 461808) then
+					regVersion := 72 // 4.7.2+
+				else if (regVersion >= 461308) then
+					regVersion := 71 // 4.7.1+
+				else if (regVersion >= 460798) then
+					regVersion := 70 // 4.7.0+
+				else if (regVersion >= 394802) then
+					regVersion := 62 // 4.6.2+
+				else if (regVersion >= 394254) then
+					regVersion := 61 // 4.6.1+
+				else if (regVersion >= 393295) then
+					regVersion := 60 // 4.6.0+
+				else if (regVersion >= 379893) then
+					regVersion := 52 // 4.5.2+
+				else if (regVersion >= 378675) then
+					regVersion := 51 // 4.5.1+
+				else if (regVersion >= 378389) then
+					regVersion := 50 // 4.5.0+
+				else
+					regVersion := -1;
+			end;
+	end;
+	Result := regVersion;
+end;

+ 27 - 0
Installer/scripts/products/netcore31.iss

@@ -0,0 +1,27 @@
+// requires Windows 10 Version 1607+, Windows 7 SP1+, Windows 8.1, Windows Server 2012 R2
+// https://dotnet.microsoft.com/download/dotnet-core/3.1
+
+[CustomMessages]
+netcore31_title=.NET Core Runtime 3.1.6 (x86)
+netcore31_title_x64=.NET Core Runtime 3.1.6 (x64)
+
+netcore31_size=23 MB
+netcore31_size_x64=26 MB
+
+[Code]
+const
+	netcore31_url = 'http://go.microsoft.com/fwlink/?linkid=2137641';
+	netcore31_url_x64 = 'http://go.microsoft.com/fwlink/?linkid=2137640';
+
+procedure netcore31();
+begin
+	if (not IsIA64()) then begin
+		if not netcoreinstalled(Core, '3.1.6') then
+			AddProduct('netcore31' + GetArchitectureString() + '.exe',
+				'/lcid ' + CustomMessage('lcid') + ' /passive /norestart',
+				CustomMessage('netcore31_title' + GetArchitectureString()),
+				CustomMessage('netcore31_size' + GetArchitectureString()),
+				GetString(netcore31_url, netcore31_url_x64, ''),
+				false, false, false);
+	end;
+end;

+ 27 - 0
Installer/scripts/products/netcore31desktop.iss

@@ -0,0 +1,27 @@
+// requires Windows 10 Version 1607+, Windows 7 SP1+, Windows 8.1, Windows Server 2012 R2
+// https://dotnet.microsoft.com/download/dotnet-core/3.1
+
+[CustomMessages]
+netcore31desktop_title=.NET Desktop Runtime 3.1.6 (x86)
+netcore31desktop_title_x64=.NET Desktop Runtime 3.1.6 (x64)
+
+netcore31desktop_size=23 MB
+netcore31desktop_size_x64=26 MB
+
+[Code]
+const
+	netcore31desktop_url = 'http://go.microsoft.com/fwlink/?linkid=2137844';
+	netcore31desktop_url_x64 = 'http://go.microsoft.com/fwlink/?linkid=2137941';
+
+procedure netcore31desktop();
+begin
+	if (not IsIA64()) then begin
+		if not netcoreinstalled(Desktop, '3.1.6') then
+			AddProduct('netcore31desktop' + GetArchitectureString() + '.exe',
+				'/lcid ' + CustomMessage('lcid') + ' /passive /norestart',
+				CustomMessage('netcore31desktop_title' + GetArchitectureString()),
+				CustomMessage('netcore31desktop_size' + GetArchitectureString()),
+				GetString(netcore31desktop_url, netcore31desktop_url_x64, ''),
+				false, false, false);
+	end;
+end;

+ 32 - 0
Installer/scripts/products/netcorecheck.iss

@@ -0,0 +1,32 @@
+// NetCoreCheck tool is necessary for detecting if a specific version of .NET Core/.NET 5.0 is installed: https://github.com/dotnet/runtime/issues/36479
+// source code: https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
+// download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256
+// download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504
+
+[Files]
+Source: "src\netcorecheck.exe"; Flags: dontcopy noencryption
+Source: "src\netcorecheck_x64.exe"; Flags: dontcopy noencryption
+
+[Code]
+type
+	NetCoreRuntimeType = (Asp, Core, Desktop);
+
+function netcoreinstalled(runtime: NetCoreRuntimeType; version: String): Boolean;
+var
+	netcoreRuntime: String;
+	resultCode: Integer;
+begin
+	case runtime of
+		Asp:
+			netcoreRuntime := 'Microsoft.AspNetCore.App';
+		Core:
+			netcoreRuntime := 'Microsoft.NETCore.App';
+		Desktop:
+			netcoreRuntime := 'Microsoft.WindowsDesktop.App';
+	end;
+
+	if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureString() + '.exe') then
+		ExtractTemporaryFile('netcorecheck' + GetArchitectureString() + '.exe');
+
+	Result := Exec(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureString() + '.exe', netcoreRuntime + ' ' + version, '', SW_HIDE, ewWaitUntilTerminated, resultCode) and (resultCode = 0);
+end;

+ 60 - 0
Installer/scripts/products/stringversion.iss

@@ -0,0 +1,60 @@
+[Code]
+function stringtoversion(var temp: String): Integer;
+var
+	part: String;
+	pos1: Integer;
+
+begin
+	if (Length(temp) = 0) then begin
+		Result := -1;
+		Exit;
+	end;
+
+	pos1 := Pos('.', temp);
+	if (pos1 = 0) then begin
+		Result := StrToInt(temp);
+		temp := '';
+	end else begin
+		part := Copy(temp, 1, pos1 - 1);
+		temp := Copy(temp, pos1 + 1, Length(temp));
+		Result := StrToInt(part);
+	end;
+end;
+
+function compareinnerversion(var x, y: String): Integer;
+var
+	num1, num2: Integer;
+
+begin
+	num1 := stringtoversion(x);
+	num2 := stringtoversion(y);
+	if (num1 = -1) and (num2 = -1) then begin
+		Result := 0;
+		Exit;
+	end;
+
+	if (num1 < 0) then begin
+		num1 := 0;
+	end;
+	if (num2 < 0) then begin
+		num2 := 0;
+	end;
+
+	if (num1 < num2) then begin
+		Result := -1;
+	end else if (num1 > num2) then begin
+		Result := 1;
+	end else begin
+		Result := compareinnerversion(x, y);
+	end;
+end;
+
+function compareversion(versionA, versionB: String): Integer;
+var
+  temp1, temp2: String;
+
+begin
+	temp1 := versionA;
+	temp2 := versionB;
+	Result := compareinnerversion(temp1, temp2);
+end;

+ 47 - 0
Installer/scripts/products/winversion.iss

@@ -0,0 +1,47 @@
+[Code]
+var
+	WindowsVersion: TWindowsVersion;
+
+procedure initwinversion();
+begin
+	GetWindowsVersionEx(WindowsVersion);
+end;
+
+function exactwinversion(MajorVersion, MinorVersion: Integer): Boolean;
+begin
+	Result := (WindowsVersion.Major = MajorVersion) and (WindowsVersion.Minor = MinorVersion);
+end;
+
+function minwinversion(MajorVersion, MinorVersion: Integer): Boolean;
+begin
+	Result := (WindowsVersion.Major > MajorVersion) or ((WindowsVersion.Major = MajorVersion) and (WindowsVersion.Minor >= MinorVersion));
+end;
+
+function maxwinversion(MajorVersion, MinorVersion: Integer): Boolean;
+begin
+	Result := (WindowsVersion.Major < MajorVersion) or ((WindowsVersion.Major = MajorVersion) and (WindowsVersion.Minor <= MinorVersion));
+end;
+
+function exactwinspversion(MajorVersion, MinorVersion, SpVersion: Integer): Boolean;
+begin
+	if exactwinversion(MajorVersion, MinorVersion) then
+		Result := WindowsVersion.ServicePackMajor = SpVersion
+	else
+		Result := true;
+end;
+
+function minwinspversion(MajorVersion, MinorVersion, SpVersion: Integer): Boolean;
+begin
+	if exactwinversion(MajorVersion, MinorVersion) then
+		Result := WindowsVersion.ServicePackMajor >= SpVersion
+	else
+		Result := true;
+end;
+
+function maxwinspversion(MajorVersion, MinorVersion, SpVersion: Integer): Boolean;
+begin
+	if exactwinversion(MajorVersion, MinorVersion) then
+		Result := WindowsVersion.ServicePackMajor <= SpVersion
+	else
+		Result := true;
+end;

BIN
Installer/src/netcorecheck.exe


BIN
Installer/src/netcorecheck_x64.exe


+ 1 - 0
PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller.csproj

@@ -4,6 +4,7 @@
     <OutputType>WinExe</OutputType>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <UseWPF>true</UseWPF>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
   </PropertyGroup>
 
   <ItemGroup>

+ 28 - 22
PixiEditor.UpdateInstaller/ViewModelMain.cs

@@ -1,12 +1,34 @@
-using System;
+using PixiEditor.UpdateModule;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
-using PixiEditor.UpdateModule;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
 
 namespace PixiEditor.UpdateInstaller
 {
     public class ViewModelMain : ViewModelBase
     {
-        private float progressValue;
+        public ViewModelMain Current { get; private set; }
+        public UpdateModule.UpdateInstaller Installer { get; set; }
+
+        public string UpdateDirectory { get; private set; }
+
+        private float _progressValue;
+
+        public float ProgressValue
+        {
+            get => _progressValue;
+            set 
+            { 
+                _progressValue = value;
+                RaisePropertyChanged(nameof(ProgressValue));
+            }
+        }
 
         public ViewModelMain()
         {
@@ -20,29 +42,13 @@ namespace PixiEditor.UpdateInstaller
             UpdateDirectory = updateDirectory;
         }
 
-        public ViewModelMain Current { get; }
-
-        public UpdateModule.UpdateInstaller Installer { get; set; }
-
-        public string UpdateDirectory { get; }
-
-        public float ProgressValue
-        {
-            get => progressValue;
-            set
-            {
-                progressValue = value;
-                RaisePropertyChanged(nameof(ProgressValue));
-            }
-        }
-
         public void InstallUpdate()
         {
-            string[] files = Directory.GetFiles(UpdateDirectory, "update-*.zip");
+            string[] files = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.zip");
 
             if (files.Length > 0)
             {
-                Installer = new UpdateModule.UpdateInstaller(files[0]);
+                Installer = new UpdateModule.UpdateInstaller(files[0], UpdateDirectory);
                 Installer.ProgressChanged += Installer_ProgressChanged;
                 Installer.Install();
             }
@@ -57,4 +63,4 @@ namespace PixiEditor.UpdateInstaller
             ProgressValue = e.Progress;
         }
     }
-}
+}

+ 76 - 0
PixiEditor.UpdateInstaller/app.manifest

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <!-- UAC Manifest Options
+             If you want to change the Windows User Account Control level replace the 
+             requestedExecutionLevel node with one of the following.
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+            Specifying requestedExecutionLevel element will disable file and registry virtualization. 
+            Remove this element if your application requires this virtualization for backwards
+            compatibility.
+        -->
+        <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
+
+      <!-- Windows 7 -->
+      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
+
+      <!-- Windows 8 -->
+      <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
+
+      <!-- Windows 8.1 -->
+      <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
+
+      <!-- Windows 10 -->
+      <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
+
+    </application>
+  </compatibility>
+
+  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
+       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
+       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
+       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
+  <!--
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+    </windowsSettings>
+  </application>
+  -->
+
+  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
+  <!--
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>
+  -->
+
+</assembly>

+ 14 - 6
PixiEditor.UpdateModule/UpdateDownloader.cs

@@ -9,8 +9,7 @@ namespace PixiEditor.UpdateModule
 {
     public static class UpdateDownloader
     {
-        public static readonly string DownloadLocation = AppDomain.CurrentDomain.BaseDirectory;
-
+        public static string DownloadLocation = Path.Join(Path.GetTempPath(), "PixiEditor");
         public static async Task DownloadReleaseZip(ReleaseInfo release)
         {
             Asset matchingAsset = GetMatchingAsset(release);
@@ -19,20 +18,29 @@ namespace PixiEditor.UpdateModule
             {
                 client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
                 client.DefaultRequestHeaders.Add("Accept", "application/octet-stream");
-                HttpResponseMessage response = await client.GetAsync(matchingAsset.Url);
+                var response = await client.GetAsync(matchingAsset.Url);
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     byte[] bytes = await response.Content.ReadAsByteArrayAsync();
+                    CreateTempDirectory();
                     File.WriteAllBytes(Path.Join(DownloadLocation, $"update-{release.TagName}.zip"), bytes);
                 }
             }
         }
 
+        public static void CreateTempDirectory()
+        {
+            if (!Directory.Exists(DownloadLocation))
+            {
+                Directory.CreateDirectory(DownloadLocation);
+            }
+        }
+
         private static Asset GetMatchingAsset(ReleaseInfo release)
         {
             string arch = IntPtr.Size == 8 ? "x64" : "x86";
-            return release.Assets.First(x => x.ContentType == "application/x-zip-compressed"
-                                             && x.Name.Contains(arch));
+            return release.Assets.First(x => x.ContentType.Contains("zip")
+            && x.Name.Contains(arch));
         }
     }
-}
+}

+ 20 - 19
PixiEditor.UpdateModule/UpdateInstaller.cs

@@ -8,38 +8,38 @@ namespace PixiEditor.UpdateModule
     public class UpdateInstaller
     {
         public const string TargetDirectoryName = "UpdateFiles";
-        private float progress;
-
-        public UpdateInstaller(string archiveFileName)
-        {
-            ArchiveFileName = archiveFileName;
-        }
+        public static string UpdateFilesPath = Path.Join(UpdateDownloader.DownloadLocation, TargetDirectoryName);
 
         public event EventHandler<UpdateProgressChangedEventArgs> ProgressChanged;
-
-        public float Progress
+        private float _progress = 0;
+        public float Progress 
         {
-            get => progress;
+            get => _progress;
             set
             {
-                progress = value;
+                _progress = value;
                 ProgressChanged?.Invoke(this, new UpdateProgressChangedEventArgs(value));
             }
         }
-
         public string ArchiveFileName { get; set; }
+        public string TargetDirectory { get; set; }
+
+        public UpdateInstaller(string archiveFileName, string targetDirectory)
+        {
+            ArchiveFileName = archiveFileName;
+            TargetDirectory = targetDirectory;
+        }
 
         public void Install()
         {
-            Process[] processes = Process.GetProcessesByName("PixiEditor");
+            var processes = Process.GetProcessesByName("PixiEditor");
             if (processes.Length > 0)
             {
                 processes[0].WaitForExit();
             }
-
-            ZipFile.ExtractToDirectory(ArchiveFileName, TargetDirectoryName, true);
-            Progress = 25; // 25% for unzip
-            string dirWithFiles = Directory.GetDirectories(TargetDirectoryName)[0];
+            ZipFile.ExtractToDirectory(ArchiveFileName, UpdateFilesPath, true);
+            Progress = 25; //25% for unzip
+            string dirWithFiles = Directory.GetDirectories(UpdateFilesPath)[0];
             string[] files = Directory.GetFiles(dirWithFiles);
             CopyFilesToDestination(files);
             DeleteArchive();
@@ -49,12 +49,13 @@ namespace PixiEditor.UpdateModule
         private void DeleteArchive()
         {
             File.Delete(ArchiveFileName);
+            Directory.Delete(UpdateFilesPath, true);
         }
 
         private void CopyFilesToDestination(string[] files)
         {
-            float fileCopiedVal = 74f / files.Length; // 74% is reserved for copying
-            string destinationDir = Path.GetDirectoryName(ArchiveFileName);
+            float fileCopiedVal = 74f / files.Length; //74% is reserved for copying
+            string destinationDir = TargetDirectory;
             foreach (string file in files)
             {
                 string targetFileName = Path.GetFileName(file);
@@ -63,4 +64,4 @@ namespace PixiEditor.UpdateModule
             }
         }
     }
-}
+}

+ 136 - 135
PixiEditor/Helpers/GlobalMouseHook.cs

@@ -1,135 +1,136 @@
-using System;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-namespace PixiEditor.Helpers
-{
-    public delegate void MouseUpEventHandler(object sender, Point p);
-
-    // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
-    [ExcludeFromCodeCoverage]
-    public static class GlobalMouseHook
-    {
-        private const int WhMouseLl = 14;
-        private const int WmLbuttonup = 0x0202;
-        private static int mouseHookHandle;
-        private static HookProc mouseDelegate;
-
-        private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
-
-        public static event MouseUpEventHandler OnMouseUp
-        {
-            add
-            {
-                Subscribe();
-                MouseUp += value;
-            }
-
-            remove
-            {
-                MouseUp -= value;
-                Unsubscribe();
-            }
-        }
-
-        private static event MouseUpEventHandler MouseUp;
-
-        public static void RaiseMouseUp()
-        {
-            MouseUp?.Invoke(default, default);
-        }
-
-        private static void Unsubscribe()
-        {
-            if (mouseHookHandle != 0)
-            {
-                int result = UnhookWindowsHookEx(mouseHookHandle);
-                mouseHookHandle = 0;
-                mouseDelegate = null;
-                if (result == 0)
-                {
-                    int errorCode = Marshal.GetLastWin32Error();
-                    throw new Win32Exception(errorCode);
-                }
-            }
-        }
-
-        private static void Subscribe()
-        {
-            if (mouseHookHandle == 0)
-            {
-                mouseDelegate = MouseHookProc;
-                mouseHookHandle = SetWindowsHookEx(
-                    WhMouseLl,
-                    mouseDelegate,
-                    GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
-                    0);
-                if (mouseHookHandle == 0)
-                {
-                    int errorCode = Marshal.GetLastWin32Error();
-                    throw new Win32Exception(errorCode);
-                }
-            }
-        }
-
-        private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
-        {
-            if (nCode >= 0)
-            {
-                Msllhookstruct mouseHookStruct = (Msllhookstruct)Marshal.PtrToStructure(lParam, typeof(Msllhookstruct));
-                if (wParam == WmLbuttonup)
-                {
-                    if (MouseUp != null)
-                    {
-                        MouseUp.Invoke(null, new System.Windows.Point(mouseHookStruct.Pt.X, mouseHookStruct.Pt.Y));
-                    }
-                }
-            }
-
-            return CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
-        }
-
-        [DllImport(
-            "user32.dll",
-            CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall,
-            SetLastError = true)]
-        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
-
-        [DllImport(
-            "user32.dll",
-            CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall,
-            SetLastError = true)]
-        private static extern int UnhookWindowsHookEx(int idHook);
-
-        [DllImport(
-            "user32.dll",
-            CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall)]
-        private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
-
-        [DllImport("kernel32.dll")]
-        private static extern IntPtr GetModuleHandle(string name);
-
-        [StructLayout(LayoutKind.Sequential)]
-        private struct Point
-        {
-            public readonly int X;
-            public readonly int Y;
-        }
-
-        [StructLayout(LayoutKind.Sequential)]
-        private struct Msllhookstruct
-        {
-            public readonly Point Pt;
-            public readonly uint MouseData;
-            public readonly uint Flags;
-            public readonly uint Time;
-            public readonly IntPtr DwExtraInfo;
-        }
-    }
-}
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Input;
+
+namespace PixiEditor.Helpers
+{
+    // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
+    [ExcludeFromCodeCoverage]
+    public static class GlobalMouseHook
+    {
+        private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
+        private static int _mouseHookHandle;
+        private static HookProc _mouseDelegate;
+
+        private static event MouseUpEventHandler MouseUp;
+        public static event MouseUpEventHandler OnMouseUp
+        {
+            add
+            {
+                Subscribe();
+                MouseUp += value;
+            }
+            remove
+            {
+                MouseUp -= value;
+                Unsubscribe();
+            }
+        }
+
+        public static void RaiseMouseUp()
+        {
+            MouseUp?.Invoke(default, default, default);
+        }
+
+        private static void Unsubscribe()
+        {
+            if (_mouseHookHandle != 0)
+            {
+                int result = UnhookWindowsHookEx(_mouseHookHandle);
+                _mouseHookHandle = 0;
+                _mouseDelegate = null;
+                if (result == 0)
+                {
+                    int errorCode = Marshal.GetLastWin32Error();
+                    throw new Win32Exception(errorCode);
+                }
+            }
+        }
+
+        private static void Subscribe()
+        {
+            if (_mouseHookHandle == 0)
+            {
+                _mouseDelegate = MouseHookProc;
+                _mouseHookHandle = SetWindowsHookEx(
+                    WH_MOUSE_LL,
+                    _mouseDelegate,
+                    GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
+                    0);
+                if (_mouseHookHandle == 0)
+                {
+                    int errorCode = Marshal.GetLastWin32Error();
+                    throw new Win32Exception(errorCode);
+                }
+            }
+        }
+
+        private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
+        {
+            if (nCode >= 0)
+            {
+                MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
+                if (wParam == WM_LBUTTONUP || wParam == WM_MBUTTONUP || wParam == WM_RBUTTONUP)
+                {
+                    if (MouseUp != null)
+                    {
+                        MouseButton button = wParam == WM_LBUTTONUP ? MouseButton.Left
+                            : wParam == WM_MBUTTONUP ? MouseButton.Middle : MouseButton.Right;
+                        MouseUp.Invoke(null, new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y), button);
+                    }
+                }
+            }
+            return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
+        }
+
+        private const int WH_MOUSE_LL = 14;
+        private const int WM_LBUTTONUP = 0x0202;
+        private const int WM_MBUTTONUP = 0x0208;
+        private const int WM_RBUTTONUP = 0x0205;
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct POINT
+        {
+            public int x;
+            public int y;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct MSLLHOOKSTRUCT
+        {
+            public POINT pt;
+            public uint mouseData;
+            public uint flags;
+            public uint time;
+            public IntPtr dwExtraInfo;
+        }
+
+        [DllImport("user32.dll", 
+            CharSet = CharSet.Auto,
+            CallingConvention = CallingConvention.StdCall, 
+            SetLastError = true)]
+        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
+
+        [DllImport(
+            "user32.dll",
+            CharSet = CharSet.Auto,
+            CallingConvention = CallingConvention.StdCall,
+            SetLastError = true)]
+        private static extern int UnhookWindowsHookEx(int idHook);
+
+        [DllImport(
+            "user32.dll", 
+            CharSet = CharSet.Auto,
+            CallingConvention = CallingConvention.StdCall)]
+        private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
+
+        [DllImport("kernel32.dll")]
+        private static extern IntPtr GetModuleHandle(string name);
+    }
+
+    public delegate void MouseUpEventHandler(object sender, Point p, MouseButton button);
+}

BIN
PixiEditor/Images/MoveViewportImage.png


+ 12 - 9
PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs

@@ -1,18 +1,21 @@
 using System;
 using PixiEditor.Models.DataHolders;
 
-public class BitmapChangedEventArgs : EventArgs
+namespace PixiEditor.Models.Controllers
 {
-    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues, int changedLayerIndex)
+    public class BitmapChangedEventArgs : EventArgs
     {
-        PixelsChanged = pixelsChanged;
-        OldPixelsValues = oldPixelsValues;
-        ChangedLayerIndex = changedLayerIndex;
-    }
+        public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues, int changedLayerIndex)
+        {
+            PixelsChanged = pixelsChanged;
+            OldPixelsValues = oldPixelsValues;
+            ChangedLayerIndex = changedLayerIndex;
+        }
 
-    public BitmapPixelChanges PixelsChanged { get; set; }
+        public BitmapPixelChanges PixelsChanged { get; set; }
 
-    public BitmapPixelChanges OldPixelsValues { get; set; }
+        public BitmapPixelChanges OldPixelsValues { get; set; }
 
-    public int ChangedLayerIndex { get; set; }
+        public int ChangedLayerIndex { get; set; }
+    }
 }

+ 274 - 285
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,285 +1,274 @@
-using System;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Events;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class BitmapManager : NotifyableObject
-    {
-        private Document activeDocument;
-
-        private Layer previewLayer;
-        private Tool selectedTool;
-
-        public BitmapManager()
-        {
-            MouseController = new MouseMovementController();
-            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
-            MouseController.MousePositionChanged += Controller_MousePositionChanged;
-            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
-            BitmapOperations = new BitmapOperationsUtility(this);
-            ReadonlyToolUtility = new ReadonlyToolUtility();
-        }
-
-        public event EventHandler<LayersChangedEventArgs> LayersChanged;
-
-        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
-
-        public MouseMovementController MouseController { get; set; }
-
-        public Tool SelectedTool
-        {
-            get => selectedTool;
-            private set
-            {
-                selectedTool = value;
-                RaisePropertyChanged("SelectedTool");
-            }
-        }
-
-        public Layer PreviewLayer
-        {
-            get => previewLayer;
-            set
-            {
-                previewLayer = value;
-                RaisePropertyChanged("PreviewLayer");
-            }
-        }
-
-        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
-
-        public Color PrimaryColor { get; set; }
-
-        public int ToolSize
-        {
-            get => SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") != null
-                ? SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value
-                : 1;
-            set
-            {
-                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is var toolSize)
-                {
-                    toolSize.Value = value;
-                    HighlightPixels(MousePositionConverter.CurrentCoordinates);
-                }
-            }
-        }
-
-        public BitmapOperationsUtility BitmapOperations { get; set; }
-
-        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
-
-        public Document ActiveDocument
-        {
-            get => activeDocument;
-            set
-            {
-                activeDocument = value;
-                RaisePropertyChanged("ActiveDocument");
-                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
-            }
-        }
-
-        /// <summary>
-        ///     Returns if tool is BitmapOperationTool.
-        /// </summary>
-        public static bool IsOperationTool(Tool tool)
-        {
-            return tool is BitmapOperationTool;
-        }
-
-        public void SetActiveTool(Tool tool)
-        {
-            PreviewLayer = null;
-            SelectedTool?.Toolbar.SaveToolbarSettings();
-            SelectedTool = tool;
-            SelectedTool.Toolbar.LoadSharedSettings();
-        }
-
-        public void SetActiveLayer(int index)
-        {
-            if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
-            {
-                ActiveDocument.ActiveLayer.IsActive = false;
-            }
-
-            ActiveDocument.ActiveLayerIndex = index;
-            ActiveDocument.ActiveLayer.IsActive = true;
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
-        }
-
-        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
-        {
-            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
-            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
-        }
-
-        public void AddNewLayer(string name, bool setAsActive = true)
-        {
-            AddNewLayer(name, 0, 0, setAsActive);
-        }
-
-        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
-        {
-            ActiveDocument.Layers.Add(new Layer(name, width, height)
-            {
-                MaxHeight = ActiveDocument.Height,
-                MaxWidth = ActiveDocument.Width
-            });
-            if (setAsActive)
-            {
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
-            }
-
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
-        }
-
-        public void RemoveLayer(int layerIndex)
-        {
-            if (ActiveDocument.Layers.Count == 0)
-            {
-                return;
-            }
-
-            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
-            ActiveDocument.Layers.RemoveAt(layerIndex);
-            if (wasActive)
-            {
-                SetActiveLayer(0);
-            }
-            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
-            {
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
-            }
-        }
-
-        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
-        {
-            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
-            {
-                if (IsOperationTool(SelectedTool))
-                {
-                    BitmapOperations.ExecuteTool(
-                        newPosition,
-                        MouseController.LastMouseMoveCoordinates.ToList(),
-                        (BitmapOperationTool)SelectedTool);
-                }
-                else
-                {
-                    ReadonlyToolUtility.ExecuteTool(
-                        MouseController.LastMouseMoveCoordinates.ToArray(),
-                        (ReadonlyTool)SelectedTool);
-                }
-            }
-        }
-
-        public void GeneratePreviewLayer()
-        {
-            if (ActiveDocument != null)
-            {
-                PreviewLayer = new Layer("_previewLayer")
-                {
-                    MaxWidth = ActiveDocument.Width,
-                    MaxHeight = ActiveDocument.Height
-                };
-            }
-        }
-
-        public WriteableBitmap GetCombinedLayersBitmap()
-        {
-            return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
-        }
-
-        /// <summary>
-        ///     Returns if selected tool is BitmapOperationTool.
-        /// </summary>
-        public bool IsOperationTool()
-        {
-            return IsOperationTool(SelectedTool);
-        }
-
-        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
-        {
-            SelectedTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (Mouse.LeftButton == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
-            {
-                ExecuteTool(e.NewPosition, MouseController.ClickedOnCanvas);
-            }
-            else if (Mouse.LeftButton == MouseButtonState.Released)
-            {
-                HighlightPixels(e.NewPosition);
-            }
-        }
-
-        private bool IsDraggingViewport()
-        {
-            return Keyboard.IsKeyDown(Key.LeftShift) && !(SelectedTool is ShapeTool);
-        }
-
-        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
-        {
-            SelectedTool.OnMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            PreviewLayer = null;
-        }
-
-        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
-        {
-            SelectedTool.OnMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (IsOperationTool(SelectedTool) && ((BitmapOperationTool)SelectedTool).RequiresPreviewLayer)
-            {
-                BitmapOperations.StopAction();
-            }
-        }
-
-        private void HighlightPixels(Coordinates newPosition)
-        {
-            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || SelectedTool.HideHighlight)
-            {
-                return;
-            }
-
-            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(
-                CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
-            if (CanChangeHighlightOffset(highlightArea))
-            {
-                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y, 0, 0);
-            }
-            else if (!IsInsideBounds(highlightArea))
-            {
-                PreviewLayer = null;
-            }
-            else
-            {
-                GeneratePreviewLayer();
-                PreviewLayer.SetPixels(
-                    BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
-            }
-        }
-
-        private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
-        {
-            return highlightArea.Length > 0 && PreviewLayer != null &&
-                   IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
-        }
-
-        private bool IsInsideBounds(Coordinates[] highlightArea)
-        {
-            return highlightArea[0].X <= ActiveDocument.Width - 1 &&
-                   highlightArea[0].Y <= ActiveDocument.Height - 1 &&
-                   highlightArea[^1].X >= 0 && highlightArea[^1].Y >= 0;
-        }
-    }
-}
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Events;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.Tools;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class BitmapManager : NotifyableObject
+    {
+        public MouseMovementController MouseController { get; set; }
+
+        public Tool SelectedTool
+        {
+            get => _selectedTool;
+            private set
+            {
+                _selectedTool = value;
+                RaisePropertyChanged("SelectedTool");
+            }
+        }
+
+        public Layer PreviewLayer
+        {
+            get => _previewLayer;
+            set
+            {
+                _previewLayer = value;
+                RaisePropertyChanged("PreviewLayer");
+            }
+        }
+
+        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
+
+        public Color PrimaryColor { get; set; }
+
+        public int ToolSize
+        {
+            get => SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") != null
+            ? SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value
+            : 1;
+            set
+            {
+                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is var toolSize)
+                {
+                    toolSize.Value = value;
+                    HighlightPixels(MousePositionConverter.CurrentCoordinates);
+                }
+            }
+        }
+
+        public BitmapOperationsUtility BitmapOperations { get; set; }
+        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
+
+        public Document ActiveDocument
+        {
+            get => _activeDocument;
+            set
+            {
+                _activeDocument = value;
+                RaisePropertyChanged("ActiveDocument");
+                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
+            }
+        }
+
+        private Document _activeDocument;
+
+        private Layer _previewLayer;
+        private Tool _selectedTool;
+
+        public BitmapManager()
+        {
+            MouseController = new MouseMovementController();
+            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
+            MouseController.MousePositionChanged += Controller_MousePositionChanged;
+            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
+            MouseController.OnMouseDown += MouseController_OnMouseDown;
+            MouseController.OnMouseUp += MouseController_OnMouseUp;
+            BitmapOperations = new BitmapOperationsUtility(this);
+            ReadonlyToolUtility = new ReadonlyToolUtility();
+        }
+
+        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
+
+        private void MouseController_OnMouseDown(object sender, MouseEventArgs e)
+        {
+            SelectedTool.OnMouseDown(e);
+        }
+
+        private void MouseController_OnMouseUp(object sender, MouseEventArgs e)
+        {
+            SelectedTool.OnMouseUp(e);
+        }
+
+        public void SetActiveTool(Tool tool)
+        {
+            PreviewLayer = null;
+            SelectedTool?.Toolbar.SaveToolbarSettings();
+            SelectedTool = tool;
+            SelectedTool.Toolbar.LoadSharedSettings();
+        }
+
+        public void SetActiveLayer(int index)
+        {
+            if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
+                ActiveDocument.ActiveLayer.IsActive = false;
+            ActiveDocument.ActiveLayerIndex = index;
+            ActiveDocument.ActiveLayer.IsActive = true;
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
+        }
+
+        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        {
+            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
+            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
+        }
+
+        public void AddNewLayer(string name, bool setAsActive = true)
+        {
+            AddNewLayer(name, 0, 0, setAsActive);
+        }
+
+        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
+        {
+            ActiveDocument.Layers.Add(new Layer(name, width, height)
+            {
+                MaxHeight = ActiveDocument.Height,
+                MaxWidth = ActiveDocument.Width
+            });
+            if (setAsActive) SetActiveLayer(ActiveDocument.Layers.Count - 1);
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
+        }
+
+        public void RemoveLayer(int layerIndex)
+        {
+            if (ActiveDocument.Layers.Count == 0) return;
+
+            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
+            ActiveDocument.Layers.RemoveAt(layerIndex);
+            if (wasActive)
+                SetActiveLayer(0);
+            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
+                SetActiveLayer(ActiveDocument.Layers.Count - 1);
+        }
+
+        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
+        {
+            SelectedTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            if (Mouse.LeftButton == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
+            {
+                ExecuteTool(e.NewPosition, MouseController.ClickedOnCanvas);   
+            }
+            else if (Mouse.LeftButton == MouseButtonState.Released)
+            {
+                HighlightPixels(e.NewPosition);
+            }
+        }
+
+        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
+        {
+            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
+            {
+                if (IsOperationTool(SelectedTool))
+                {
+                    BitmapOperations.ExecuteTool(newPosition,
+                        MouseController.LastMouseMoveCoordinates.ToList(), (BitmapOperationTool)SelectedTool);
+                }
+                else
+                {
+                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates.ToArray(),
+                        (ReadonlyTool)SelectedTool);
+                }
+            }
+        }
+
+        private bool IsDraggingViewport()
+        {
+            return SelectedTool is MoveViewportTool;
+        }
+
+        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
+        {
+            SelectedTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            PreviewLayer = null;
+        }
+
+        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
+        {
+            SelectedTool.OnStoppedRecordingMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            if (IsOperationTool(SelectedTool) && ((BitmapOperationTool) SelectedTool).RequiresPreviewLayer)
+                BitmapOperations.StopAction();
+        }
+
+        public void GeneratePreviewLayer()
+        {
+            if (ActiveDocument != null)
+                PreviewLayer = new Layer("_previewLayer")
+                {
+                    MaxWidth = ActiveDocument.Width,
+                    MaxHeight = ActiveDocument.Height
+                };
+        }
+
+        private void HighlightPixels(Coordinates newPosition)
+        {
+            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || SelectedTool.HideHighlight) return;
+            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(
+                CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
+            if (CanChangeHighlightOffset(highlightArea))
+            {
+                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y,0,0);
+            }
+            else if (!IsInsideBounds(highlightArea))
+            {
+                PreviewLayer = null;
+            }
+            else
+            {
+                GeneratePreviewLayer();
+                PreviewLayer.SetPixels(
+                    BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
+            }
+        }
+
+        private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
+        {
+            return highlightArea.Length > 0 && PreviewLayer != null && 
+                   IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
+        }
+
+        private bool IsInsideBounds(Coordinates[] highlightArea)
+        {
+            return highlightArea[0].X <= ActiveDocument.Width - 1 &&
+                    highlightArea[0].Y <= ActiveDocument.Height - 1 &&
+                   highlightArea[^1].X >= 0 && highlightArea[^1].Y >= 0;
+        }
+
+        public WriteableBitmap GetCombinedLayersBitmap()
+        {
+            return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
+        }
+
+        /// <summary>
+        ///     Returns if selected tool is BitmapOperationTool
+        /// </summary>
+        /// <returns></returns>
+        public bool IsOperationTool()
+        {
+            return IsOperationTool(SelectedTool);
+        }
+
+        /// <summary>
+        ///     Returns if tool is BitmapOperationTool
+        /// </summary>
+        /// <param name="tool"></param>
+        /// <returns></returns>
+        public static bool IsOperationTool(Tool tool)
+        {
+            return tool is BitmapOperationTool;
+        }
+    }
+}

+ 26 - 12
PixiEditor/Models/Controllers/MouseMovementController.cs

@@ -1,22 +1,21 @@
 using System;
 using System.Collections.Generic;
+using System.Windows.Input;
+using Accessibility;
 using PixiEditor.Models.Position;
 
 namespace PixiEditor.Models.Controllers
 {
     public class MouseMovementController
     {
-        public event EventHandler StartedRecordingChanges;
-
-        public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
-
-        public event EventHandler StoppedRecordingChanges;
-
         public List<Coordinates> LastMouseMoveCoordinates { get; } = new List<Coordinates>();
-
         public bool IsRecordingChanges { get; private set; }
-
         public bool ClickedOnCanvas { get; set; }
+        public event EventHandler StartedRecordingChanges;
+        public event EventHandler<MouseEventArgs> OnMouseDown;
+        public event EventHandler<MouseEventArgs> OnMouseUp;
+        public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
+        public event EventHandler StoppedRecordingChanges;
 
         public void StartRecordingMouseMovementChanges(bool clickedOnCanvas)
         {
@@ -32,23 +31,38 @@ namespace PixiEditor.Models.Controllers
         public void RecordMouseMovementChange(Coordinates mouseCoordinates)
         {
             if (IsRecordingChanges)
-            {
                 if (LastMouseMoveCoordinates.Count == 0 || mouseCoordinates != LastMouseMoveCoordinates[^1])
                 {
                     LastMouseMoveCoordinates.Add(mouseCoordinates);
                     MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
                 }
-            }
         }
 
         /// <summary>
-        ///     Plain mouse move, does not affect mouse drag recordings.
+        ///     Plain mouse move, does not affect mouse drag recordings
         /// </summary>
+        /// <param name="mouseCoordinates"></param>
         public void MouseMoved(Coordinates mouseCoordinates)
         {
             MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
         }
 
+        /// <summary>
+        /// Plain mouse down, does not affect mouse recordings
+        /// </summary>
+        public void MouseDown(MouseEventArgs args)
+        {
+            OnMouseDown?.Invoke(this, args);
+        }
+
+        /// <summary>
+        /// Plain mouse up, does not affect mouse recordings
+        /// </summary>
+        public void MouseUp(MouseEventArgs args)
+        {
+            OnMouseUp?.Invoke(this, args);
+        }
+
         public void StopRecordingMouseMovementChanges()
         {
             if (IsRecordingChanges)
@@ -59,4 +73,4 @@ namespace PixiEditor.Models.Controllers
             }
         }
     }
-}
+}

+ 8 - 5
PixiEditor/Models/Controllers/MouseMovementEventArgs.cs

@@ -1,12 +1,15 @@
 using System;
 using PixiEditor.Models.Position;
 
-public class MouseMovementEventArgs : EventArgs
+namespace PixiEditor.Models.Controllers
 {
-    public MouseMovementEventArgs(Coordinates mousePosition)
+    public class MouseMovementEventArgs : EventArgs
     {
-        NewPosition = mousePosition;
-    }
+        public MouseMovementEventArgs(Coordinates mousePosition)
+        {
+            NewPosition = mousePosition;
+        }
 
-    public Coordinates NewPosition { get; set; }
+        public Coordinates NewPosition { get; set; }
+    }
 }

+ 96 - 87
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -1,87 +1,96 @@
-using System.Collections.Generic;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.ImageManipulation
-{
-    public static class BitmapUtils
-    {
-        /// <summary>
-        ///     Converts pixel bytes to WriteableBitmap.
-        /// </summary>
-        /// <param name="currentBitmapWidth">Width of bitmap.</param>
-        /// <param name="currentBitmapHeight">Height of bitmap.</param>
-        /// <param name="byteArray">Bitmap byte array.</param>
-        /// <returns>WriteableBitmap.</returns>
-        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
-        {
-            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
-            bitmap.FromByteArray(byteArray);
-            return bitmap;
-        }
-
-        /// <summary>
-        ///     Converts layers bitmaps into one bitmap.
-        /// </summary>
-        /// <param name="layers">Layers to combine.</param>
-        /// <param name="width">Width of final bitmap.</param>
-        /// <param name="height">Height of final bitmap.</param>
-        /// <returns>WriteableBitmap of layered bitmaps.</returns>
-        public static WriteableBitmap CombineLayers(Layer[] layers, int width, int height)
-        {
-            WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
-
-            using (finalBitmap.GetBitmapContext())
-            {
-                for (int i = 0; i < layers.Length; i++)
-                {
-                    for (int y = 0; y < finalBitmap.Height; y++)
-                    {
-                        for (int x = 0; x < finalBitmap.Width; x++)
-                        {
-                            Color color = layers[i].GetPixelWithOffset(x, y);
-                            color = Color.FromArgb((byte)(color.A * layers[i].Opacity), color.R, color.G, color.B);
-                            if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0)
-                            {
-                                finalBitmap.SetPixel(x, y, color);
-                            }
-                        }
-                    }
-                }
-            }
-
-            return finalBitmap;
-        }
-
-        public static Dictionary<Layer, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
-        {
-            Dictionary<Layer, Color[]> result = new Dictionary<Layer, Color[]>();
-
-            for (int i = 0; i < layers.Length; i++)
-            {
-                Color[] pixels = new Color[selection.Length];
-
-                using (layers[i].LayerBitmap.GetBitmapContext())
-                {
-                    for (int j = 0; j < pixels.Length; j++)
-                    {
-                        Coordinates position = layers[i].GetRelativePosition(selection[j]);
-                        if (position.X < 0 || position.X > layers[i].Width - 1 || position.Y < 0 ||
-                            position.Y > layers[i].Height - 1)
-                        {
-                            continue;
-                        }
-
-                        pixels[j] = layers[i].GetPixel(position.X, position.Y);
-                    }
-                }
-
-                result[layers[i]] = pixels;
-            }
-
-            return result;
-        }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using Color = System.Windows.Media.Color;
+
+namespace PixiEditor.Models.ImageManipulation
+{
+    public static class BitmapUtils
+    {
+        /// <summary>
+        ///     Converts pixel bytes to WriteableBitmap
+        /// </summary>
+        /// <param name="currentBitmapWidth">Width of bitmap</param>
+        /// <param name="currentBitmapHeight">Height of bitmap</param>
+        /// <param name="byteArray">Bitmap byte array</param>
+        /// <returns>WriteableBitmap</returns>
+        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight,
+            byte[] byteArray)
+        {
+            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
+            bitmap.FromByteArray(byteArray);
+            return bitmap;
+        }
+
+        /// <summary>
+        ///     Converts layers bitmaps into one bitmap.
+        /// </summary>
+        /// <param name="layers">Layers to combine</param>
+        /// <param name="width">Width of final bitmap</param>
+        /// <param name="height">Height of final bitmap</param>
+        /// <returns>WriteableBitmap of layered bitmaps</returns>
+        public static WriteableBitmap CombineLayers(Layer[] layers, int width, int height)
+        {
+            WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
+
+            using (finalBitmap.GetBitmapContext())
+            {
+                for (int i = 0; i < layers.Length; i++)
+                    for (int y = 0; y < finalBitmap.Height; y++)
+                        for (int x = 0; x < finalBitmap.Width; x++)
+                        {
+                            Color color = layers[i].GetPixelWithOffset(x, y);
+                            if (i > 0 && color.A < 255)
+                            {
+                                var lastLayerPixel = layers[i - 1].GetPixelWithOffset(x, y);
+                                byte r = (byte)((color.R * color.A / 255) + (lastLayerPixel.R * lastLayerPixel.A * (255 - color.A) / (255 * 255)));
+                                byte g = (byte)((color.G * color.A / 255) + (lastLayerPixel.G * lastLayerPixel.A * (255 - color.A) / (255 * 255)));
+                                byte b = (byte)((color.B * color.A / 255) + (lastLayerPixel.B * lastLayerPixel.A * (255 - color.A) / (255 * 255)));
+                                byte a = (byte)(color.A + (lastLayerPixel.A * (255 - color.A) / 255));
+                                color = Color.FromArgb((byte)(a * layers[i].Opacity), r, g, b);
+                            }
+                            else
+                            {
+                                color = Color.FromArgb((byte)(color.A * layers[i].Opacity), color.R, color.G, color.B);
+                            }
+                            if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0) finalBitmap.SetPixel(x, y, color);
+                        }
+            }
+
+            return finalBitmap;
+        }
+
+        public static Dictionary<Layer, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
+        {
+            Dictionary<Layer, Color[]> result = new Dictionary<Layer, Color[]>();
+
+            for (int i = 0; i < layers.Length; i++)
+            {
+                Color[] pixels = new Color[selection.Length];
+
+                using (layers[i].LayerBitmap.GetBitmapContext())
+                {
+
+                    for (int j = 0; j < pixels.Length; j++)
+                    {
+                        Coordinates position = layers[i].GetRelativePosition(selection[j]);
+                        if (position.X < 0 || position.X > layers[i].Width - 1 || position.Y < 0 ||
+                            position.Y > layers[i].Height - 1)
+                            continue;
+                        pixels[j] = layers[i].GetPixel(position.X, position.Y);
+                    }
+                }
+
+
+                result[layers[i]] = pixels;
+            }
+
+            return result;
+        }
+    }
+}

+ 25 - 0
PixiEditor/Models/Processes/ProcessHelper.cs

@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using System.Diagnostics;
+
+namespace PixiEditor.Models.Processes
+{
+    public static class ProcessHelper
+    {
+        public static Process RunAsAdmin(string path)
+        {
+            Process proc = new Process();
+            try
+            {
+                proc.StartInfo.FileName = path;
+                proc.StartInfo.Verb = "runas";
+                proc.StartInfo.UseShellExecute = true;
+                proc.Start();
+            }
+            catch (Win32Exception ex)
+            {
+                throw ex;
+            }
+            return proc;
+        }
+    }
+}

+ 12 - 21
PixiEditor/Models/Tools/Tool.cs

@@ -7,22 +7,17 @@ namespace PixiEditor.Models.Tools
 {
     public abstract class Tool : NotifyableObject
     {
-        private bool isActive;
-
         public abstract ToolType ToolType { get; }
-
         public string ImagePath => $"/Images/{ToolType}Image.png";
-
         public bool HideHighlight { get; set; } = false;
-
         public string Tooltip { get; set; }
 
         public bool IsActive
         {
-            get => isActive;
+            get => _isActive;
             set
             {
-                isActive = value;
+                _isActive = value;
                 RaisePropertyChanged("IsActive");
             }
         }
@@ -30,23 +25,19 @@ namespace PixiEditor.Models.Tools
         public Cursor Cursor { get; set; } = Cursors.Arrow;
 
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
-
+
+        private bool _isActive;
         public bool CanStartOutsideCanvas { get; set; } = false;
 
-        public virtual void OnMouseDown(MouseEventArgs e)
-        {
-        }
+        public virtual void OnMouseDown(MouseEventArgs e) { }
+        public virtual void OnMouseUp(MouseEventArgs e) { }
 
-        public virtual void OnMouseUp(MouseEventArgs e)
-        {
-        }
+        public virtual void OnRecordingLeftMouseDown(MouseEventArgs e) { }
 
-        public virtual void OnMouseMove(MouseEventArgs e)
-        {
-        }
+        public virtual void OnStoppedRecordingMouseUp(MouseEventArgs e) { }
 
-        public virtual void AfterAddedUndo()
-        {
-        }
+        public virtual void OnMouseMove(MouseEventArgs e) { }
+
+        public virtual void AfterAddedUndo() { }
     }
-}
+}

+ 1 - 0
PixiEditor/Models/Tools/ToolType.cs

@@ -3,6 +3,7 @@
     public enum ToolType
     {
         None,
+        MoveViewport,
         Move,
         Pen,
         Select,

+ 81 - 95
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -1,95 +1,81 @@
-using System;
-using System.Collections.Generic;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Models.Colors;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class BrightnessTool : BitmapOperationTool
-    {
-        private const float CorrectionFactor = 5f; // Initial correction factor
-
-        private readonly List<Coordinates> pixelsVisited = new List<Coordinates>();
-
-        public BrightnessTool()
-        {
-            Tooltip = "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
-            Toolbar = new BrightnessToolToolbar(CorrectionFactor);
-        }
-
-        public override ToolType ToolType => ToolType.Brightness;
-
-        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
-
-        public override void OnMouseDown(MouseEventArgs e)
-        {
-            pixelsVisited.Clear();
-        }
-
-        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
-        {
-            int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-            float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
-            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out BrightnessMode mode);
-            Mode = mode;
-
-            LayerChange[] layersChanges = new LayerChange[1];
-            if (Keyboard.IsKeyDown(Key.LeftCtrl))
-            {
-                layersChanges[0] = new LayerChange(
-                    ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor),
-                    layer);
-            }
-            else
-            {
-                layersChanges[0] = new LayerChange(
-                    ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor),
-                    layer);
-            }
-
-            return layersChanges;
-        }
-
-        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
-        {
-            DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
-            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(
-                centeredCoords.Coords1.X,
-                centeredCoords.Coords1.Y,
-                centeredCoords.Coords2.X,
-                centeredCoords.Coords2.Y);
-            BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
-
-            for (int i = 0; i < rectangleCoordinates.Length; i++)
-            {
-                if (Mode == BrightnessMode.Default)
-                {
-                    if (pixelsVisited.Contains(rectangleCoordinates[i]))
-                    {
-                        continue;
-                    }
-
-                    pixelsVisited.Add(rectangleCoordinates[i]);
-                }
-
-                Color pixel = layer.GetPixelWithOffset(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
-                Color newColor = ExColor.ChangeColorBrightness(
-                    Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
-                    correctionFactor);
-                changes.ChangedPixels.Add(
-                    new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
-                    newColor);
-            }
-
-            return changes;
-        }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using PixiEditor.Models.Colors;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class BrightnessTool : BitmapOperationTool
+    {
+        private const float CorrectionFactor = 5f; //Initial correction factor
+
+        public override ToolType ToolType => ToolType.Brightness;
+        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
+
+        private List<Coordinates> _pixelsVisited = new List<Coordinates>();
+
+        public BrightnessTool()
+        {
+            Tooltip = "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
+            Toolbar = new BrightnessToolToolbar(CorrectionFactor);
+        }
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+            _pixelsVisited.Clear();
+        }
+
+        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
+        {
+            int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+            float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
+            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out BrightnessMode mode);
+            Mode = mode;
+
+            LayerChange[] layersChanges = new LayerChange[1];
+            if (Keyboard.IsKeyDown(Key.LeftCtrl))
+                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor),
+                    layer);
+            else
+                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor),
+                    layer);
+            return layersChanges;
+        }
+
+        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize,
+            float correctionFactor)
+        {
+            DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
+            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(centeredCoords.Coords1.X,
+                centeredCoords.Coords1.Y,
+                centeredCoords.Coords2.X, centeredCoords.Coords2.Y);
+            BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
+
+            for (int i = 0; i < rectangleCoordinates.Length; i++)
+            {
+                if (Mode == BrightnessMode.Default)
+                {
+                    if(_pixelsVisited.Contains(rectangleCoordinates[i]))
+                        continue;
+                    _pixelsVisited.Add(rectangleCoordinates[i]);
+                }
+
+                Color pixel = layer.GetPixelWithOffset(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
+                Color newColor = ExColor.ChangeColorBrightness(Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
+                    correctionFactor);
+                changes.ChangedPixels.Add(new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
+                    newColor);
+            }
+
+            return changes;
+        }
+    }
+}

+ 23 - 36
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using System.Windows.Media;
-using System.Windows.Media.Imaging;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
@@ -10,66 +9,54 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class FloodFill : BitmapOperationTool
     {
+        public override ToolType ToolType => ToolType.Bucket;
+
         public FloodFill()
         {
             Tooltip = "Fills area with color (G)";
         }
 
-        public override ToolType ToolType => ToolType.Bucket;
-
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             return Only(ForestFire(layer, coordinates[0], color), layer);
         }
 
-        public BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
+        private BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
         {
             List<Coordinates> changedCoords = new List<Coordinates>();
 
-            Layer clone = layer.Clone();
             int width = ViewModelMain.Current.BitmapManager.ActiveDocument.Width;
             int height = ViewModelMain.Current.BitmapManager.ActiveDocument.Height;
 
+            var visited = new bool[width, height];
+
             Color colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
 
-            Stack<Coordinates> stack = new Stack<Coordinates>();
+            var stack = new Stack<Coordinates>();
             stack.Push(new Coordinates(startingCoords.X, startingCoords.Y));
 
-            using (clone.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadWrite))
+            while (stack.Count > 0)
             {
-                while (stack.Count > 0)
-                {
-                    Coordinates cords = stack.Pop();
-                    Coordinates relativeCords = clone.GetRelativePosition(cords);
+                var cords = stack.Pop();
+                var relativeCords = layer.GetRelativePosition(cords);
 
-                    if (cords.X < 0 || cords.X > width - 1)
-                    {
-                        continue;
-                    }
-
-                    if (cords.Y < 0 || cords.Y > height - 1)
-                    {
-                        continue;
-                    }
-
-                    if (clone.GetPixel(relativeCords.X, relativeCords.Y) == newColor)
-                    {
-                        continue;
-                    }
-
-                    if (clone.GetPixel(relativeCords.X, relativeCords.Y) == colorToReplace)
-                    {
-                        changedCoords.Add(new Coordinates(cords.X, cords.Y));
-                        clone.SetPixel(new Coordinates(cords.X, cords.Y), newColor);
-                        stack.Push(new Coordinates(cords.X, cords.Y - 1));
-                        stack.Push(new Coordinates(cords.X + 1, cords.Y));
-                        stack.Push(new Coordinates(cords.X, cords.Y + 1));
-                        stack.Push(new Coordinates(cords.X - 1, cords.Y));
-                    }
+                if (cords.X < 0 || cords.X > width - 1) continue;
+                if (cords.Y < 0 || cords.Y > height - 1) continue;
+                if (visited[cords.X, cords.Y]) continue;
+                if (layer.GetPixel(relativeCords.X, relativeCords.Y) == newColor) continue;
+
+                if (layer.GetPixel(relativeCords.X, relativeCords.Y) == colorToReplace)
+                {
+                    changedCoords.Add(new Coordinates(cords.X, cords.Y));
+                    visited[cords.X, cords.Y] = true;
+                    stack.Push(new Coordinates(cords.X, cords.Y - 1));                    
+                    stack.Push(new Coordinates(cords.X, cords.Y + 1));                    
+                    stack.Push(new Coordinates(cords.X - 1, cords.Y));
+                    stack.Push(new Coordinates(cords.X + 1, cords.Y));
                 }
             }
 
             return BitmapPixelChanges.FromSingleColoredArray(changedCoords, newColor);
         }
     }
-}
+}

+ 192 - 212
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -1,212 +1,192 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
-using Transform = PixiEditor.Models.ImageManipulation.Transform;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class MoveTool : BitmapOperationTool
-    {
-        private Layer[] affectedLayers;
-        private Dictionary<Layer, bool> clearedPixels = new Dictionary<Layer, bool>();
-        private Coordinates[] currentSelection;
-        private Coordinates lastMouseMove;
-        private Coordinates lastStartMousePos;
-        private Dictionary<Layer, Thickness> startingOffsets;
-        private Dictionary<Layer, Color[]> startPixelColors;
-        private Coordinates[] startSelection;
-        private bool updateViewModelSelection = true;
-
-        public MoveTool()
-        {
-            Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers";
-            Cursor = Cursors.Arrow;
-            HideHighlight = true;
-            RequiresPreviewLayer = true;
-            UseDefaultUndoMethod = true;
-        }
-
-        public bool MoveAll { get; set; } = false;
-
-        public override ToolType ToolType => ToolType.Move;
-
-        public override void AfterAddedUndo()
-        {
-            if (currentSelection != null && currentSelection.Length != 0)
-            {
-                // Inject to default undo system change custom changes made by this tool
-                foreach (KeyValuePair<Layer, Color[]> item in startPixelColors)
-                {
-                    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
-                    Change changes = UndoManager.UndoStack.Peek();
-                    int layerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(item.Key);
-
-                    ((LayerChange[])changes.OldValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
-                        .AddRangeOverride(beforeMovePixels.ChangedPixels);
-
-                    ((LayerChange[])changes.NewValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
-                        .AddRangeNewOnly(BitmapPixelChanges
-                            .FromSingleColoredArray(startSelection, System.Windows.Media.Colors.Transparent)
-                            .ChangedPixels);
-                }
-            }
-        }
-
-        public override void OnMouseUp(MouseEventArgs e) // This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
-        {
-            // is because it doesn't fire if no pixel changes were made.
-            if (currentSelection != null && currentSelection.Length == 0)
-            {
-                UndoManager.AddUndoChange(new Change(
-                    ApplyOffsets,
-                    new object[] { startingOffsets },
-                    ApplyOffsets,
-                    new object[] { GetOffsets(affectedLayers) },
-                    "Move layers"));
-            }
-        }
-
-        public override LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color)
-        {
-            Coordinates start = mouseMove[^1];
-
-            // I am aware that this could be moved to OnMouseDown, but it is executed before Use, so I didn't want to complicate for now
-            if (lastStartMousePos != start)
-            {
-                ResetSelectionValues(start);
-
-                // Move offset if no selection
-                if (ViewModelMain.Current.ActiveSelection != null && ViewModelMain.Current.ActiveSelection.SelectedPoints.Count > 0)
-                {
-                    currentSelection = ViewModelMain.Current.ActiveSelection.SelectedPoints.ToArray();
-                }
-                else
-                {
-                    currentSelection = Array.Empty<Coordinates>();
-                }
-
-                if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
-                {
-                    affectedLayers = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Where(x => x.IsVisible)
-                        .ToArray();
-                }
-                else
-                {
-                    affectedLayers = new[] { layer };
-                }
-
-                startSelection = currentSelection;
-                startPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, startSelection);
-                startingOffsets = GetOffsets(affectedLayers);
-            }
-
-            LayerChange[] result = new LayerChange[affectedLayers.Length];
-            Coordinates end = mouseMove[0];
-            for (int i = 0; i < affectedLayers.Length; i++)
-            {
-                if (currentSelection.Length > 0)
-                {
-                    BitmapPixelChanges changes = MoveSelection(affectedLayers[i], mouseMove);
-                    changes = RemoveTransparentPixels(changes);
-
-                    result[i] = new LayerChange(changes, affectedLayers[i]);
-                }
-                else
-                {
-                    Coordinates vector = Transform.GetTranslation(lastMouseMove, end);
-                    affectedLayers[i].Offset = new Thickness(affectedLayers[i].OffsetX + vector.X, affectedLayers[i].OffsetY + vector.Y, 0, 0);
-                    result[i] = new LayerChange(BitmapPixelChanges.Empty, affectedLayers[i]);
-                }
-            }
-
-            lastMouseMove = end;
-
-            return result;
-        }
-
-        public BitmapPixelChanges MoveSelection(Layer layer, Coordinates[] mouseMove)
-        {
-            Coordinates end = mouseMove[0];
-
-            currentSelection = TranslateSelection(end, out Coordinates[] previousSelection);
-            if (updateViewModelSelection)
-            {
-                ViewModelMain.Current.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
-            }
-
-            ClearSelectedPixels(layer, previousSelection);
-
-            lastMouseMove = end;
-            return BitmapPixelChanges.FromArrays(currentSelection, startPixelColors[layer]);
-        }
-
-        private void ApplyOffsets(object[] parameters)
-        {
-            Dictionary<Layer, Thickness> offsets = (Dictionary<Layer, Thickness>)parameters[0];
-            foreach (KeyValuePair<Layer, Thickness> offset in offsets)
-            {
-                offset.Key.Offset = offset.Value;
-            }
-        }
-
-        private Dictionary<Layer, Thickness> GetOffsets(Layer[] layers)
-        {
-            Dictionary<Layer, Thickness> dict = new Dictionary<Layer, Thickness>();
-            for (int i = 0; i < layers.Length; i++)
-            {
-                dict.Add(layers[i], layers[i].Offset);
-            }
-
-            return dict;
-        }
-
-        private BitmapPixelChanges RemoveTransparentPixels(BitmapPixelChanges pixels)
-        {
-            foreach (KeyValuePair<Coordinates, Color> item in pixels.ChangedPixels.Where(x => x.Value.A == 0).ToList())
-            {
-                pixels.ChangedPixels.Remove(item.Key);
-            }
-
-            return pixels;
-        }
-
-        private void ResetSelectionValues(Coordinates start)
-        {
-            lastStartMousePos = start;
-            lastMouseMove = start;
-            clearedPixels = new Dictionary<Layer, bool>();
-            updateViewModelSelection = true;
-            startPixelColors = null;
-            startSelection = null;
-        }
-
-        private Coordinates[] TranslateSelection(Coordinates end, out Coordinates[] previousSelection)
-        {
-            Coordinates translation = Transform.GetTranslation(lastMouseMove, end);
-            previousSelection = currentSelection.ToArray();
-            return Transform.Translate(previousSelection, translation);
-        }
-
-        private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
-        {
-            if (!clearedPixels.ContainsKey(layer) || clearedPixels[layer] == false)
-            {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.First(x => x == layer)
-                    .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, System.Windows.Media.Colors.Transparent));
-
-                clearedPixels[layer] = true;
-            }
-        }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+using Transform = PixiEditor.Models.ImageManipulation.Transform;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class MoveTool : BitmapOperationTool
+    {
+        public bool MoveAll { get; set; } = false;
+
+        public override ToolType ToolType => ToolType.Move;
+        private Layer[] _affectedLayers;
+        private Dictionary<Layer, bool> _clearedPixels = new Dictionary<Layer, bool>();
+        private Coordinates[] _currentSelection;
+        private Coordinates _lastMouseMove;
+        private Coordinates _lastStartMousePos;
+        private Dictionary<Layer, Color[]> _startPixelColors;
+        private Dictionary<Layer, Thickness> _startingOffsets;
+        private Coordinates[] _startSelection;
+        private bool _updateViewModelSelection = true;
+
+        public MoveTool()
+        {
+            Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers";
+            Cursor = Cursors.Arrow;
+            HideHighlight = true;
+            RequiresPreviewLayer = true;
+            UseDefaultUndoMethod = true;
+        }
+
+        public override void AfterAddedUndo()
+        {
+            if (_currentSelection != null && _currentSelection.Length != 0)
+            {
+                //Inject to default undo system change custom changes made by this tool
+                foreach (var item in _startPixelColors)
+                {
+                    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(_startSelection, item.Value);
+                    Change changes = UndoManager.UndoStack.Peek();
+                    int layerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(item.Key);
+
+                    ((LayerChange[]) changes.OldValue).First(x=> x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
+                        .AddRangeOverride(beforeMovePixels.ChangedPixels);
+
+                    ((LayerChange[]) changes.NewValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
+                        .AddRangeNewOnly(BitmapPixelChanges
+                            .FromSingleColoredArray(_startSelection, System.Windows.Media.Colors.Transparent)
+                            .ChangedPixels);
+                }
+            }
+        }
+
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e) //This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
+        {   //is because it doesn't fire if no pixel changes were made.
+            if (_currentSelection != null && _currentSelection.Length == 0)
+            {
+                UndoManager.AddUndoChange(new Change(ApplyOffsets, new object[]{_startingOffsets}, 
+                    ApplyOffsets, new object[] { GetOffsets(_affectedLayers)}, "Move layers"));
+            }
+        }
+
+        private void ApplyOffsets(object[] parameters)
+        {
+            Dictionary<Layer, Thickness> offsets = (Dictionary<Layer, Thickness>)parameters[0];
+            foreach (var offset in offsets)
+            {
+                offset.Key.Offset = offset.Value;
+            }
+        }
+
+        public override LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color)
+        {
+            Coordinates start = mouseMove[^1];
+            if (_lastStartMousePos != start) //I am aware that this could be moved to OnMouseDown, but it is executed before Use, so I didn't want to complicate for now
+            {
+                ResetSelectionValues(start);
+                if (ViewModelMain.Current.ActiveSelection != null && ViewModelMain.Current.ActiveSelection.SelectedPoints.Count > 0) //Move offset if no selection
+                {
+                    _currentSelection = ViewModelMain.Current.ActiveSelection.SelectedPoints.ToArray();
+                }
+                else
+                {
+                    _currentSelection = Array.Empty<Coordinates>();
+                }
+
+                if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
+                    _affectedLayers = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Where(x => x.IsVisible)
+                        .ToArray();
+                else
+                    _affectedLayers = new[] {layer};
+
+                _startSelection = _currentSelection;
+                _startPixelColors = BitmapUtils.GetPixelsForSelection(_affectedLayers, _startSelection);
+                _startingOffsets = GetOffsets(_affectedLayers);
+            }
+
+            LayerChange[] result = new LayerChange[_affectedLayers.Length];
+            var end = mouseMove[0];
+            for (int i = 0; i < _affectedLayers.Length; i++)
+            {
+                if (_currentSelection.Length > 0)
+                {
+                    var changes = MoveSelection(_affectedLayers[i], mouseMove);
+                    changes = RemoveTransparentPixels(changes);
+
+                    result[i] = new LayerChange(changes, _affectedLayers[i]);
+                }
+                else
+                {
+                    var vector = Transform.GetTranslation(_lastMouseMove, end);
+                    _affectedLayers[i].Offset = new Thickness(_affectedLayers[i].OffsetX + vector.X, _affectedLayers[i].OffsetY + vector.Y, 0, 0);
+                    result[i] = new LayerChange(BitmapPixelChanges.Empty, _affectedLayers[i]);
+                }
+            }
+            _lastMouseMove = end;
+
+            return result;
+        }
+
+        private Dictionary<Layer, Thickness> GetOffsets(Layer[] layers)
+        {
+            Dictionary<Layer, Thickness> dict = new Dictionary<Layer, Thickness>();
+            for (int i = 0; i < layers.Length; i++)
+            {
+                dict.Add(layers[i], layers[i].Offset);
+            }
+
+            return dict;
+        }
+
+        private BitmapPixelChanges RemoveTransparentPixels(BitmapPixelChanges pixels)
+        {
+            foreach (var item in pixels.ChangedPixels.Where(x => x.Value.A == 0).ToList())
+                pixels.ChangedPixels.Remove(item.Key);
+            return pixels;
+        }
+
+        public BitmapPixelChanges MoveSelection(Layer layer, Coordinates[] mouseMove)
+        {
+            Coordinates end = mouseMove[0];
+
+            _currentSelection = TranslateSelection(end, out Coordinates[] previousSelection);
+            if (_updateViewModelSelection)
+                ViewModelMain.Current.ActiveSelection.SetSelection(_currentSelection, SelectionType.New);
+            ClearSelectedPixels(layer, previousSelection);
+
+
+            _lastMouseMove = end;
+            return BitmapPixelChanges.FromArrays(_currentSelection, _startPixelColors[layer]);
+        }
+
+        private void ResetSelectionValues(Coordinates start)
+        {
+            _lastStartMousePos = start;
+            _lastMouseMove = start;
+            _clearedPixels = new Dictionary<Layer, bool>();
+            _updateViewModelSelection = true;
+            _startPixelColors = null;
+            _startSelection = null;
+        }
+
+        private Coordinates[] TranslateSelection(Coordinates end, out Coordinates[] previousSelection)
+        {
+            Coordinates translation = Transform.GetTranslation(_lastMouseMove, end);
+            previousSelection = _currentSelection.ToArray();
+            return Transform.Translate(previousSelection, translation);
+        }
+
+        private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
+        {
+            if (!_clearedPixels.ContainsKey(layer) || _clearedPixels[layer] == false)
+            {
+                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.First(x => x == layer)
+                    .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, System.Windows.Media.Colors.Transparent));
+
+                _clearedPixels[layer] = true;
+            }
+        }
+    }
+}

+ 50 - 0
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -0,0 +1,50 @@
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+using System.Drawing;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class MoveViewportTool : ReadonlyTool
+    {
+        public override ToolType ToolType => ToolType.MoveViewport;
+        private Point _clickPoint;
+
+        public MoveViewportTool()
+        {
+            HideHighlight = true;
+            Cursor = Cursors.SizeAll;
+            Tooltip = "Move viewport. (H)";
+        }
+
+        public override void OnMouseDown(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
+            {
+                _clickPoint = MousePositionConverter.GetCursorPosition();
+            }
+        }
+
+        public override void OnMouseMove(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
+            {
+                var point = MousePositionConverter.GetCursorPosition();
+                ViewModelMain.Current.ViewportPosition = new System.Windows.Point(point.X - _clickPoint.X, 
+                    point.Y - _clickPoint.Y);
+            }
+        }
+
+        public override void OnMouseUp(MouseEventArgs e)
+        {
+            if (e.MiddleButton == MouseButtonState.Pressed)
+            {
+                ViewModelMain.Current.SetActiveTool(ViewModelMain.Current.LastActionTool);
+            }
+        }
+
+        public override void Use(Coordinates[] pixels)
+        {
+        }
+    }
+}

+ 88 - 91
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -1,91 +1,88 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Controls;
-using System.Windows.Input;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class SelectTool : ReadonlyTool
-    {
-        private Selection oldSelection;
-
-        public SelectTool()
-        {
-            Tooltip = "Selects area. (M)";
-            Toolbar = new SelectToolToolbar();
-        }
-
-        public SelectionType SelectionType { get; set; } = SelectionType.Add;
-
-        public override ToolType ToolType => ToolType.Select;
-
-        public override void OnMouseDown(MouseEventArgs e)
-        {
-            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out SelectionType selectionType);
-            SelectionType = selectionType;
-
-            oldSelection = null;
-            if (ViewModelMain.Current.ActiveSelection != null &&
-                ViewModelMain.Current.ActiveSelection.SelectedPoints != null)
-            {
-                oldSelection = ViewModelMain.Current.ActiveSelection;
-            }
-        }
-
-        public override void OnMouseUp(MouseEventArgs e)
-        {
-            if (ViewModelMain.Current.ActiveSelection.SelectedPoints.Count() <= 1)
-            {
-                // If we have not selected multiple points, clear the selection
-                ViewModelMain.Current.ActiveSelection.Clear();
-            }
-
-            UndoManager.AddUndoChange(new Change("ActiveSelection", oldSelection, ViewModelMain.Current.ActiveSelection, "Select pixels"));
-        }
-
-        public override void Use(Coordinates[] pixels)
-        {
-            Select(pixels);
-        }
-
-        public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
-        {
-            RectangleTool rectangleTool = new RectangleTool();
-            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
-            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
-            return selection;
-        }
-
-        /// <summary>
-        ///     Gets coordinates of every pixel in root layer.
-        /// </summary>
-        /// <returns>Coordinates array of pixels.</returns>
-        public IEnumerable<Coordinates> GetAllSelection()
-        {
-            return GetAllSelection(ViewModelMain.Current.BitmapManager.ActiveDocument);
-        }
-
-        /// <summary>
-        ///     Gets coordinates of every pixel in chosen document.
-        /// </summary>
-        /// <returns>Coordinates array of pixels.</returns>
-        public IEnumerable<Coordinates> GetAllSelection(Document document)
-        {
-            return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
-        }
-
-        private void Select(Coordinates[] pixels)
-        {
-            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
-            ViewModelMain.Current.ActiveSelection.SetSelection(selection, SelectionType);
-        }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Input;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class SelectTool : ReadonlyTool
+    {
+        public override ToolType ToolType => ToolType.Select;
+        private Selection _oldSelection;
+        public SelectionType SelectionType = SelectionType.Add;
+
+        public SelectTool()
+        {
+            Tooltip = "Selects area. (M)";
+            Toolbar = new SelectToolToolbar();
+        }
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out SelectionType);
+
+            _oldSelection = null;
+            if (ViewModelMain.Current.ActiveSelection != null &&
+                ViewModelMain.Current.ActiveSelection.SelectedPoints != null)
+                _oldSelection = ViewModelMain.Current.ActiveSelection;
+        }
+
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        {
+            if (ViewModelMain.Current.ActiveSelection.SelectedPoints.Count() <= 1)
+            {
+                // If we have not selected multiple points, clear the selection
+                ViewModelMain.Current.ActiveSelection.Clear();
+            }
+
+            UndoManager.AddUndoChange(new Change("ActiveSelection", _oldSelection,
+                ViewModelMain.Current.ActiveSelection, "Select pixels"));
+        }
+
+        public override void Use(Coordinates[] pixels)
+        {
+            Select(pixels);
+        }
+
+        private void Select(Coordinates[] pixels)
+        {
+            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
+            ViewModelMain.Current.ActiveSelection.SetSelection(selection, SelectionType);
+        }
+
+        public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
+        {
+            RectangleTool rectangleTool = new RectangleTool();
+            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
+            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
+            return selection;
+        }
+
+        /// <summary>
+        ///     Gets coordinates of every pixel in root layer
+        /// </summary>
+        /// <returns>Coordinates array of pixels</returns>
+        public IEnumerable<Coordinates> GetAllSelection()
+        {
+            return GetAllSelection(ViewModelMain.Current.BitmapManager.ActiveDocument);
+        }
+
+        /// <summary>
+        ///     Gets coordinates of every pixel in chosen document
+        /// </summary>
+        /// <param name="document"></param>
+        /// <returns>Coordinates array of pixels</returns>
+        public IEnumerable<Coordinates> GetAllSelection(Document document)
+        {
+            return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
+        }
+    }
+}

+ 71 - 69
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -1,69 +1,71 @@
-using System;
-using System.Windows;
-using System.Windows.Input;
-using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class ZoomTool : ReadonlyTool
-    {
-        public const float ZoomSensitivityMultiplier = 30f;
-        private readonly double pixelsPerZoomMultiplier;
-        private readonly double workAreaWidth = SystemParameters.WorkArea.Width;
-        private double startingX;
-
-        public ZoomTool()
-        {
-            HideHighlight = true;
-            CanStartOutsideCanvas = true;
-            Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
-            pixelsPerZoomMultiplier = workAreaWidth / ZoomSensitivityMultiplier;
-        }
-
-        public override ToolType ToolType => ToolType.Zoom;
-
-        public override void OnMouseDown(MouseEventArgs e)
-        {
-            startingX = MousePositionConverter.GetCursorPosition().X;
-            ViewModelMain.Current.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
-        }
-
-        public override void OnMouseMove(MouseEventArgs e)
-        {
-            if (e.LeftButton == MouseButtonState.Pressed)
-            {
-                double xPos = MousePositionConverter.GetCursorPosition().X;
-
-                double rawPercentDifference = (xPos - startingX) / pixelsPerZoomMultiplier; // negative - zoom out, positive - zoom in, linear
-                double finalPercentDifference = Math.Pow(2, rawPercentDifference) * 100.0; // less than 100 - zoom out, greater than 100 - zoom in
-                Zoom(finalPercentDifference);
-            }
-        }
-
-        public override void OnMouseUp(MouseEventArgs e)
-        {
-            if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released &&
-                startingX == MousePositionConverter.GetCursorPosition().X)
-            {
-                if (Keyboard.Modifiers.HasFlag(ModifierKeys.Alt))
-                {
-                    Zoom(85);
-                }
-                else
-                {
-                    Zoom(115);
-                }
-            }
-        }
-
-        public void Zoom(double percentage)
-        {
-            ViewModelMain.Current.ZoomPercentage = percentage;
-        }
-
-        public override void Use(Coordinates[] pixels)
-        {
-        }
-    }
-}
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class ZoomTool : ReadonlyTool
+    {
+        public const float ZoomSensitivityMultiplier = 30f;
+        public override ToolType ToolType => ToolType.Zoom;
+        private double _startingX;
+        private double _workAreaWidth = SystemParameters.WorkArea.Width;
+        private double _pixelsPerZoomMultiplier;
+
+        public ZoomTool()
+        {
+            HideHighlight = true;
+            CanStartOutsideCanvas = true;
+            Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
+            _pixelsPerZoomMultiplier = _workAreaWidth / ZoomSensitivityMultiplier;
+        }
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+            _startingX = MousePositionConverter.GetCursorPosition().X;
+            ViewModelMain.Current.ZoomPercentage = 100; //This resest the value, so callback in MainDrawingPanel can fire again later
+        }
+
+        public override void OnMouseMove(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed)
+            {
+                double xPos = MousePositionConverter.GetCursorPosition().X;
+
+                double rawPercentDifference = (xPos - _startingX) / _pixelsPerZoomMultiplier; //negative - zoom out, positive - zoom in, linear
+                double finalPercentDifference = Math.Pow(2, rawPercentDifference) * 100.0; //less than 100 - zoom out, greater than 100 - zoom in
+                Zoom(finalPercentDifference);
+            }
+        }
+
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released && 
+                _startingX == MousePositionConverter.GetCursorPosition().X)
+            {
+                if (Keyboard.Modifiers.HasFlag(ModifierKeys.Alt))
+                {
+                    Zoom(85);
+                }
+                else
+                {
+                    Zoom(115);
+                }
+            }
+        }
+
+        public void Zoom(double percentage)
+        {
+            ViewModelMain.Current.ZoomPercentage = percentage;
+        }
+
+        public override void Use(Coordinates[] pixels)
+        {
+        }
+    }
+}

+ 4 - 2
PixiEditor/PixiEditor.csproj

@@ -29,6 +29,7 @@
   <ItemGroup>
     <None Remove="Images\AnchorDot.png" />
     <None Remove="Images\MoveImage.png" />
+    <None Remove="Images\MoveViewportImage.png" />
     <None Remove="Images\PixiEditorLogo.png" />
     <None Remove="Images\SelectImage.png" />
     <None Remove="Images\ZoomImage.png" />
@@ -42,13 +43,13 @@
     </None>
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Dirkster.AvalonDock.Themes.VS2013" Version="4.30.0" />
+    <PackageReference Include="Dirkster.AvalonDock.Themes.VS2013" Version="4.40.0" />
     <PackageReference Include="Expression.Blend.Sdk">
       <Version>1.0.2</Version>
     </PackageReference>
     <PackageReference Include="Extended.Wpf.Toolkit" Version="3.8.2" />
     <PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
-    <PackageReference Include="PixiEditor.ColorPicker" Version="1.0.0" />
+    <PackageReference Include="PixiEditor.ColorPicker" Version="1.0.1" />
     <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
     <PackageReference Include="WriteableBitmapEx">
       <Version>1.6.7</Version>
@@ -62,6 +63,7 @@
     <Resource Include="Images\BrightnessImage.png" />
     <Resource Include="Images\LineImage.png" />
     <Resource Include="Images\MoveImage.png" />
+    <Resource Include="Images\MoveViewportImage.png" />
     <Resource Include="Images\PenImage.png" />
     <Resource Include="Images\ColorPickerImage.png" />
     <Resource Include="Images\PixiEditorLogo.png" />

+ 22 - 20
PixiEditor/Properties/AssemblyInfo.cs

@@ -19,25 +19,26 @@ using System.Windows;
 // COM, set the ComVisible attribute to true on that type.
 [assembly: ComVisible(false)]
 
-// In order to begin building localizable applications, set
-// <UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
-// inside a <PropertyGroup>.  For example, if you are using US english
-// in your source files, set the <UICulture> to en-US.  Then uncomment
-// the NeutralResourceLanguage attribute below.  Update the "en-US" in
-// the line below to match the UICulture setting in the project file.
-
-// [assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+//In order to begin building localizable applications, set
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>.  For example, if you are using US english
+//in your source files, set the <UICulture> to en-US.  Then uncomment
+//the NeutralResourceLanguage attribute below.  Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
 [assembly: ThemeInfo(
-    ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located
-
-    // (used if a resource is not found in the page,
-    // or application resource dictionaries)
-    ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located
-
-// (used if a resource is not found in the page,
-// app, or any theme specific resource dictionaries)
-]
-
+    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+    //(used if a resource is not found in the page,
+    // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+    //(used if a resource is not found in the page,
+    // app, or any theme specific resource dictionaries)
+)]
+
+
 // Version information for an assembly consists of the following four values:
 //
 //      Major Version
@@ -48,5 +49,6 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.3.0")]
-[assembly: AssemblyFileVersion("0.1.3.0")]
+
+[assembly: AssemblyVersion("0.1.3.2")]
+[assembly: AssemblyFileVersion("0.1.3.2")]

File diff suppressed because it is too large
+ 241 - 511
PixiEditor/ViewModels/ViewModelMain.cs


+ 2 - 1
PixiEditor/Views/MainDrawingPanel.xaml

@@ -8,7 +8,8 @@
              mc:Ignorable="d" PreviewMouseDown="MainDrawingPanel_MouseDown" PreviewMouseUp="MainDrawingPanel_PreviewMouseUp"
              d:DesignHeight="450" d:DesignWidth="800" x:Name="mainDrawingPanel" PreviewMouseWheel="Zoombox_MouseWheel">
     <xctk:Zoombox PreviewMouseDown="Zoombox_PreviewMouseDown" Cursor="{Binding Cursor}" Name="Zoombox" KeepContentInBounds="True"
-                  Loaded="Zoombox_Loaded" IsAnimated="False" CurrentViewChanged="Zoombox_CurrentViewChanged" DragModifiers="Shift" ZoomModifiers="None">
+                  Loaded="Zoombox_Loaded" IsAnimated="False" MouseDown="Zoombox_MouseDown"
+                  CurrentViewChanged="Zoombox_CurrentViewChanged" DragModifiers="Blocked" ZoomModifiers="None">
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="MouseMove">
                 <i:InvokeCommandAction Command="{Binding MouseMoveCommand, ElementName=mainDrawingPanel, Mode=OneWay}" />

+ 251 - 187
PixiEditor/Views/MainDrawingPanel.xaml.cs

@@ -1,187 +1,251 @@
-using System;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using PixiEditor.Models.Tools.Tools;
-using PixiEditor.ViewModels;
-using Xceed.Wpf.Toolkit.Core.Input;
-using Xceed.Wpf.Toolkit.Zoombox;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for MainDrawingPanel.xaml.
-    /// </summary>
-    public partial class MainDrawingPanel : UserControl
-    {
-        // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty CenterProperty =
-            DependencyProperty.Register("Center", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(true, OnCenterChanged));
-
-        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MouseXProperty =
-            DependencyProperty.Register("MouseX", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
-
-        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MouseYProperty =
-            DependencyProperty.Register("MouseY", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
-
-        // Using a DependencyProperty as the backing store for MouseMoveCommand.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MouseMoveCommandProperty =
-            DependencyProperty.Register("MouseMoveCommand", typeof(ICommand), typeof(MainDrawingPanel), new PropertyMetadata(null));
-
-        // Using a DependencyProperty as the backing store for CenterOnStart.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty CenterOnStartProperty =
-            DependencyProperty.Register("CenterOnStart", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(false));
-
-        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ItemProperty =
-            DependencyProperty.Register("Item", typeof(object), typeof(MainDrawingPanel), new PropertyMetadata(default(FrameworkElement)));
-
-        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty IsUsingZoomToolProperty =
-            DependencyProperty.Register("IsUsingZoomTool", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(false));
-
-        // Using a DependencyProperty as the backing store for ZoomPercentage.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ZoomPercentageProperty =
-            DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(0.0, ZoomPercentegeChanged));
-
-        public MainDrawingPanel()
-        {
-            InitializeComponent();
-            Zoombox.ZoomToSelectionModifiers = new KeyModifierCollection { KeyModifier.RightAlt };
-        }
-
-        public double ClickScale { get; set; }
-
-        public double ZoomPercentage
-        {
-            get => (double)GetValue(ZoomPercentageProperty);
-            set => SetValue(ZoomPercentageProperty, value);
-        }
-
-        public bool Center
-        {
-            get => (bool)GetValue(CenterProperty);
-            set => SetValue(CenterProperty, value);
-        }
-
-        public double MouseX
-        {
-            get => (double)GetValue(MouseXProperty);
-            set => SetValue(MouseXProperty, value);
-        }
-
-        public double MouseY
-        {
-            get => (double)GetValue(MouseYProperty);
-            set => SetValue(MouseYProperty, value);
-        }
-
-        public ICommand MouseMoveCommand
-        {
-            get => (ICommand)GetValue(MouseMoveCommandProperty);
-            set => SetValue(MouseMoveCommandProperty, value);
-        }
-
-        public bool CenterOnStart
-        {
-            get => (bool)GetValue(CenterOnStartProperty);
-            set => SetValue(CenterOnStartProperty, value);
-        }
-
-        public object Item
-        {
-            get => GetValue(ItemProperty);
-            set => SetValue(ItemProperty, value);
-        }
-
-        public bool IsUsingZoomTool
-        {
-            get => (bool)GetValue(IsUsingZoomToolProperty);
-            set => SetValue(IsUsingZoomToolProperty, value);
-        }
-
-        private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            MainDrawingPanel panel = (MainDrawingPanel)d;
-            panel.Zoombox.CenterContent();
-        }
-
-        private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            MainDrawingPanel panel = (MainDrawingPanel)d;
-            double percentage = (double)e.NewValue;
-            if (percentage == 100)
-            {
-                panel.SetClickValues();
-            }
-
-            panel.Zoombox.ZoomTo(panel.ClickScale * ((double)e.NewValue / 100.0));
-        }
-
-        private void Zoombox_CurrentViewChanged(object sender, ZoomboxViewChangedEventArgs e)
-        {
-            Zoombox.MinScale = 32 / ((FrameworkElement)Item).Width;
-            Zoombox.KeepContentInBounds = !(Zoombox.Scale > Zoombox.MinScale * 35.0);
-        }
-
-        private void Zoombox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
-        {
-            if (ZoomPercentage == 100)
-            {
-                SetClickValues();
-            }
-        }
-
-        private void SetClickValues()
-        {
-            if (!IsUsingZoomTool)
-            {
-                return;
-            }
-
-            ClickScale = Zoombox.Scale;
-            SetZoomOrigin();
-        }
-
-        private void SetZoomOrigin()
-        {
-            FrameworkElement item = (FrameworkElement)Item;
-            if (item == null)
-            {
-                return;
-            }
-
-            Point mousePos = Mouse.GetPosition(item);
-            Zoombox.ZoomOrigin = new Point(Math.Clamp(mousePos.X / item.Width, 0, 1), Math.Clamp(mousePos.Y / item.Height, 0, 1));
-        }
-
-        private void Zoombox_Loaded(object sender, RoutedEventArgs e)
-        {
-            if (CenterOnStart)
-            {
-                ((Zoombox)sender).CenterContent();
-            }
-
-            ClickScale = Zoombox.Scale;
-        }
-
-        private void Zoombox_MouseWheel(object sender, MouseWheelEventArgs e)
-        {
-            SetZoomOrigin();
-        }
-
-        private void MainDrawingPanel_MouseDown(object sender, MouseButtonEventArgs e)
-        {
-            IsUsingZoomTool = ViewModelMain.Current.BitmapManager.SelectedTool is ZoomTool;
-            Mouse.Capture((IInputElement)sender, CaptureMode.SubTree);
-            SetClickValues();
-        }
-
-        private void MainDrawingPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
-        {
-            ((IInputElement)sender).ReleaseMouseCapture();
-        }
-    }
-}
+using PixiEditor.ViewModels;
+using System;
+using System.Windows;
+using System.Windows.Automation;
+using System.Windows.Controls;
+using System.Windows.Input;
+using PixiEditor.Models.Tools.Tools;
+using Xceed.Wpf.Toolkit.Core.Input;
+using Xceed.Wpf.Toolkit.Zoombox;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for MainDrawingPanel.xaml
+    /// </summary>
+    public partial class MainDrawingPanel : UserControl
+    {
+        // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty CenterProperty =
+            DependencyProperty.Register("Center", typeof(bool), typeof(MainDrawingPanel),
+                new PropertyMetadata(true, OnCenterChanged));
+
+        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseXProperty =
+            DependencyProperty.Register("MouseX", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
+
+        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseYProperty =
+            DependencyProperty.Register("MouseY", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
+
+        // Using a DependencyProperty as the backing store for MouseMoveCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseMoveCommandProperty =
+            DependencyProperty.Register("MouseMoveCommand", typeof(ICommand), typeof(MainDrawingPanel),
+                new PropertyMetadata(null));
+
+        // Using a DependencyProperty as the backing store for CenterOnStart.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty CenterOnStartProperty =
+            DependencyProperty.Register("CenterOnStart", typeof(bool), typeof(MainDrawingPanel),
+                new PropertyMetadata(false));
+
+        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ItemProperty =
+            DependencyProperty.Register("Item", typeof(object), typeof(MainDrawingPanel), new PropertyMetadata(default(FrameworkElement)));
+
+        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty IsUsingZoomToolProperty =
+            DependencyProperty.Register("IsUsingZoomTool", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(false));
+
+
+        public double ZoomPercentage
+        {
+            get { return (double)GetValue(ZoomPercentageProperty); }
+            set { SetValue(ZoomPercentageProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ZoomPercentage.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ZoomPercentageProperty =
+            DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(0.0, ZoomPercentegeChanged));
+
+
+
+        public Point ViewportPosition
+        {
+            get { return (Point)GetValue(ViewportPositionProperty); }
+            set { SetValue(ViewportPositionProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ViewportPosition.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ViewportPositionProperty =
+            DependencyProperty.Register("ViewportPosition", typeof(Point),
+                typeof(MainDrawingPanel), new PropertyMetadata(default(Point), ViewportPosCallback));
+
+        public bool Center
+        {
+            get => (bool) GetValue(CenterProperty);
+            set => SetValue(CenterProperty, value);
+        }
+
+        public double MouseX
+        {
+            get => (double) GetValue(MouseXProperty);
+            set => SetValue(MouseXProperty, value);
+        }
+
+        public double MouseY
+        {
+            get => (double) GetValue(MouseYProperty);
+            set => SetValue(MouseYProperty, value);
+        }
+
+
+        public ICommand MouseMoveCommand
+        {
+            get => (ICommand) GetValue(MouseMoveCommandProperty);
+            set => SetValue(MouseMoveCommandProperty, value);
+        }
+
+
+        public bool CenterOnStart
+        {
+            get => (bool) GetValue(CenterOnStartProperty);
+            set => SetValue(CenterOnStartProperty, value);
+        }
+
+
+        public object Item
+        {
+            get => GetValue(ItemProperty);
+            set => SetValue(ItemProperty, value);
+        }
+
+        public bool IsUsingZoomTool
+        {
+            get => (bool) GetValue(IsUsingZoomToolProperty);
+            set => SetValue(IsUsingZoomToolProperty, value);
+        }
+
+        public ICommand MiddleMouseClickedCommand
+        {
+            get { return (ICommand)GetValue(MiddleMouseClickedCommandProperty); }
+            set { SetValue(MiddleMouseClickedCommandProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MiddleMouseClickedCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MiddleMouseClickedCommandProperty =
+            DependencyProperty.Register("MiddleMouseClickedCommand", typeof(ICommand), typeof(MainDrawingPanel), new PropertyMetadata(default(ICommand)));
+
+
+
+        public object MiddleMouseClickedCommandParameter
+        {
+            get { return (object)GetValue(MiddleMouseClickedCommandParameterProperty); }
+            set { SetValue(MiddleMouseClickedCommandParameterProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MiddleMouseClickedCommandParameter.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MiddleMouseClickedCommandParameterProperty =
+            DependencyProperty.Register("MiddleMouseClickedCommandParameter", typeof(object), typeof(MainDrawingPanel), 
+                new PropertyMetadata(default(object)));
+
+
+
+        private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            MainDrawingPanel panel = (MainDrawingPanel)d;
+            double percentage = (double)e.NewValue;
+            if(percentage == 100)
+            {
+                panel.SetClickValues();
+            }
+            panel.Zoombox.ZoomTo(panel.ClickScale * ((double)e.NewValue / 100.0));
+        }
+
+        public double ClickScale;
+        public Point ClickPosition;
+
+        public MainDrawingPanel()
+        {
+            InitializeComponent();
+            Zoombox.ZoomToSelectionModifiers = new KeyModifierCollection() { KeyModifier.RightAlt };
+        }
+
+        private static void ViewportPosCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            MainDrawingPanel panel = (MainDrawingPanel)d;
+            if (PresentationSource.FromVisual(panel.Zoombox) == null)
+            {
+                panel.Zoombox.Position = default;
+                return;
+            }           
+            TranslateZoombox(panel, (Point)e.NewValue);
+        }
+
+        private static void TranslateZoombox(MainDrawingPanel panel, Point vector)
+        {
+            var newPos = new Point(panel.ClickPosition.X + vector.X,
+                panel.ClickPosition.Y + vector.Y);
+            panel.Zoombox.Position = newPos;
+        }
+
+        private void Zoombox_CurrentViewChanged(object sender, ZoomboxViewChangedEventArgs e)
+        {
+            Zoombox.MinScale = 32 / ((FrameworkElement)Item).Width;
+            Zoombox.KeepContentInBounds = !(Zoombox.Scale > Zoombox.MinScale * 35.0);
+        }
+
+        private void Zoombox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (ZoomPercentage == 100)
+            {
+                SetClickValues();
+            }
+        }
+
+        private void SetClickValues()
+        {
+            if (!IsUsingZoomTool) return;
+            ClickScale = Zoombox.Scale;
+            SetZoomOrigin();
+        }
+
+        private void SetZoomOrigin()
+        {
+            var item = (FrameworkElement)Item;
+            if (item == null) return;
+            var mousePos = Mouse.GetPosition(item);
+            Zoombox.ZoomOrigin = new Point(Math.Clamp(mousePos.X / item.Width, 0, 1), Math.Clamp(mousePos.Y / item.Height, 0, 1));
+        }
+
+        private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            MainDrawingPanel panel = (MainDrawingPanel) d;
+            panel.Zoombox.CenterContent();
+        }
+
+
+        private void Zoombox_Loaded(object sender, RoutedEventArgs e)
+        {
+            if (CenterOnStart) ((Zoombox) sender).CenterContent();
+            ClickScale = Zoombox.Scale;
+        }
+
+        private void Zoombox_MouseWheel(object sender, MouseWheelEventArgs e)
+        {
+            SetZoomOrigin();
+        }
+
+        private void MainDrawingPanel_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            IsUsingZoomTool = ViewModelMain.Current.BitmapManager.SelectedTool is ZoomTool;
+            Mouse.Capture((IInputElement)sender, CaptureMode.SubTree);
+            ClickPosition = ((FrameworkElement)Item).TranslatePoint(new Point(0, 0), Zoombox);
+            SetClickValues();
+        }
+
+        private void MainDrawingPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
+        {
+            ((IInputElement)sender).ReleaseMouseCapture();
+        }
+
+        private void Zoombox_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.MiddleButton == MouseButtonState.Pressed && 
+                MiddleMouseClickedCommand.CanExecute(MiddleMouseClickedCommandParameter))
+            {
+                MiddleMouseClickedCommand.Execute(MiddleMouseClickedCommandParameter);
+            }
+        }
+    }
+}

+ 49 - 48
PixiEditor/Views/MainWindow.xaml

@@ -5,14 +5,15 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:vm="clr-namespace:PixiEditor.ViewModels"
         xmlns:vws="clr-namespace:PixiEditor.Views"
+        xmlns:tools="clr-namespace:PixiEditor.Models.Tools"
         xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:ui="clr-namespace:PixiEditor.Helpers.UI"
-        xmlns:cmd="http://www.galasoft.ch/mvvmlight"
+        xmlns:cmd="http://www.galasoft.ch/mvvmlight" 
         xmlns:avalondock="https://github.com/Dirkster99/AvalonDock"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
-        mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
+        mc:Ignorable="d" WindowStyle="None" Initialized="mainWindow_Initialized"
         Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
         WindowStartupLocation="CenterScreen" WindowState="Maximized" DataContext="{DynamicResource ViewModelMain}">
     <WindowChrome.WindowChrome>
@@ -25,7 +26,7 @@
         <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
         <converters:BoolToIntConverter x:Key="BoolToIntConverter" />
         <converters:FloatNormalizeConverter x:Key="FloatNormalizeConverter" />
-        <converters:DoubleToIntConverter x:Key="DoubleToIntConverter" />
+        <converters:DoubleToIntConverter x:Key="DoubleToIntConverter"/>
     </Window.Resources>
 
     <Window.CommandBindings>
@@ -44,7 +45,7 @@
             <cmd:EventToCommand Command="{Binding KeyDownCommand}" PassEventArgsToCommand="True" />
         </i:EventTrigger>
         <i:EventTrigger EventName="KeyUp">
-            <cmd:EventToCommand Command="{Binding KeyUpCommand}" PassEventArgsToCommand="True" />
+            <cmd:EventToCommand Command="{Binding KeyUpCommand}" PassEventArgsToCommand="True"/>
         </i:EventTrigger>
         <i:EventTrigger EventName="ContentRendered">
             <i:InvokeCommandAction Command="{Binding OnStartupCommand}" />
@@ -106,21 +107,21 @@
                     <MenuItem Header="Resize Canvas..." Command="{Binding OpenResizePopupCommand}"
                               CommandParameter="canvas" InputGestureText="Ctrl+Shift+C" />
                     <MenuItem Header="Clip Canvas" Command="{Binding ClipCanvasCommand}" />
-                    <Separator />
+                    <Separator/>
                     <MenuItem Header="Center Content" Command="{Binding CenterContentCommand}" />
                 </MenuItem>
                 <MenuItem Header="_Help">
                     <MenuItem Header="Documentation" Command="{Binding OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/flabbet/PixiEditor/wiki" />
+                              CommandParameter="https://github.com/flabbet/PixiEditor/wiki"/>
                     <MenuItem Header="Repository" Command="{Binding OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/flabbet/PixiEditor" />
+                              CommandParameter="https://github.com/flabbet/PixiEditor"/>
                     <MenuItem Header="Shortcuts" Command="{Binding OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/flabbet/PixiEditor/wiki/Shortcuts" />
-                    <Separator />
+                              CommandParameter="https://github.com/flabbet/PixiEditor/wiki/Shortcuts"/>
+                    <Separator/>
                     <MenuItem Header="License" Command="{Binding OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/flabbet/PixiEditor/blob/master/LICENSE" />
+                              CommandParameter="https://github.com/flabbet/PixiEditor/blob/master/LICENSE"/>
                     <MenuItem Header="Third Party Licenses" Command="{Binding OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/flabbet/PixiEditor/wiki/Third-party-licenses" />
+                              CommandParameter="https://github.com/flabbet/PixiEditor/wiki/Third-party-licenses"/>
                 </MenuItem>
             </Menu>
             <StackPanel DockPanel.Dock="Right" VerticalAlignment="Top" Orientation="Horizontal"
@@ -162,7 +163,10 @@
         <Grid Grid.Column="1" Grid.Row="2" Background="#303030" Margin="0,7,5,0">
             <Grid>
                 <vws:MainDrawingPanel ZoomPercentage="{Binding ZoomPercentage, Mode=TwoWay}" Center="{Binding RecenterZoombox, Mode=TwoWay}" x:Name="DrawingPanel"
-                                      CenterOnStart="True" Cursor="{Binding ToolCursor}">
+                                      CenterOnStart="True" Cursor="{Binding ToolCursor}" 
+                                      MiddleMouseClickedCommand="{Binding SelectToolCommand}" 
+                                      MiddleMouseClickedCommandParameter="{x:Static tools:ToolType.MoveViewport}"
+                                      ViewportPosition="{Binding ViewportPosition, Mode=TwoWay}">
                     <i:Interaction.Triggers>
                         <i:EventTrigger EventName="MouseMove">
                             <i:InvokeCommandAction Command="{Binding MouseMoveCommand}" />
@@ -171,7 +175,7 @@
                             <i:InvokeCommandAction Command="{Binding MouseUpCommand}" />
                         </i:EventTrigger>
                         <i:EventTrigger EventName="MouseDown">
-                            <i:InvokeCommandAction Command="{Binding MouseDownCommand}" />
+                            <i:InvokeCommandAction Command="{Binding MouseDownCommand}"/>
                         </i:EventTrigger>
                     </i:Interaction.Triggers>
                     <i:Interaction.Behaviors>
@@ -190,8 +194,8 @@
                             <Image Source="{Binding BitmapManager.PreviewLayer.LayerBitmap}" Panel.ZIndex="2"
                                    RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
                                    Width="{Binding BitmapManager.PreviewLayer.Width}"
-                                   Height="{Binding BitmapManager.PreviewLayer.Height}"
-                                   Margin="{Binding BitmapManager.PreviewLayer.Offset}" />
+                                   Height="{Binding BitmapManager.PreviewLayer.Height}" 
+                                   Margin="{Binding BitmapManager.PreviewLayer.Offset}"/>
                             <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Layers}">
                                 <ItemsControl.ItemsPanel>
                                     <ItemsPanelTemplate>
@@ -211,7 +215,7 @@
                             <Image VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding ActiveSelection.SelectionLayer.LayerBitmap}"
                                    RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
                                    Width="{Binding ActiveSelection.SelectionLayer.Width}"
-                                   Height="{Binding ActiveSelection.SelectionLayer.Height}"
+                                   Height="{Binding ActiveSelection.SelectionLayer.Height}" 
                                    Margin="{Binding ActiveSelection.SelectionLayer.Offset}" />
                         </Canvas>
                     </vws:MainDrawingPanel.Item>
@@ -246,9 +250,10 @@
                 <RowDefinition Height="250*" />
                 <RowDefinition Height="209*" />
             </Grid.RowDefinitions>
-            <StackPanel Grid.Row="2" Orientation="Vertical" ZIndex="15" />
+            <StackPanel Grid.Row="2" Orientation="Vertical" ZIndex="15">
+            </StackPanel>
             <colorpicker:StandardColorPicker Grid.Row="0" SelectedColor="{Binding PrimaryColor, Mode=TwoWay}"
-                                             SecondaryColor="{Binding SecondaryColor, Mode=TwoWay}" />
+                             SecondaryColor="{Binding SecondaryColor, Mode=TwoWay}" />
             <avalondock:DockingManager Foreground="White" Background="{StaticResource AccentColor}" BorderThickness="0"
                                        Grid.Row="1">
                 <avalondock:LayoutRoot x:Name="LayoutRoot">
@@ -262,13 +267,13 @@
                                             HorizontalAlignment="Stretch" Margin="5"
                                             Style="{StaticResource DarkRoundButton}" />
                                     <StackPanel Orientation="Horizontal" Margin="10,0">
-                                        <Label Content="Opacity" Foreground="White" VerticalAlignment="Center" />
+                                        <Label Content="Opacity" Foreground="White" VerticalAlignment="Center"/>
                                         <vws:NumberInput Min="0" Max="100" Width="40" Height="20" VerticalAlignment="Center"
                                                          Value="{Binding BitmapManager.ActiveDocument.ActiveLayer.Opacity, Mode=TwoWay, 
                                             Converter={StaticResource FloatNormalizeConverter}}" />
-                                        <Label Content="%" Foreground="White" VerticalAlignment="Center" />
+                                        <Label Content="%" Foreground="White" VerticalAlignment="Center"/>
                                     </StackPanel>
-                                    <Separator Background="{StaticResource BrighterAccentColor}" Margin="0,10,0,10" />
+                                    <Separator Background="{StaticResource BrighterAccentColor}" Margin="0,10,0,10"/>
                                     <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Layers}"
                                                   x:Name="layersItemsControl" AlternationCount="9999">
                                         <ItemsControl.ItemsPanel>
@@ -278,31 +283,29 @@
                                         </ItemsControl.ItemsPanel>
                                         <ItemsControl.ItemTemplate>
                                             <DataTemplate>
-                                                <vws:LayerItem
-                                                    LayerIndex="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                            Path=(ItemsControl.AlternationIndex)}"
-                                                    SetActiveLayerCommand="{Binding Path=DataContext.SetActiveLayerCommand, ElementName=mainWindow}"
-                                                    LayerName="{Binding Name, Mode=TwoWay}" IsActive="{Binding IsActive, Mode=TwoWay}"
-                                                    IsRenaming="{Binding IsRenaming, Mode=TwoWay}"
-                                                    MoveToBackCommand="{Binding DataContext.MoveToBackCommand, ElementName=mainWindow}"
-                                                    MoveToFrontCommand="{Binding DataContext.MoveToFrontCommand, ElementName=mainWindow}">
+                                                <vws:LayerItem LayerIndex="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                            Path=(ItemsControl.AlternationIndex)}" SetActiveLayerCommand="{Binding Path=DataContext.SetActiveLayerCommand, ElementName=mainWindow}"
+                                                               LayerName="{Binding Name, Mode=TwoWay}" IsActive="{Binding IsActive, Mode=TwoWay}"
+                                                               IsRenaming="{Binding IsRenaming, Mode=TwoWay}"
+                                                               MoveToBackCommand="{Binding DataContext.MoveToBackCommand, ElementName=mainWindow}"
+                                                               MoveToFrontCommand="{Binding DataContext.MoveToFrontCommand, ElementName=mainWindow}">
                                                     <vws:LayerItem.ContextMenu>
                                                         <ContextMenu>
                                                             <MenuItem Header="Delete"
-                                                                      Command="{Binding DeleteLayerCommand, Source={StaticResource ViewModelMain}}"
-                                                                      CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                                                              Command="{Binding DeleteLayerCommand, Source={StaticResource ViewModelMain}}"
+                                                                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
                             Path=(ItemsControl.AlternationIndex)}" />
                                                             <MenuItem Header="Rename"
-                                                                      Command="{Binding RenameLayerCommand, Source={StaticResource ViewModelMain}}"
-                                                                      CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                                                              Command="{Binding RenameLayerCommand, Source={StaticResource ViewModelMain}}"
+                                                                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
                             Path=(ItemsControl.AlternationIndex)}" />
                                                             <MenuItem Header="Move to front"
-                                                                      Command="{Binding MoveToFrontCommand, Source={StaticResource ViewModelMain}}"
-                                                                      CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                                                              Command="{Binding MoveToFrontCommand, Source={StaticResource ViewModelMain}}"
+                                                                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
                             Path=(ItemsControl.AlternationIndex)}" />
                                                             <MenuItem Header="Move to back"
-                                                                      Command="{Binding MoveToBackCommand, Source={StaticResource ViewModelMain}}"
-                                                                      CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                                                              Command="{Binding MoveToBackCommand, Source={StaticResource ViewModelMain}}"
+                                                                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
                             Path=(ItemsControl.AlternationIndex)}" />
                                                         </ContextMenu>
                                                     </vws:LayerItem.ContextMenu>
@@ -385,20 +388,18 @@
         </Grid>
         <DockPanel Grid.Row="3" Grid.Column="1">
             <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
-                <TextBlock Text="X:" Foreground="White" FontSize="16" />
-                <TextBlock Margin="4,0,10,0" Text="{Binding MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16" />
-                <TextBlock Text="Y:" Foreground="White" FontSize="16" />
-                <TextBlock Margin="4,0,10,0" Text="{Binding MouseYOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16" />
+                <TextBlock Text="X:" Foreground="White" FontSize="16"/>
+                <TextBlock Margin="4,0,10,0" Text="{Binding MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
+                <TextBlock Text="Y:" Foreground="White" FontSize="16"/>
+                <TextBlock Margin="4,0,10,0" Text="{Binding MouseYOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
             </StackPanel>
         </DockPanel>
         <StackPanel Margin="10,0,0,0" VerticalAlignment="Center" Grid.Row="3"
-                    Grid.Column="3" Orientation="Horizontal">
-            <Button Style="{StaticResource BaseDarkButton}"
-                    Visibility="{Binding UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}}" FontSize="14" Height="20" Command="{Binding RestartApplicationCommand}">
-                Restart
-            </Button>
+                       Grid.Column="3" Orientation="Horizontal">
+            <Button Style="{StaticResource BaseDarkButton}" 
+                    Visibility="{Binding UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}}" FontSize="14" Height="20" Command="{Binding RestartApplicationCommand}">Restart</Button>
             <TextBlock VerticalAlignment="Center" Padding="10" HorizontalAlignment="Right"
-                       Foreground="White" FontSize="14" Text="{Binding VersionText}" />
+                       Foreground="White" FontSize="14"  Text="{Binding VersionText}" />
         </StackPanel>
     </Grid>
-</Window>
+</Window>

+ 24 - 8
PixiEditor/Views/MainWindow.xaml.cs

@@ -1,19 +1,22 @@
 using System;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.IO;
+using System.Reflection;
 using System.Windows;
 using System.Windows.Input;
+using PixiEditor.Models.Processes;
+using PixiEditor.UpdateModule;
 using PixiEditor.ViewModels;
 
 namespace PixiEditor
 {
     /// <summary>
-    ///     Interaction logic for MainWindow.xaml.
+    ///     Interaction logic for MainWindow.xaml
     /// </summary>
     public partial class MainWindow : Window
     {
-        private readonly ViewModelMain viewModel;
-
+        ViewModelMain viewModel;
         public MainWindow()
         {
             InitializeComponent();
@@ -28,16 +31,19 @@ namespace PixiEditor
             e.CanExecute = true;
         }
 
+
         private void CommandBinding_Executed_Minimize(object sender, ExecutedRoutedEventArgs e)
         {
             SystemCommands.MinimizeWindow(this);
         }
 
+
         private void CommandBinding_Executed_Maximize(object sender, ExecutedRoutedEventArgs e)
         {
             SystemCommands.MaximizeWindow(this);
         }
 
+
         private void CommandBinding_Executed_Restore(object sender, ExecutedRoutedEventArgs e)
         {
             SystemCommands.RestoreWindow(this);
@@ -48,6 +54,7 @@ namespace PixiEditor
             SystemCommands.CloseWindow(this);
         }
 
+
         private void MainWindowStateChangeRaised(object sender, EventArgs e)
         {
             if (WindowState == WindowState.Maximized)
@@ -62,16 +69,25 @@ namespace PixiEditor
             }
         }
 
-        private void MainWindow_Initialized(object sender, EventArgs e)
+        private void mainWindow_Initialized(object sender, EventArgs e)
         {
             string dir = AppDomain.CurrentDomain.BaseDirectory;
-            bool updateFileExists = Directory.GetFiles(dir, "update-*.zip").Length > 0;
+            UpdateDownloader.CreateTempDirectory();
+            bool updateFileExists = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.zip").Length > 0;
             string updaterPath = Path.Join(dir, "PixiEditor.UpdateInstaller.exe");
             if (updateFileExists && File.Exists(updaterPath))
             {
-                Process.Start(updaterPath);
-                Close();
+                try
+                {
+                    ProcessHelper.RunAsAdmin(updaterPath);
+                    Close();
+                }
+                catch(Win32Exception)
+                {
+                    MessageBox.Show("Couldn't update without administrator rights.", "Insufficient permissions", 
+                        MessageBoxButton.OK, MessageBoxImage.Error);
+                }
             }
         }
     }
-}
+}

+ 5 - 5
PixiEditorTests/PixiEditorTests.csproj

@@ -12,19 +12,19 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Codecov" Version="1.12.1" />
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0">
+    <PackageReference Include="Codecov" Version="1.12.3" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
     <PackageReference Include="OpenCover" Version="4.7.922" />
     <PackageReference Include="xunit" Version="2.4.1" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Xunit.StaFact" Version="0.3.18" />
+    <PackageReference Include="Xunit.StaFact" Version="1.0.37" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

@@ -88,7 +88,7 @@ namespace PixiEditorTests.ViewModelsTests
 
             Assert.True(viewModel.BitmapManager.MouseController.IsRecordingChanges);
 
-            viewModel.MouseHook_OnMouseUp(default, default);
+            viewModel.MouseHook_OnMouseUp(default, default, default);
 
             Assert.False(viewModel.BitmapManager.MouseController.IsRecordingChanges);
         }

+ 4 - 33
windows-x64-release.yml

@@ -5,7 +5,8 @@
 
 trigger:
 - release
-- cd-azure-pipelines
+
+pr: none
 
 pool:
   vmImage: 'windows-latest'
@@ -42,36 +43,6 @@ steps:
   inputs:
     filePath: 'assemblyVerReader.ps1'
 
-- task: DotNetCoreCLI@2
-  displayName: "Build release PixiEditor Self-contained"
-  inputs:
-    command: 'publish'
-    publishWebProjects: false
-    projects: '**/PixiEditor.csproj'
-    arguments: '-o "Builds/PixiEditor-x64" --self-contained=true -r "win-x64" -c Release'
-    zipAfterPublish: false
-
-- task: CopyFiles@2
-  displayName: "Copy updater to PixiEditor target dir"
-  inputs:
-    SourceFolder: 'UpdateInstaller'
-    Contents: '**'
-    TargetFolder: 'Builds/PixiEditor-x64/PixiEditor/'
-    flattenFolders: true
-
-- task: PowerShell@2
-  displayName: "Compile installer"
-  inputs:
-    targetType: 'inline'
-    script: '& "$env:userprofile\.nuget\packages\tools.innosetup\6.0.5\tools\ISCC.exe" Installer\installer-setup-x64.iss'
-  
-- task: PublishPipelineArtifact@1
-  displayName: "Publish artifact"
-  inputs:
-    targetPath: 'Installer/Assets/PixiEditor-x64'
-    artifact: 'PixiEditor-setup-x64.exe'
-    publishLocation: 'pipeline'
-
 - task: DotNetCoreCLI@2
   displayName: "Build release PixiEditor x64 light"
   inputs:
@@ -84,7 +55,7 @@ steps:
 - task: ArchiveFiles@2
   inputs:
     rootFolderOrFile: 'Builds\PixiEditor-x64-light'
-    includeRootFolder: true
+    includeRootFolder: false
     archiveType: 'zip'
     archiveFile: 'PixiEditor.$(TagVersion).x64.zip'
     replaceExistingArchive: true
@@ -114,5 +85,5 @@ steps:
   displayName: "Publish artifact"
   inputs:
     targetPath: 'Installer/Assets/PixiEditor-x64-light/'
-    artifact: 'PixiEditor-setup-x64-light.exe'
+    artifact: 'PixiEditor-setup-x64.exe'
     publishLocation: 'pipeline'

+ 3 - 33
windows-x86-release.yml

@@ -5,7 +5,7 @@
 
 trigger:
 - release
-- cd-azure-pipelines
+pr: none
 
 pool:
   vmImage: 'windows-latest'
@@ -42,36 +42,6 @@ steps:
     arguments: '-o "UpdateInstaller" -r "win-x86" --self-contained=false -p:PublishSingleFile=true -c Release'
     zipAfterPublish: false
 
-- task: DotNetCoreCLI@2
-  displayName: "Build release PixiEditor Self-contained"
-  inputs:
-    command: 'publish'
-    publishWebProjects: false
-    projects: '**/PixiEditor.csproj'
-    arguments: '-o "Builds/PixiEditor-x86" --self-contained=true -r "win-x86" -c Release'
-    zipAfterPublish: false
-
-- task: CopyFiles@2
-  displayName: "Copy updater to PixiEditor target dir"
-  inputs:
-    SourceFolder: 'UpdateInstaller'
-    Contents: '**'
-    TargetFolder: 'Builds/PixiEditor-x86/PixiEditor'
-    flattenFolders: true
-
-- task: PowerShell@2
-  displayName: "Compile installer"
-  inputs:
-    targetType: 'inline'
-    script: '& "$env:userprofile\.nuget\packages\tools.innosetup\6.0.5\tools\ISCC.exe" Installer\installer-setup-x86.iss'
-  
-- task: PublishPipelineArtifact@1
-  displayName: "Publish artifact"
-  inputs:
-    targetPath: 'Installer/Assets/PixiEditor-x86/'
-    artifact: 'PixiEditor-setup-x86.exe'
-    publishLocation: 'pipeline'
-
 - task: DotNetCoreCLI@2
   displayName: "Build release PixiEditor x86 light"
   inputs:
@@ -84,7 +54,7 @@ steps:
 - task: ArchiveFiles@2
   inputs:
     rootFolderOrFile: 'Builds\PixiEditor-x86-light'
-    includeRootFolder: true
+    includeRootFolder: false
     archiveType: 'zip'
     archiveFile: 'PixiEditor.$(TagVersion).x86.zip'
     replaceExistingArchive: true
@@ -114,5 +84,5 @@ steps:
   displayName: "Publish artifact"
   inputs:
     targetPath: 'Installer/Assets/PixiEditor-x86-light/'
-    artifact: 'PixiEditor-setup-x86-light.exe'
+    artifact: 'PixiEditor-setup-x86.exe'
     publishLocation: 'pipeline'

Some files were not shown because too many files changed in this diff