diff --git a/README.md b/README.md index 22e909bf..9d923107 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Opensource Git GUI client. * Fast * English/简体中文/繁體中文 * Built-in light/dark themes +* Customize theme * Visual commit graph * Supports SSH access with each remote * GIT commands with GUI @@ -24,6 +25,8 @@ Opensource Git GUI client. * File histories * Blame * Revision Diffs + * Branch Diff + * Image Diff * GitFlow support > **Linux** only tested on **Ubuntu 22.04** on **X11**. @@ -87,6 +90,48 @@ This app supports open repository in external tools listed in the table below. ![Theme Light](./screenshots/theme_light.png) +## How to Customize Theme + +1. Create a new json file, and provide your favorite colors with follow keys: + +| Key | Description | +| --- | --- | +| Color.Window | Window background color | +| Color.WindowBorder | Window border color. Only used on Linux. | +| Color.TitleBar | Title bar background color | +| Color.ToolBar | Tool bar background color | +| Color.Popup | Popup panel background color | +| Color.Contents | Background color used in inputs, data grids, file content viewer, change lists, text diff viewer, etc. | +| Color.Badage | Badage background color | +| Color.Conflict | Conflict panel background color | +| Color.ConflictForeground | Conflict panel foreground color | +| Color.Border0 | Border color used in some controls, like Window, Tab, Toolbar, etc. | +| Color.Border1 | Border color used in inputs, like TextBox, ComboBox, etc. | +| Color.Border2 | Border color used in visual lines, like seperators, Rectange, etc. | +| Color.FlatButton.Background | Flat button background color, like `Cancel`, `Commit & Push` button | +| Color.FlatButton.BackgroundHovered | Flat button background color when hovered, like `Cancel` button | +| Color.FlatButton.PrimaryBackground | Primary flat button background color, like `Ok`, `Commit` button | +| Color.FlatButton.PrimaryBackgroundHovered | Primary flat button background color when hovered, like `Ok`, `Commit` button | +| Color.FG1 | Primary foreground color for all text elements | +| Color.FG2 | Secondary foreground color for all text elements | +| Color.Diff.EmptyBG | Background color used in empty lines in diff viewer | +| Color.Diff.AddedBG | Background color used in added lines in diff viewer | +| Color.Diff.DeletedBG | Background color used in deleted lines in diff viewer | +| Color.Diff.AddedHighlight | Background color used for changed words in added lines in diff viewer | +| Color.Diff.DeletedHighlight | Background color used for changed words in deleted lines in diff viewer | + +For example: + +```json +{ + "Color.Window": "#FFFF6059" +} +``` + +2. Open `Preference` -> `Appearance`, choose the json file you just created in `Custom Color Schema`. + +> **NOTE**: The `Custom Color Schema` will override the colors with same keys in current active theme. + ## Contributing Thanks to all the people who contribute. diff --git a/VERSION b/VERSION index b14fccec..19a7efe8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.16 \ No newline at end of file +8.17 \ No newline at end of file diff --git a/build/build.windows.ps1 b/build/build.windows.ps1 index 392a1ec3..a218d5cd 100644 --- a/build/build.windows.ps1 +++ b/build/build.windows.ps1 @@ -6,6 +6,16 @@ if (Test-Path SourceGit) { Remove-Item *.zip -Force +dotnet publish ..\src\SourceGit.csproj -c Release -r win-arm64 -o SourceGit -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained + +Remove-Item SourceGit\*.pdb -Force + +Compress-Archive -Path SourceGit -DestinationPath "sourcegit_$version.win-arm64.zip" + +if (Test-Path SourceGit) { + Remove-Item SourceGit -Recurse -Force +} + dotnet publish ..\src\SourceGit.csproj -c Release -r win-x64 -o SourceGit -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained Remove-Item SourceGit\*.pdb -Force diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs index e47631c1..b115844d 100644 --- a/src/Commands/GitFlow.cs +++ b/src/Commands/GitFlow.cs @@ -1,90 +1,164 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Threading; namespace SourceGit.Commands { - public class GitFlow : Command + public static class GitFlow { - public GitFlow(string repo) + public class BranchDetectResult { - WorkingDirectory = repo; - Context = repo; + public bool IsGitFlowBranch { get; set; } = false; + public string Type { get; set; } = string.Empty; + public string Prefix { get; set; } = string.Empty; } - public bool Init(List branches, string master, string develop, string feature, string release, string hotfix, string version) + public static bool IsEnabled(string repo, List branches) + { + var localBrancheNames = new HashSet(); + foreach (var branch in branches) + { + if (branch.IsLocal) + localBrancheNames.Add(branch.Name); + } + + var config = new Config(repo).ListAll(); + if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master)) + return false; + + if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop)) + return false; + + return config.ContainsKey("gitflow.prefix.feature") && + config.ContainsKey("gitflow.prefix.release") && + config.ContainsKey("gitflow.prefix.hotfix"); + } + + public static bool Init(string repo, List branches, string master, string develop, string feature, string release, string hotfix, string version) { var current = branches.Find(x => x.IsCurrent); var masterBranch = branches.Find(x => x.Name == master); if (masterBranch == null && current != null) - Branch.Create(WorkingDirectory, master, current.Head); + Branch.Create(repo, master, current.Head); var devBranch = branches.Find(x => x.Name == develop); if (devBranch == null && current != null) - Branch.Create(WorkingDirectory, develop, current.Head); + Branch.Create(repo, develop, current.Head); - var cmd = new Config(WorkingDirectory); - cmd.Set("gitflow.branch.master", master); - cmd.Set("gitflow.branch.develop", develop); - cmd.Set("gitflow.prefix.feature", feature); - cmd.Set("gitflow.prefix.bugfix", "bugfix/"); - cmd.Set("gitflow.prefix.release", release); - cmd.Set("gitflow.prefix.hotfix", hotfix); - cmd.Set("gitflow.prefix.support", "support/"); - cmd.Set("gitflow.prefix.versiontag", version, true); + var config = new Config(repo); + config.Set("gitflow.branch.master", master); + config.Set("gitflow.branch.develop", develop); + config.Set("gitflow.prefix.feature", feature); + config.Set("gitflow.prefix.bugfix", "bugfix/"); + config.Set("gitflow.prefix.release", release); + config.Set("gitflow.prefix.hotfix", hotfix); + config.Set("gitflow.prefix.support", "support/"); + config.Set("gitflow.prefix.versiontag", version, true); - Args = "flow init -d"; - return Exec(); + var init = new Command(); + init.WorkingDirectory = repo; + init.Context = repo; + init.Args = "flow init -d"; + return init.Exec(); } - public bool Start(Models.GitFlowBranchType type, string name) + public static string Prefix(string repo, string type) { - switch (type) + return new Config(repo).Get($"gitflow.prefix.{type}"); + } + + public static BranchDetectResult DetectType(string repo, List branches, string branch) + { + var rs = new BranchDetectResult(); + var localBrancheNames = new HashSet(); + foreach (var b in branches) { - case Models.GitFlowBranchType.Feature: - Args = $"flow feature start {name}"; - break; - case Models.GitFlowBranchType.Release: - Args = $"flow release start {name}"; - break; - case Models.GitFlowBranchType.Hotfix: - Args = $"flow hotfix start {name}"; - break; - default: - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(Context, "Bad branch type!!!"); - }); - return false; + if (b.IsLocal) + localBrancheNames.Add(b.Name); } - return Exec(); + var config = new Config(repo).ListAll(); + if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master)) + return rs; + + if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop)) + return rs; + + if (!config.TryGetValue("gitflow.prefix.feature", out var feature) || + !config.TryGetValue("gitflow.prefix.release", out var release) || + !config.TryGetValue("gitflow.prefix.hotfix", out var hotfix)) + return rs; + + if (branch.StartsWith(feature, StringComparison.Ordinal)) + { + rs.IsGitFlowBranch = true; + rs.Type = "feature"; + rs.Prefix = feature; + } + else if (branch.StartsWith(release, StringComparison.Ordinal)) + { + rs.IsGitFlowBranch = true; + rs.Type = "release"; + rs.Prefix = release; + } + else if (branch.StartsWith(hotfix, StringComparison.Ordinal)) + { + rs.IsGitFlowBranch = true; + rs.Type = "hotfix"; + rs.Prefix = hotfix; + } + + return rs; } - public bool Finish(Models.GitFlowBranchType type, string name, bool keepBranch) + public static bool Start(string repo, string type, string name) { + if (!SUPPORTED_BRANCH_TYPES.Contains(type)) + { + Dispatcher.UIThread.Post(() => + { + App.RaiseException(repo, "Bad branch type!!!"); + }); + + return false; + } + + var start = new Command(); + start.WorkingDirectory = repo; + start.Context = repo; + start.Args = $"flow {type} start {name}"; + return start.Exec(); + } + + public static bool Finish(string repo, string type, string name, bool keepBranch) + { + if (!SUPPORTED_BRANCH_TYPES.Contains(type)) + { + Dispatcher.UIThread.Post(() => + { + App.RaiseException(repo, "Bad branch type!!!"); + }); + + return false; + } + var option = keepBranch ? "-k" : string.Empty; - switch (type) - { - case Models.GitFlowBranchType.Feature: - Args = $"flow feature finish {option} {name}"; - break; - case Models.GitFlowBranchType.Release: - Args = $"flow release finish {option} {name} -m \"RELEASE_DONE\""; - break; - case Models.GitFlowBranchType.Hotfix: - Args = $"flow hotfix finish {option} {name} -m \"HOTFIX_DONE\""; - break; - default: - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(Context, "Bad branch type!!!"); - }); - return false; - } - - return Exec(); + var finish = new Command(); + finish.WorkingDirectory = repo; + finish.Context = repo; + finish.Args = $"flow {type} finish {option} {name}"; + return finish.Exec(); } + + private static readonly List SUPPORTED_BRANCH_TYPES = new List() + { + "feature", + "release", + "bugfix", + "hotfix", + "support", + }; } } diff --git a/src/Commands/GitIgnore.cs b/src/Commands/GitIgnore.cs new file mode 100644 index 00000000..44bb268b --- /dev/null +++ b/src/Commands/GitIgnore.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace SourceGit.Commands +{ + public static class GitIgnore + { + public static void Add(string repo, string pattern) + { + var file = Path.Combine(repo, ".gitignore"); + if (!File.Exists(file)) + File.WriteAllLines(file, [ pattern ]); + else + File.AppendAllLines(file, [ pattern ]); + } + } +} diff --git a/src/Commands/QueryCommitFullMessage.cs b/src/Commands/QueryCommitFullMessage.cs index f4be9bc5..c8f1867d 100644 --- a/src/Commands/QueryCommitFullMessage.cs +++ b/src/Commands/QueryCommitFullMessage.cs @@ -12,7 +12,8 @@ namespace SourceGit.Commands public string Result() { var rs = ReadToEnd(); - if (rs.IsSuccess) return rs.StdOut.TrimEnd(); + if (rs.IsSuccess) + return rs.StdOut.TrimEnd(); return string.Empty; } } diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 075d6467..401f1682 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -58,11 +58,14 @@ namespace SourceGit.Commands } nextPartIdx++; - + start = end + 1; end = rs.StdOut.IndexOf('\n', start); } + if (start < rs.StdOut.Length) + _current.Subject = rs.StdOut.Substring(start); + if (_findFirstMerged && !_isHeadFounded && _commits.Count > 0) MarkFirstMerged(); diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 4a553817..a3b13929 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; namespace SourceGit.Commands { diff --git a/src/Converters/WindowStateConverters.cs b/src/Converters/WindowStateConverters.cs deleted file mode 100644 index 05f0b1cd..00000000 --- a/src/Converters/WindowStateConverters.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -using Avalonia; -using Avalonia.Controls; -using Avalonia.Data.Converters; - -namespace SourceGit.Converters -{ - public static class WindowStateConverters - { - public static readonly FuncValueConverter ToContentMargin = - new FuncValueConverter(state => - { - if (OperatingSystem.IsWindows() && state == WindowState.Maximized) - return new Thickness(6); - else if (OperatingSystem.IsLinux() && state != WindowState.Maximized) - return new Thickness(6); - else - return new Thickness(0); - }); - - public static readonly FuncValueConverter ToTitleBarHeight = - new FuncValueConverter(state => - { - if (state == WindowState.Maximized) - return new GridLength(OperatingSystem.IsMacOS() ? 34 : 30); - else - return new GridLength(38); - }); - - public static readonly FuncValueConverter IsNormal = - new FuncValueConverter(state => state == WindowState.Normal); - } -} diff --git a/src/Models/GitFlow.cs b/src/Models/GitFlow.cs deleted file mode 100644 index 9522236d..00000000 --- a/src/Models/GitFlow.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace SourceGit.Models -{ - public enum GitFlowBranchType - { - None, - Feature, - Release, - Hotfix, - } - - public class GitFlow - { - public string Feature { get; set; } - public string Release { get; set; } - public string Hotfix { get; set; } - - public bool IsEnabled - { - get - { - return !string.IsNullOrEmpty(Feature) - && !string.IsNullOrEmpty(Release) - && !string.IsNullOrEmpty(Hotfix); - } - } - - public GitFlowBranchType GetBranchType(string name) - { - if (!IsEnabled) - return GitFlowBranchType.None; - if (name.StartsWith(Feature)) - return GitFlowBranchType.Feature; - if (name.StartsWith(Release)) - return GitFlowBranchType.Release; - if (name.StartsWith(Hotfix)) - return GitFlowBranchType.Hotfix; - return GitFlowBranchType.None; - } - } -} diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 8f43742c..32124080 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -98,4 +98,6 @@ M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l328 0 0 64-328 0Z M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z M248 221a77 77 0 00-30-21c-18-7-40-10-68-5a224 224 0 00-45 13c-5 2-10 5-15 8l-3 2v68l11-9c10-8 21-14 34-19 13-5 26-7 39-7 12 0 21 3 28 10 6 6 9 16 9 29l-62 9c-14 2-26 6-36 11a80 80 0 00-25 20c-7 8-12 17-15 27-6 21-6 44 1 65a70 70 0 0041 43c10 4 21 6 34 6a80 80 0 0063-28v22h64V298c0-16-2-31-6-44a91 91 0 00-18-33zm-41 121v15c0 8-1 15-4 22a48 48 0 01-24 29 44 44 0 01-33 2 29 29 0 01-10-6 25 25 0 01-6-9 30 30 0 01-2-12c0-5 1-9 2-14a21 21 0 015-9 28 28 0 0110-7 83 83 0 0120-5l42-6zm323-68a144 144 0 00-16-42 87 87 0 00-28-29 75 75 0 00-41-11 73 73 0 00-44 14c-6 5-12 11-17 17V64H326v398h59v-18c8 10 18 17 30 21 6 2 13 3 21 3 16 0 31-4 43-11 12-7 23-18 31-31a147 147 0 0019-46 248 248 0 006-57c0-17-2-33-5-49zm-55 49c0 15-1 28-4 39-2 11-6 20-10 27a41 41 0 01-15 15 37 37 0 01-36 1 44 44 0 01-13-12 59 59 0 01-9-18A76 76 0 01384 352v-33c0-10 1-20 4-29 2-8 6-15 10-22a43 43 0 0115-13 37 37 0 0119-5 35 35 0 0132 18c4 6 7 14 9 23 2 9 3 20 3 31zM154 634a58 58 0 0120-15c14-6 35-7 49-1 7 3 13 6 20 12l21 17V572l-6-4a124 124 0 00-58-14c-20 0-38 4-54 11-16 7-30 17-41 30-12 13-20 29-26 46-6 17-9 36-9 57 0 18 3 36 8 52 6 16 14 30 24 42 10 12 23 21 38 28 15 7 32 10 50 10 15 0 28-2 39-5 11-3 21-8 30-14l5-4v-57l-13 6a26 26 0 01-5 2c-3 1-6 2-8 3-2 1-15 6-15 6-4 2-9 3-14 4a63 63 0 01-38-4 53 53 0 01-20-14 70 70 0 01-13-24 111 111 0 01-5-34c0-13 2-26 5-36 3-10 8-19 14-26zM896 384h-256V320h288c21 1 32 12 32 32v384c0 18-12 32-32 32H504l132 133-45 45-185-185c-16-21-16-25 0-45l185-185L637 576l-128 128H896V384z + M128 183C128 154 154 128 183 128h521c30 0 55 26 55 55v38c0 17-17 34-34 34s-34-17-34-34v-26H196v495h26c17 0 34 17 34 34s-17 34-34 34h-38c-30 0-55-26-55-55V183zM380 896h-34c-26 0-47-21-47-47v-90h68V828h64V896H380c4 0 0 0 0 0zM759 828V896h90c26 0 47-21 47-47v-90h-68V828h-68zM828 435H896V346c0-26-21-47-47-47h-90v68H828v68zM435 299v68H367V439H299V346C299 320 320 299 346 299h90zM367 649H299v-107h68v107zM546 367V299h107v68h-107zM828 546H896v107h-68v-107zM649 828V896h-107v-68h107zM730 508v188c0 17-17 34-34 34h-188c-17 0-34-17-34-34s17-34 34-34h102l-124-124c-13-13-13-34 0-47 13-13 34-13 47 0l124 124V512c0-17 17-34 34-34 21-4 38 9 38 30z + M590 74 859 342V876c0 38-31 68-68 68H233c-38 0-68-31-68-68V142c0-38 31-68 68-68h357zm-12 28H233a40 40 0 00-40 38L193 142v734a40 40 0 0038 40L233 916h558a40 40 0 0040-38L831 876V354L578 102zM855 371h-215c-46 0-83-36-84-82l0-2V74h28v213c0 30 24 54 54 55l2 0h215v28zM57 489m28 0 853 0q28 0 28 28l0 284q0 28-28 28l-853 0q-28 0-28-28l0-284q0-28 28-28ZM157 717c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C184 610 172 603 156 603c-29 0-54 21-54 57 0 37 24 56 54 56zM245 711v-108h-34v108h34zm69 0v-86H341V603H262v22h28V711h24zM393 711v-108h-34v108h34zm66 6c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C485 610 474 603 458 603c-29 0-54 21-54 57 0 37 24 56 54 56zm88-6v-36c0-13-2-28-3-40h1l10 24 25 52H603v-108h-23v36c0 13 2 28 3 40h-1l-10-24L548 603H523v108h23zM677 717c30 0 51-22 51-57 0-36-21-56-51-56-30 0-51 20-51 56 0 36 21 57 51 57zm3-23c-16 0-26-12-26-32 0-19 10-31 26-31 16 0 26 11 26 31S696 694 680 694zm93 17v-38h13l21 38H836l-25-43c12-5 19-15 19-31 0-26-20-34-44-34H745v108h27zm16-51H774v-34h15c16 0 25 4 25 16s-9 18-25 18zM922 711v-22h-43v-23h35v-22h-35V625h41V603H853v108h68z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 856577d7..a1b03bf0 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -33,6 +33,7 @@ Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! Checkout${0}$ + Compare with Branch Compare with HEAD Compare with Worktree Copy Branch Name @@ -49,6 +50,7 @@ Rename${0}$ Tracking ... Unset Upstream + Branch Compare Bytes CANCEL CHANGE DISPLAY MODE @@ -241,6 +243,8 @@ Create new page Open preference dialog REPOSITORY + Commit staged changes + Commit and push staged changes Force to reload this repository Stage/Unstage selected changes Open commit search @@ -473,6 +477,11 @@ Search Repositories ... Sort Changes + Add To .gitignore ... + Ignore all *{0} files + Ignore *{0} files in the same folder + Ignore files in the same folder + Ignore this file only Amend You can stage this file now. COMMIT diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index e5b46759..1de41e04 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -36,6 +36,7 @@ 逐行追溯(blame) 选中文件不支持该操作!!! 检出(checkout)${0}$ + 与其他分支对比 与当前HEAD比较 与本地工作树比较 复制分支名 @@ -52,6 +53,7 @@ 重命名${0}$ 切换上游分支... 取消追踪 + 分支比较 字节 取 消 切换变更显示模式 @@ -244,6 +246,8 @@ 新建页面 打开偏好设置面板 仓库页面快捷键 + 提交暂存区更改 + 提交暂存区更改并推送 重新加载仓库状态 将选中的变更暂存或从暂存列表中移除 打开历史搜索 @@ -476,6 +480,11 @@ 快速查找仓库... 排序 本地更改 + 添加至 .gitignore 忽略列表 ... + 忽略所有 *{0} 文件 + 忽略同目录下所有 *{0} 文件 + 忽略同目录下所有文件 + 忽略本文件 修补(--amend) 现在您已可将其加入暂存区中 提交 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index ae7a6d47..79c4be87 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -36,6 +36,7 @@ 逐行追溯(blame) 選中檔案不支援該操作!!! 檢出(checkout)${0}$ + 與其他分支比較 與當前HEAD比較 與本地工作樹比較 複製分支名 @@ -52,6 +53,7 @@ 重新命名${0}$ 切換上游分支... 取消追蹤 + 分支比較 位元組 取 消 切換變更顯示模式 @@ -244,6 +246,8 @@ 新建頁面 開啟偏好設定面板 倉庫頁面快捷鍵 + 提交暫存區變更 + 提交暫存區變更併推送 重新載入倉庫狀態 將選中的變更暫存或從暫存列表中移除 開啟歷史搜尋 @@ -476,6 +480,11 @@ 快速查詢倉庫... 排序 本地更改 + 添加至 .gitignore 忽略清單 ... + 忽略所有 *{0} 檔案 + 忽略同路徑下所有 *{0} 檔案 + 忽略同路徑下所有檔案 + 忽略本檔案 修補(--amend) 現在您已可將其加入暫存區中 提交 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 114ac72d..fdf3ec3e 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -18,6 +18,143 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index 36889bca..40ff7ca6 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -6,6 +6,7 @@ #FFFFBE2F #FF29c941 #FFF0F5F9 + #FFAFAFAF #FFCFDEEA #FFF0F5F9 #FFF8F8F8 @@ -14,22 +15,21 @@ #FF6F6F6F #FFF8F8F8 #FF836C2E + #FFFFFFFF #FFCFCFCF #FF898989 #FFCFCFCF - #FFEFEFEF #FFF8F8F8 White #FF4295FF #FF529DFB #FF1F1F1F #FF6F6F6F - #FFFFFFFF - #3C000000 - #3C00FF00 - #3CFF0000 - #5A00FF00 - #50FF0000 + #3C000000 + #3C00FF00 + #3CFF0000 + #5A00FF00 + #50FF0000 @@ -37,6 +37,7 @@ #FFFCBB2D #FF25C53C #FF252525 + #FF444444 #FF1F1F1F #FF2C2C2C #FF2B2B2B @@ -45,22 +46,21 @@ #FF505050 #FFF8F8F8 #FFFAFAD2 + #FF252525 #FF181818 #FF7C7C7C #FF404040 - #FF252525 #FF303030 #FF333333 #FF3A3A3A #FF404040 #FFDDDDDD #40F1F1F1 - #FF252525 - #3C000000 - #3C00FF00 - #3CFF0000 - #5A00FF00 - #50FF0000 + #3C000000 + #3C00FF00 + #3CFF0000 + #5A00FF00 + #50FF0000 @@ -68,6 +68,7 @@ + @@ -76,22 +77,21 @@ + - - - - - - - + + + + + diff --git a/src/ViewModels/BranchCompare.cs b/src/ViewModels/BranchCompare.cs new file mode 100644 index 00000000..6b19d249 --- /dev/null +++ b/src/ViewModels/BranchCompare.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +using Avalonia.Controls; +using Avalonia.Threading; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class BranchCompare : ObservableObject + { + public Models.Branch Base + { + get; + private set; + } + + public Models.Branch To + { + get; + private set; + } + + public Models.Commit BaseHead + { + get => _baseHead; + private set => SetProperty(ref _baseHead, value); + } + + public Models.Commit ToHead + { + get => _toHead; + private set => SetProperty(ref _toHead, value); + } + + public List VisibleChanges + { + get => _visibleChanges; + private set => SetProperty(ref _visibleChanges, value); + } + + public List SelectedChanges + { + get => _selectedChanges; + set + { + if (SetProperty(ref _selectedChanges, value)) + { + if (value != null && value.Count == 1) + DiffContext = new DiffContext(_repo, new Models.DiffOption(Base.Head, To.Head, value[0]), _diffContext); + else + DiffContext = null; + } + } + } + + public string SearchFilter + { + get => _searchFilter; + set + { + if (SetProperty(ref _searchFilter, value)) + { + RefreshVisible(); + } + } + } + + public DiffContext DiffContext + { + get => _diffContext; + private set => SetProperty(ref _diffContext, value); + } + + public BranchCompare(string repo, Models.Branch baseBranch, Models.Branch toBranch) + { + _repo = repo; + + Base = baseBranch; + To = toBranch; + + Task.Run(() => + { + var baseHead = new Commands.QuerySingleCommit(_repo, Base.Head).Result(); + var toHead = new Commands.QuerySingleCommit(_repo, To.Head).Result(); + _changes = new Commands.CompareRevisions(_repo, Base.Head, To.Head).Result(); + + var visible = _changes; + if (!string.IsNullOrWhiteSpace(_searchFilter)) + { + visible = new List(); + foreach (var c in _changes) + { + if (c.Path.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase)) + visible.Add(c); + } + } + + Dispatcher.UIThread.Invoke(() => + { + BaseHead = baseHead; + ToHead = toHead; + VisibleChanges = visible; + }); + }); + } + + public void NavigateTo(string commitSHA) + { + var repo = Preference.FindRepository(_repo); + if (repo != null) + repo.NavigateToCommit(commitSHA); + } + + public void ClearSearchFilter() + { + SearchFilter = string.Empty; + } + + public ContextMenu CreateChangeContextMenu() + { + if (_selectedChanges == null || _selectedChanges.Count != 1) + return null; + + var change = _selectedChanges[0]; + var menu = new ContextMenu(); + + var diffWithMerger = new MenuItem(); + diffWithMerger.Header = App.Text("DiffWithMerger"); + diffWithMerger.Icon = App.CreateMenuIcon("Icons.Diff"); + diffWithMerger.Click += (_, ev) => + { + var opt = new Models.DiffOption(Base.Head, To.Head, change); + var type = Preference.Instance.ExternalMergeToolType; + var exec = Preference.Instance.ExternalMergeToolPath; + + var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type); + if (tool == null || !File.Exists(exec)) + { + App.RaiseException(_repo, "Invalid merge tool in preference setting!"); + return; + } + + var args = tool.Type != 0 ? tool.DiffCmd : Preference.Instance.ExternalMergeToolDiffCmd; + Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, exec, args, opt)); + ev.Handled = true; + }; + menu.Items.Add(diffWithMerger); + + if (change.Index != Models.ChangeState.Deleted) + { + var full = Path.GetFullPath(Path.Combine(_repo, change.Path)); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = App.CreateMenuIcon("Icons.Folder.Open"); + explore.IsEnabled = File.Exists(full); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(full, true); + ev.Handled = true; + }; + menu.Items.Add(explore); + } + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyPath.Click += (_, ev) => + { + App.CopyText(change.Path); + ev.Handled = true; + }; + menu.Items.Add(copyPath); + + var copyFileName = new MenuItem(); + copyFileName.Header = App.Text("CopyFileName"); + copyFileName.Icon = App.CreateMenuIcon("Icons.Copy"); + copyFileName.Click += (_, e) => + { + App.CopyText(Path.GetFileName(change.Path)); + e.Handled = true; + }; + menu.Items.Add(copyFileName); + + return menu; + } + + private void RefreshVisible() + { + if (_changes == null) + return; + + if (string.IsNullOrEmpty(_searchFilter)) + { + VisibleChanges = _changes; + } + else + { + var visible = new List(); + foreach (var c in _changes) + { + if (c.Path.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase)) + visible.Add(c); + } + + VisibleChanges = visible; + } + } + + private string _repo = string.Empty; + private Models.Commit _baseHead = null; + private Models.Commit _toHead = null; + private List _changes = null; + private List _visibleChanges = null; + private List _selectedChanges = null; + private string _searchFilter = string.Empty; + private DiffContext _diffContext = null; + } +} diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 27a13836..430f4b7d 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -385,6 +385,7 @@ namespace SourceGit.ViewModels FullMessage = string.Empty; VisibleChanges = null; SelectedChanges = null; + ViewRevisionFileContent = null; if (_commit == null) return; diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs index 1fad99ae..7db69366 100644 --- a/src/ViewModels/GitFlowFinish.cs +++ b/src/ViewModels/GitFlowFinish.cs @@ -4,10 +4,15 @@ namespace SourceGit.ViewModels { public class GitFlowFinish : Popup { - public Models.Branch Branch => _branch; - public bool IsFeature => _type == Models.GitFlowBranchType.Feature; - public bool IsRelease => _type == Models.GitFlowBranchType.Release; - public bool IsHotfix => _type == Models.GitFlowBranchType.Hotfix; + public Models.Branch Branch + { + get; + set; + } = null; + + public bool IsFeature => _type == "feature"; + public bool IsRelease => _type == "release"; + public bool IsHotfix => _type == "hotfix"; public bool KeepBranch { @@ -15,11 +20,13 @@ namespace SourceGit.ViewModels set; } = false; - public GitFlowFinish(Repository repo, Models.Branch branch, Models.GitFlowBranchType type) + public GitFlowFinish(Repository repo, Models.Branch branch, string type, string prefix) { _repo = repo; - _branch = branch; _type = type; + _prefix = prefix; + + Branch = branch; View = new Views.GitFlowFinish() { DataContext = this }; } @@ -28,29 +35,16 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); return Task.Run(() => { - var branch = _branch.Name; - switch (_type) - { - case Models.GitFlowBranchType.Feature: - branch = branch.Substring(_repo.GitFlow.Feature.Length); - break; - case Models.GitFlowBranchType.Release: - branch = branch.Substring(_repo.GitFlow.Release.Length); - break; - default: - branch = branch.Substring(_repo.GitFlow.Hotfix.Length); - break; - } - - SetProgressDescription($"Git Flow - finishing {_branch.Name} ..."); - var succ = new Commands.GitFlow(_repo.FullPath).Finish(_type, branch, KeepBranch); + var name = Branch.Name.StartsWith(_prefix) ? Branch.Name.Substring(_prefix.Length) : Branch.Name; + SetProgressDescription($"Git Flow - finishing {_type} {name} ..."); + var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, KeepBranch); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); } private readonly Repository _repo = null; - private readonly Models.Branch _branch = null; - private readonly Models.GitFlowBranchType _type = Models.GitFlowBranchType.None; + private readonly string _type = "feature"; + private readonly string _prefix = string.Empty; } } diff --git a/src/ViewModels/GitFlowStart.cs b/src/ViewModels/GitFlowStart.cs index 33fa3cf4..0806dfb4 100644 --- a/src/ViewModels/GitFlowStart.cs +++ b/src/ViewModels/GitFlowStart.cs @@ -19,27 +19,15 @@ namespace SourceGit.ViewModels get => _prefix; } - public bool IsFeature => _type == Models.GitFlowBranchType.Feature; - public bool IsRelease => _type == Models.GitFlowBranchType.Release; - public bool IsHotfix => _type == Models.GitFlowBranchType.Hotfix; + public bool IsFeature => _type == "feature"; + public bool IsRelease => _type == "release"; + public bool IsHotfix => _type == "hotfix"; - public GitFlowStart(Repository repo, Models.GitFlowBranchType type) + public GitFlowStart(Repository repo, string type) { _repo = repo; _type = type; - - switch (type) - { - case Models.GitFlowBranchType.Feature: - _prefix = repo.GitFlow.Feature; - break; - case Models.GitFlowBranchType.Release: - _prefix = repo.GitFlow.Release; - break; - default: - _prefix = repo.GitFlow.Hotfix; - break; - } + _prefix = Commands.GitFlow.Prefix(repo.FullPath, type); View = new Views.GitFlowStart() { DataContext = this }; } @@ -65,15 +53,15 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); return Task.Run(() => { - SetProgressDescription($"Git Flow - starting {_prefix}{_name} ..."); - var succ = new Commands.GitFlow(_repo.FullPath).Start(_type, _name); + SetProgressDescription($"Git Flow - starting {_type} {_name} ..."); + var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); } private readonly Repository _repo = null; - private readonly Models.GitFlowBranchType _type = Models.GitFlowBranchType.Feature; + private readonly string _type = "feature"; private readonly string _prefix = string.Empty; private string _name = null; } diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 73007e02..c536731c 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -209,17 +209,6 @@ namespace SourceGit.ViewModels e.Handled = true; }; menu.Items.Add(reset); - - var checkoutCommit = new MenuItem(); - checkoutCommit.Header = App.Text("CommitCM.Checkout"); - checkoutCommit.Icon = App.CreateMenuIcon("Icons.Check"); - checkoutCommit.Click += (o, e) => - { - if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new CheckoutCommit(_repo, commit)); - e.Handled = true; - }; - menu.Items.Add(checkoutCommit); } else { @@ -290,6 +279,20 @@ namespace SourceGit.ViewModels menu.Items.Add(revert); } + if (current.Head != commit.SHA) + { + var checkoutCommit = new MenuItem(); + checkoutCommit.Header = App.Text("CommitCM.Checkout"); + checkoutCommit.Icon = App.CreateMenuIcon("Icons.Detached"); + checkoutCommit.Click += (o, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowPopup(new CheckoutCommit(_repo, commit)); + e.Handled = true; + }; + menu.Items.Add(checkoutCommit); + } + menu.Items.Add(new MenuItem() { Header = "-" }); if (current.Head != commit.SHA) @@ -448,8 +451,8 @@ namespace SourceGit.ViewModels submenu.Items.Add(push); submenu.Items.Add(new MenuItem() { Header = "-" }); - var type = _repo.GitFlow.GetBranchType(current.Name); - if (type != Models.GitFlowBranchType.None) + var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, current.Name); + if (detect.IsGitFlowBranch) { var finish = new MenuItem(); finish.Header = new Views.NameHighlightedTextBlock("BranchCM.Finish", current.Name); @@ -457,7 +460,7 @@ namespace SourceGit.ViewModels finish.Click += (o, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new GitFlowFinish(_repo, current, type)); + PopupHost.ShowPopup(new GitFlowFinish(_repo, current, detect.Type, detect.Prefix)); e.Handled = true; }; submenu.Items.Add(finish); @@ -507,8 +510,8 @@ namespace SourceGit.ViewModels submenu.Items.Add(merge); submenu.Items.Add(new MenuItem() { Header = "-" }); - var type = _repo.GitFlow.GetBranchType(branch.Name); - if (type != Models.GitFlowBranchType.None) + var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, branch.Name); + if (detect.IsGitFlowBranch) { var finish = new MenuItem(); finish.Header = new Views.NameHighlightedTextBlock("BranchCM.Finish", branch.Name); @@ -516,7 +519,7 @@ namespace SourceGit.ViewModels finish.Click += (o, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new GitFlowFinish(_repo, branch, type)); + PopupHost.ShowPopup(new GitFlowFinish(_repo, branch, detect.Type, detect.Prefix)); e.Handled = true; }; submenu.Items.Add(finish); diff --git a/src/ViewModels/InitGitFlow.cs b/src/ViewModels/InitGitFlow.cs index 3faf5e61..9e58f4bd 100644 --- a/src/ViewModels/InitGitFlow.cs +++ b/src/ViewModels/InitGitFlow.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -6,7 +7,6 @@ namespace SourceGit.ViewModels { public partial class InitGitFlow : Popup { - [GeneratedRegex(@"^[\w\-/\.]+$")] private static partial Regex TAG_PREFIX(); @@ -62,6 +62,23 @@ namespace SourceGit.ViewModels public InitGitFlow(Repository repo) { _repo = repo; + + var localBranches = new List(); + foreach (var branch in repo.Branches) + { + if (branch.IsLocal) + localBranches.Add(branch.Name); + } + + if (localBranches.Contains("master")) + _master = "master"; + else if (localBranches.Contains("main")) + _master = "main"; + else if (localBranches.Count > 0) + _master = localBranches[0]; + else + _master = "master"; + View = new Views.InitGitFlow() { DataContext = this }; } @@ -79,9 +96,7 @@ namespace SourceGit.ViewModels public static ValidationResult ValidateTagPrefix(string tagPrefix, ValidationContext ctx) { if (!string.IsNullOrWhiteSpace(tagPrefix) && !TAG_PREFIX().IsMatch(tagPrefix)) - { return new ValidationResult("Bad tag prefix format!"); - } return ValidationResult.Success; } @@ -93,14 +108,7 @@ namespace SourceGit.ViewModels return Task.Run(() => { - var succ = new Commands.GitFlow(_repo.FullPath).Init(_repo.Branches, _master, _develop, _featurePrefix, _releasePrefix, _hotfixPrefix, _tagPrefix); - if (succ) - { - _repo.GitFlow.Feature = _featurePrefix; - _repo.GitFlow.Release = _releasePrefix; - _repo.GitFlow.Hotfix = _hotfixPrefix; - } - + var succ = Commands.GitFlow.Init(_repo.FullPath, _repo.Branches, _master, _develop, _featurePrefix, _releasePrefix, _hotfixPrefix, _tagPrefix); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index c8453930..60cf93c8 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -136,6 +136,7 @@ namespace SourceGit.ViewModels last.Node = new RepositoryNode() { Id = Guid.NewGuid().ToString() }; last.Data = Welcome.Instance; + last.Popup = null; GC.Collect(); } @@ -188,6 +189,7 @@ namespace SourceGit.ViewModels } Pages = new AvaloniaList { ActivePage }; + OnPropertyChanged(nameof(Pages)); GC.Collect(); } diff --git a/src/ViewModels/LayoutInfo.cs b/src/ViewModels/LayoutInfo.cs new file mode 100644 index 00000000..e78ad2cf --- /dev/null +++ b/src/ViewModels/LayoutInfo.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Avalonia.Controls; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class LayoutInfo : ObservableObject + { + public double LauncherWidth + { + get; + set; + } = 1280; + + public double LauncherHeight + { + get; + set; + } = 720; + + public WindowState LauncherWindowState + { + get; + set; + } = WindowState.Normal; + + [JsonConverter(typeof(GridLengthConverter))] + public GridLength RepositorySidebarWidth + { + get => _repositorySidebarWidth; + set => SetProperty(ref _repositorySidebarWidth, value); + } + + [JsonConverter(typeof(GridLengthConverter))] + public GridLength WorkingCopyLeftWidth + { + get => _workingCopyLeftWidth; + set => SetProperty(ref _workingCopyLeftWidth, value); + } + + [JsonConverter(typeof(GridLengthConverter))] + public GridLength StashesLeftWidth + { + get => _stashesLeftWidth; + set => SetProperty(ref _stashesLeftWidth, value); + } + + [JsonConverter(typeof(GridLengthConverter))] + public GridLength CommitDetailChangesLeftWidth + { + get => _commitDetailChangesLeftWidth; + set => SetProperty(ref _commitDetailChangesLeftWidth, value); + } + + [JsonConverter(typeof(GridLengthConverter))] + public GridLength CommitDetailFilesLeftWidth + { + get => _commitDetailFilesLeftWidth; + set => SetProperty(ref _commitDetailFilesLeftWidth, value); + } + + private GridLength _repositorySidebarWidth = new GridLength(250, GridUnitType.Pixel); + private GridLength _workingCopyLeftWidth = new GridLength(300, GridUnitType.Pixel); + private GridLength _stashesLeftWidth = new GridLength(300, GridUnitType.Pixel); + private GridLength _commitDetailChangesLeftWidth = new GridLength(256, GridUnitType.Pixel); + private GridLength _commitDetailFilesLeftWidth = new GridLength(256, GridUnitType.Pixel); + } + + public class GridLengthConverter : JsonConverter + { + public override GridLength Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var size = reader.GetDouble(); + return new GridLength(size, GridUnitType.Pixel); + } + + public override void Write(Utf8JsonWriter writer, GridLength value, JsonSerializerOptions options) + { + writer.WriteNumberValue(value.Value); + } + } +} diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 917349e0..debe672a 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -109,6 +109,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _defaultFontSize, value); } + public LayoutInfo Layout + { + get => _layout; + set => SetProperty(ref _layout, value); + } + public string AvatarServer { get => Models.AvatarManager.SelectedServer; @@ -531,6 +537,7 @@ namespace SourceGit.ViewModels private FontFamily _defaultFont = null; private FontFamily _monospaceFont = null; private double _defaultFontSize = 13; + private LayoutInfo _layout = new LayoutInfo(); private int _maxHistoryCommits = 20000; private bool _restoreTabs = false; diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index bbb0d6be..4eb6c5fc 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -51,13 +51,6 @@ namespace SourceGit.ViewModels set; } = new AvaloniaList(); - [JsonIgnore] - public Models.GitFlow GitFlow - { - get => _gitflow; - set => SetProperty(ref _gitflow, value); - } - [JsonIgnore] public int SelectedViewIndex { @@ -298,7 +291,6 @@ namespace SourceGit.ViewModels Task.Run(RefreshSubmodules); Task.Run(RefreshWorkingCopyChanges); Task.Run(RefreshStashes); - Task.Run(RefreshGitFlow); } public void OpenInFileManager() @@ -442,7 +434,7 @@ namespace SourceGit.ViewModels else { visible = new Commands.QueryCommits(FullPath, $"-1000 -- \"{_searchCommitFilter}\"", false).Result(); - } + } SearchedCommits = visible; } @@ -697,22 +689,6 @@ namespace SourceGit.ViewModels }); } - public void RefreshGitFlow() - { - var config = new Commands.Config(_fullpath).ListAll(); - var gitFlow = new Models.GitFlow(); - if (config.TryGetValue("gitflow.prefix.feature", out var feature)) - gitFlow.Feature = feature; - if (config.TryGetValue("gitflow.prefix.release", out var release)) - gitFlow.Release = release; - if (config.TryGetValue("gitflow.prefix.hotfix", out var hotfix)) - gitFlow.Hotfix = hotfix; - Dispatcher.UIThread.Invoke(() => - { - GitFlow = gitFlow; - }); - } - public void CreateNewBranch() { var current = Branches.Find(x => x.IsCurrent); @@ -797,7 +773,8 @@ namespace SourceGit.ViewModels var menu = new ContextMenu(); menu.Placement = PlacementMode.BottomEdgeAlignedLeft; - if (GitFlow.IsEnabled) + var isGitFlowEnabled = Commands.GitFlow.IsEnabled(_fullpath, _branches); + if (isGitFlowEnabled) { var startFeature = new MenuItem(); startFeature.Header = App.Text("GitFlow.StartFeature"); @@ -805,7 +782,7 @@ namespace SourceGit.ViewModels startFeature.Click += (o, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Feature)); + PopupHost.ShowPopup(new GitFlowStart(this, "feature")); e.Handled = true; }; @@ -815,7 +792,7 @@ namespace SourceGit.ViewModels startRelease.Click += (o, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Release)); + PopupHost.ShowPopup(new GitFlowStart(this, "release")); e.Handled = true; }; @@ -825,7 +802,7 @@ namespace SourceGit.ViewModels startHotfix.Click += (o, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Hotfix)); + PopupHost.ShowPopup(new GitFlowStart(this, "hotfix")); e.Handled = true; }; @@ -909,6 +886,13 @@ namespace SourceGit.ViewModels } menu.Items.Add(push); + + var compareWithBranch = CreateMenuItemToCompareBranches(branch); + if (compareWithBranch != null) + { + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(compareWithBranch); + } } else { @@ -968,24 +952,6 @@ namespace SourceGit.ViewModels menu.Items.Add(merge); menu.Items.Add(rebase); - var compare = new MenuItem(); - compare.Header = App.Text("BranchCM.CompareWithHead"); - compare.Icon = App.CreateMenuIcon("Icons.Compare"); - compare.Click += (o, e) => - { - SearchResultSelectedCommit = null; - - if (_histories != null) - { - var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result(); - var head = new Commands.QuerySingleCommit(FullPath, current.Head).Result(); - _histories.AutoSelectedCommit = null; - _histories.DetailContext = new RevisionCompare(FullPath, target, head); - } - - e.Handled = true; - }; - if (WorkingCopyChangesCount > 0) { var compareWithWorktree = new MenuItem(); @@ -1002,15 +968,22 @@ namespace SourceGit.ViewModels _histories.DetailContext = new RevisionCompare(FullPath, target, null); } }; + menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(compareWithWorktree); } - menu.Items.Add(new MenuItem() { Header = "-" }); - menu.Items.Add(compare); + var compareWithBranch = CreateMenuItemToCompareBranches(branch); + if (compareWithBranch != null) + { + if (WorkingCopyChangesCount == 0) + menu.Items.Add(new MenuItem() { Header = "-" }); + + menu.Items.Add(compareWithBranch); + } } - var type = GitFlow.GetBranchType(branch.Name); - if (type != Models.GitFlowBranchType.None) + var detect = Commands.GitFlow.DetectType(_fullpath, _branches, branch.Name); + if (detect.IsGitFlowBranch) { var finish = new MenuItem(); finish.Header = new Views.NameHighlightedTextBlock("BranchCM.Finish", branch.Name); @@ -1018,7 +991,7 @@ namespace SourceGit.ViewModels finish.Click += (o, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new GitFlowFinish(this, branch, type)); + PopupHost.ShowPopup(new GitFlowFinish(this, branch, detect.Type, detect.Prefix)); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); @@ -1263,51 +1236,39 @@ namespace SourceGit.ViewModels menu.Items.Add(merge); menu.Items.Add(rebase); menu.Items.Add(new MenuItem() { Header = "-" }); - - if (current.Head != branch.Head) - { - var compare = new MenuItem(); - compare.Header = App.Text("BranchCM.CompareWithHead"); - compare.Icon = App.CreateMenuIcon("Icons.Compare"); - compare.Click += (o, e) => - { - SearchResultSelectedCommit = null; - - if (_histories != null) - { - var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result(); - var head = new Commands.QuerySingleCommit(FullPath, current.Head).Result(); - _histories.AutoSelectedCommit = null; - _histories.DetailContext = new RevisionCompare(FullPath, target, head); - } - - e.Handled = true; - }; - menu.Items.Add(compare); - - if (WorkingCopyChangesCount > 0) - { - var compareWithWorktree = new MenuItem(); - compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree"); - compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare"); - compareWithWorktree.Click += (o, e) => - { - SearchResultSelectedCommit = null; - - if (_histories != null) - { - var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result(); - _histories.AutoSelectedCommit = null; - _histories.DetailContext = new RevisionCompare(FullPath, target, null); - } - }; - menu.Items.Add(compareWithWorktree); - } - - menu.Items.Add(new MenuItem() { Header = "-" }); - } } + var hasCompare = false; + if (WorkingCopyChangesCount > 0) + { + var compareWithWorktree = new MenuItem(); + compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree"); + compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare"); + compareWithWorktree.Click += (o, e) => + { + SearchResultSelectedCommit = null; + + if (_histories != null) + { + var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result(); + _histories.AutoSelectedCommit = null; + _histories.DetailContext = new RevisionCompare(FullPath, target, null); + } + }; + menu.Items.Add(compareWithWorktree); + hasCompare = true; + } + + var compareWithBranch = CreateMenuItemToCompareBranches(branch); + if (compareWithBranch != null) + { + menu.Items.Add(compareWithBranch); + hasCompare = true; + } + + if (hasCompare) + menu.Items.Add(new MenuItem() { Header = "-" }); + var delete = new MenuItem(); delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", $"{branch.Remote}/{branch.Name}"); delete.Icon = App.CreateMenuIcon("Icons.Clear"); @@ -1485,6 +1446,41 @@ namespace SourceGit.ViewModels return menu; } + private MenuItem CreateMenuItemToCompareBranches(Models.Branch branch) + { + if (Branches.Count == 1) + return null; + + var compare = new MenuItem(); + compare.Header = App.Text("BranchCM.CompareWithBranch"); + compare.Icon = App.CreateMenuIcon("Icons.Compare"); + + foreach (var b in Branches) + { + if (b.FullName != branch.FullName) + { + var dup = b; + var target = new MenuItem(); + target.Header = b.IsLocal ? b.Name : $"{b.Remote}/{b.Name}"; + target.Icon = App.CreateMenuIcon(b.IsCurrent ? "Icons.Check" : "Icons.Branch"); + target.Click += (_, e) => + { + var wnd = new Views.BranchCompare() + { + DataContext = new BranchCompare(FullPath, branch, dup) + }; + + wnd.Show(App.GetTopLevel() as Window); + e.Handled = true; + }; + + compare.Items.Add(target); + } + } + + return compare; + } + private BranchTreeNode.Builder BuildBranchTree(List branches, List remotes) { var builder = new BranchTreeNode.Builder(); @@ -1513,7 +1509,6 @@ namespace SourceGit.ViewModels private string _fullpath = string.Empty; private string _gitDir = string.Empty; - private Models.GitFlow _gitflow = new Models.GitFlow(); private Models.Watcher _watcher = null; private Histories _histories = null; diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index 7c5ad88a..47b1bd06 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -24,7 +24,7 @@ namespace SourceGit.ViewModels _repo = repo; _oldMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, head.SHA).Result(); _message = _oldMessage; - + Head = head; View = new Views.Reword() { DataContext = this }; } diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index 6ada9318..c001dfab 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -28,7 +28,7 @@ namespace SourceGit.ViewModels { _repo = repo; _message = new Commands.QueryCommitFullMessage(_repo.FullPath, parent.SHA).Result(); - + Head = head; Parent = parent; View = new Views.Squash() { DataContext = this }; diff --git a/src/ViewModels/TwoSideTextDiff.cs b/src/ViewModels/TwoSideTextDiff.cs index ad1b5478..8d2aedcc 100644 --- a/src/ViewModels/TwoSideTextDiff.cs +++ b/src/ViewModels/TwoSideTextDiff.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; - +using Avalonia; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -11,7 +11,13 @@ namespace SourceGit.ViewModels public List New { get; set; } = new List(); public int MaxLineNumber = 0; - public TwoSideTextDiff(Models.TextDiff diff) + public Vector SyncScrollOffset + { + get => _syncScrollOffset; + set => SetProperty(ref _syncScrollOffset, value); + } + + public TwoSideTextDiff(Models.TextDiff diff, TwoSideTextDiff previous = null) { File = diff.File; MaxLineNumber = diff.MaxLineNumber; @@ -35,6 +41,9 @@ namespace SourceGit.ViewModels } FillEmptyLines(); + + if (previous != null && previous.File == File) + _syncScrollOffset = previous._syncScrollOffset; } private void FillEmptyLines() @@ -52,5 +61,7 @@ namespace SourceGit.ViewModels New.Add(new Models.TextDiffLine()); } } + + private Vector _syncScrollOffset = Vector.Zero; } } diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 16dab612..464465d7 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -379,24 +379,16 @@ namespace SourceGit.ViewModels if (isUnstaged) { if (changes.Count == _unstaged.Count && _staged.Count == 0) - { PopupHost.ShowPopup(new Discard(_repo)); - } else - { PopupHost.ShowPopup(new Discard(_repo, changes, true)); - } } else { if (changes.Count == _staged.Count && _unstaged.Count == 0) - { PopupHost.ShowPopup(new Discard(_repo)); - } else - { PopupHost.ShowPopup(new Discard(_repo, changes, false)); - } } } } @@ -413,7 +405,7 @@ namespace SourceGit.ViewModels public ContextMenu CreateContextMenuForUnstagedChanges() { - if (_selectedUnstaged.Count == 0) + if (_selectedUnstaged == null || _selectedUnstaged.Count == 0) return null; var menu = new ContextMenu(); @@ -535,6 +527,16 @@ namespace SourceGit.ViewModels e.Handled = true; }; + + var assumeUnchanged = new MenuItem(); + assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged"); + assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore"); + assumeUnchanged.IsVisible = change.WorkTree != Models.ChangeState.Untracked; + assumeUnchanged.Click += (_, e) => + { + new Commands.AssumeUnchanged(_repo.FullPath).Add(change.Path); + e.Handled = true; + }; var history = new MenuItem(); history.Header = App.Text("FileHistory"); @@ -546,24 +548,67 @@ namespace SourceGit.ViewModels e.Handled = true; }; - var assumeUnchanged = new MenuItem(); - assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged"); - assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore"); - assumeUnchanged.IsEnabled = change.WorkTree != Models.ChangeState.Untracked; - assumeUnchanged.Click += (_, e) => - { - new Commands.AssumeUnchanged(_repo.FullPath).Add(change.Path); - e.Handled = true; - }; - menu.Items.Add(stage); menu.Items.Add(discard); menu.Items.Add(stash); menu.Items.Add(patch); - menu.Items.Add(new MenuItem() { Header = "-" }); - menu.Items.Add(history); menu.Items.Add(assumeUnchanged); menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(history); + menu.Items.Add(new MenuItem() { Header = "-" }); + + if (change.WorkTree == Models.ChangeState.Untracked) + { + var isRooted = change.Path.IndexOf('/', StringComparison.Ordinal) <= 0; + var addToIgnore = new MenuItem(); + addToIgnore.Header = App.Text("WorkingCopy.AddToGitIgnore"); + addToIgnore.Icon = App.CreateMenuIcon("Icons.GitIgnore"); + + var singleFile = new MenuItem(); + singleFile.Header = App.Text("WorkingCopy.AddToGitIgnore.SingleFile"); + singleFile.Click += (_, e) => + { + Commands.GitIgnore.Add(_repo.FullPath, change.Path); + e.Handled = true; + }; + addToIgnore.Items.Add(singleFile); + + var byParentFolder = new MenuItem(); + byParentFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InSameFolder"); + byParentFolder.IsVisible = !isRooted; + byParentFolder.Click += (_, e) => + { + Commands.GitIgnore.Add(_repo.FullPath, Path.GetDirectoryName(change.Path) + "/"); + e.Handled = true; + }; + addToIgnore.Items.Add(byParentFolder); + + var extension = Path.GetExtension(change.Path); + if (!string.IsNullOrEmpty(extension)) + { + var byExtension = new MenuItem(); + byExtension.Header = App.Text("WorkingCopy.AddToGitIgnore.Extension", extension); + byExtension.Click += (_, e) => + { + Commands.GitIgnore.Add(_repo.FullPath, "*" + extension); + e.Handled = true; + }; + addToIgnore.Items.Add(byExtension); + + var byExtensionInSameFolder = new MenuItem(); + byExtensionInSameFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.ExtensionInSameFolder", extension); + byExtensionInSameFolder.IsVisible = !isRooted; + byExtensionInSameFolder.Click += (_, e) => + { + Commands.GitIgnore.Add(_repo.FullPath, Path.GetDirectoryName(change.Path) + "/*" + extension); + e.Handled = true; + }; + addToIgnore.Items.Add(byExtensionInSameFolder); + } + + menu.Items.Add(addToIgnore); + menu.Items.Add(new MenuItem() { Header = "-" }); + } } var copy = new MenuItem(); @@ -699,7 +744,7 @@ namespace SourceGit.ViewModels public ContextMenu CreateContextMenuForStagedChanges() { - if (_selectedStaged.Count == 0) + if (_selectedStaged == null || _selectedStaged.Count == 0) return null; var menu = new ContextMenu(); @@ -921,24 +966,11 @@ namespace SourceGit.ViewModels var isUnstaged = _selectedUnstaged != null && _selectedUnstaged.Count > 0; if (change == null) - { DetailContext = null; - } else if (change.IsConflit && isUnstaged) - { DetailContext = new ConflictContext(_repo.FullPath, change); - } else - { - if (_detailContext is DiffContext previous) - { - DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), previous); - } - else - { - DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged)); - } - } + DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), _detailContext as DiffContext); } private async void UseTheirs(List changes) diff --git a/src/Views/About.axaml b/src/Views/About.axaml index 98ba444b..8c308790 100644 --- a/src/Views/About.axaml +++ b/src/Views/About.axaml @@ -1,35 +1,25 @@ - - - - - + + - + - + - + @@ -91,7 +81,7 @@ - + @@ -100,4 +90,4 @@ - + diff --git a/src/Views/About.axaml.cs b/src/Views/About.axaml.cs index 810f5205..bd98199d 100644 --- a/src/Views/About.axaml.cs +++ b/src/Views/About.axaml.cs @@ -1,12 +1,11 @@ using System.Reflection; -using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views { - public partial class About : Window + public partial class About : ChromelessWindow { public string Version { diff --git a/src/Views/Archive.axaml b/src/Views/Archive.axaml index 8cb89d49..5dc79018 100644 --- a/src/Views/Archive.axaml +++ b/src/Views/Archive.axaml @@ -28,11 +28,11 @@ - - - - - + + + + + diff --git a/src/Views/AssumeUnchangedManager.axaml b/src/Views/AssumeUnchangedManager.axaml index 6d8b2dc1..5419ff9d 100644 --- a/src/Views/AssumeUnchangedManager.axaml +++ b/src/Views/AssumeUnchangedManager.axaml @@ -1,29 +1,19 @@ - - - - - + + - + - + diff --git a/src/Views/AssumeUnchangedManager.axaml.cs b/src/Views/AssumeUnchangedManager.axaml.cs index 6db53f42..ef9c5a86 100644 --- a/src/Views/AssumeUnchangedManager.axaml.cs +++ b/src/Views/AssumeUnchangedManager.axaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace SourceGit.Views { - public partial class AssumeUnchangedManager : Window + public partial class AssumeUnchangedManager : ChromelessWindow { public AssumeUnchangedManager() { diff --git a/src/Views/Blame.axaml b/src/Views/Blame.axaml index 3befbadb..a7e850fb 100644 --- a/src/Views/Blame.axaml +++ b/src/Views/Blame.axaml @@ -1,36 +1,26 @@ - - + + - - - @@ -38,7 +28,9 @@ Background="{DynamicResource Brush.TitleBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}" DoubleTapped="MaximizeOrRestoreWindow" - PointerPressed="BeginMoveWindow"/> + PointerPressed="BeginMoveWindow" + PointerMoved="MoveWindow" + PointerReleased="EndMoveWindow"/> @@ -58,12 +50,12 @@ - + - + - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index 4acf5dc0..d4c2da28 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -319,7 +319,7 @@ namespace SourceGit.Views private TextMate.Installation _textMate = null; } - public partial class Blame : Window + public partial class Blame : ChromelessWindow { public Blame() { @@ -333,31 +333,41 @@ namespace SourceGit.Views private void MaximizeOrRestoreWindow(object sender, TappedEventArgs e) { - if (WindowState == WindowState.Maximized) - { - WindowState = WindowState.Normal; - } - else - { - WindowState = WindowState.Maximized; - } - e.Handled = true; - } + _pressedTitleBar = false; - private void CustomResizeWindow(object sender, PointerPressedEventArgs e) - { - if (sender is Border border) - { - if (border.Tag is WindowEdge edge) - { - BeginResizeDrag(edge, e); - } - } + if (WindowState == WindowState.Maximized) + WindowState = WindowState.Normal; + else + WindowState = WindowState.Maximized; + + e.Handled = true; } private void BeginMoveWindow(object sender, PointerPressedEventArgs e) { - BeginMoveDrag(e); + if (e.ClickCount != 2) + _pressedTitleBar = true; + } + + private void MoveWindow(object sender, PointerEventArgs e) + { + if (!_pressedTitleBar) + return; + + var visual = (Visual)e.Source; + BeginMoveDrag(new PointerPressedEventArgs( + e.Source, + e.Pointer, + visual, + e.GetPosition(visual), + e.Timestamp, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), + e.KeyModifiers)); + } + + private void EndMoveWindow(object sender, PointerReleasedEventArgs e) + { + _pressedTitleBar = false; } protected override void OnClosed(EventArgs e) @@ -375,5 +385,7 @@ namespace SourceGit.Views } e.Handled = true; } + + private bool _pressedTitleBar = false; } } diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml new file mode 100644 index 00000000..9ad7db03 --- /dev/null +++ b/src/Views/BranchCompare.axaml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/BranchCompare.axaml.cs b/src/Views/BranchCompare.axaml.cs new file mode 100644 index 00000000..280cfc6a --- /dev/null +++ b/src/Views/BranchCompare.axaml.cs @@ -0,0 +1,74 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; + +namespace SourceGit.Views +{ + public partial class BranchCompare : ChromelessWindow + { + public BranchCompare() + { + InitializeComponent(); + } + + private void MaximizeOrRestoreWindow(object sender, TappedEventArgs e) + { + _pressedTitleBar = false; + + if (WindowState == WindowState.Maximized) + WindowState = WindowState.Normal; + else + WindowState = WindowState.Maximized; + + e.Handled = true; + } + + private void BeginMoveWindow(object sender, PointerPressedEventArgs e) + { + if (e.ClickCount != 2) + _pressedTitleBar = true; + } + + private void MoveWindow(object sender, PointerEventArgs e) + { + if (!_pressedTitleBar) + return; + + var visual = (Visual)e.Source; + BeginMoveDrag(new PointerPressedEventArgs( + e.Source, + e.Pointer, + visual, + e.GetPosition(visual), + e.Timestamp, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), + e.KeyModifiers)); + } + + private void EndMoveWindow(object sender, PointerReleasedEventArgs e) + { + _pressedTitleBar = false; + } + + private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) + { + if (DataContext is ViewModels.BranchCompare vm && sender is ChangeCollectionView view) + { + var menu = vm.CreateChangeContextMenu(); + view.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private void OnPressedSHA(object sender, PointerPressedEventArgs e) + { + if (DataContext is ViewModels.BranchCompare vm && sender is TextBlock block) + vm.NavigateTo(block.Text); + + e.Handled = true; + } + + private bool _pressedTitleBar = false; + } +} diff --git a/src/Views/CheckoutCommit.axaml b/src/Views/CheckoutCommit.axaml index f73e2041..679299d3 100644 --- a/src/Views/CheckoutCommit.axaml +++ b/src/Views/CheckoutCommit.axaml @@ -11,23 +11,25 @@ - - - + + + + + + - - - - - + + + + + - + - - - - - + + + + + typeof(Window); + + public ChromelessWindow() + { + if (OperatingSystem.IsLinux()) + Classes.Add("custom_window_frame"); + else if (OperatingSystem.IsWindows()) + Classes.Add("fix_maximized_padding"); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + if (Classes.Contains("custom_window_frame") && CanResize) + { + string[] borderNames = [ + "PART_BorderTopLeft", + "PART_BorderTop", + "PART_BorderTopRight", + "PART_BorderLeft", + "PART_BorderRight", + "PART_BorderBottomLeft", + "PART_BorderBottom", + "PART_BorderBottomRight", + ]; + + foreach (var name in borderNames) + { + var border = e.NameScope.Find(name); + if (border != null) + { + border.PointerPressed -= OnWindowBorderPointerPressed; + border.PointerPressed += OnWindowBorderPointerPressed; + } + } + } + } + + private void OnWindowBorderPointerPressed(object sender, PointerPressedEventArgs e) + { + if (sender is Border border && border.Tag is WindowEdge edge && CanResize) + BeginResizeDrag(edge, e); + } + } +} diff --git a/src/Views/Clone.axaml b/src/Views/Clone.axaml index 47dca098..25c46a00 100644 --- a/src/Views/Clone.axaml +++ b/src/Views/Clone.axaml @@ -10,46 +10,71 @@ - - - - - - - + + + + + + Text="{Binding SSHKey, Mode=TwoWay}" + IsVisible="{Binding UseSSH}"> - - - - - + - - - - - - - - - + + + + + + diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index eea7921c..3c8a9ea5 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -14,7 +14,7 @@ namespace SourceGit.Views get => GetValue(CanNavigateProperty); set => SetValue(CanNavigateProperty, value); } - + public static readonly StyledProperty MessageProperty = AvaloniaProperty.Register(nameof(Message), string.Empty); diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 8dd585bd..fffb3bd3 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -10,7 +10,7 @@ x:DataType="vm:CommitDetail"> - + diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml index b8d5a1d0..dd204b18 100644 --- a/src/Views/CreateBranch.axaml +++ b/src/Views/CreateBranch.axaml @@ -29,11 +29,11 @@ - - - - - + + + + + diff --git a/src/Views/CreateTag.axaml b/src/Views/CreateTag.axaml index a896a344..fa42c9f2 100644 --- a/src/Views/CreateTag.axaml +++ b/src/Views/CreateTag.axaml @@ -13,7 +13,7 @@ - + - - - - - + + + + + diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 081abdf0..027a2bb3 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -234,9 +234,7 @@ - + diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml index a52445f6..bcbc02b0 100644 --- a/src/Views/FileHistories.axaml +++ b/src/Views/FileHistories.axaml @@ -1,34 +1,24 @@ - - + + - - - @@ -36,7 +26,9 @@ Background="{DynamicResource Brush.TitleBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}" DoubleTapped="MaximizeOrRestoreWindow" - PointerPressed="BeginMoveWindow"/> + PointerPressed="BeginMoveWindow" + PointerMoved="MoveWindow" + PointerReleased="EndMoveWindow"/> @@ -56,7 +48,7 @@ - + @@ -143,64 +135,5 @@ - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Views/FileHistories.axaml.cs b/src/Views/FileHistories.axaml.cs index 0e5968ff..51570310 100644 --- a/src/Views/FileHistories.axaml.cs +++ b/src/Views/FileHistories.axaml.cs @@ -1,9 +1,10 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Input; namespace SourceGit.Views { - public partial class FileHistories : Window + public partial class FileHistories : ChromelessWindow { public FileHistories() { @@ -12,31 +13,43 @@ namespace SourceGit.Views private void MaximizeOrRestoreWindow(object sender, TappedEventArgs e) { - if (WindowState == WindowState.Maximized) - { - WindowState = WindowState.Normal; - } - else - { - WindowState = WindowState.Maximized; - } - e.Handled = true; - } + _pressedTitleBar = false; - private void CustomResizeWindow(object sender, PointerPressedEventArgs e) - { - if (sender is Border border) - { - if (border.Tag is WindowEdge edge) - { - BeginResizeDrag(edge, e); - } - } + if (WindowState == WindowState.Maximized) + WindowState = WindowState.Normal; + else + WindowState = WindowState.Maximized; + + e.Handled = true; } private void BeginMoveWindow(object sender, PointerPressedEventArgs e) { - BeginMoveDrag(e); + if (e.ClickCount != 2) + _pressedTitleBar = true; } + + private void MoveWindow(object sender, PointerEventArgs e) + { + if (!_pressedTitleBar) + return; + + var visual = (Visual)e.Source; + BeginMoveDrag(new PointerPressedEventArgs( + e.Source, + e.Pointer, + visual, + e.GetPosition(visual), + e.Timestamp, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), + e.KeyModifiers)); + } + + private void EndMoveWindow(object sender, PointerReleasedEventArgs e) + { + _pressedTitleBar = false; + } + + private bool _pressedTitleBar = false; } } diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index fb067d15..bf633bff 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -1,27 +1,18 @@ - - - - - + + - + - + @@ -109,8 +100,14 @@ - - + + + + + + + + - + diff --git a/src/Views/Hotkeys.axaml.cs b/src/Views/Hotkeys.axaml.cs index c255a54a..e318d0d2 100644 --- a/src/Views/Hotkeys.axaml.cs +++ b/src/Views/Hotkeys.axaml.cs @@ -1,10 +1,9 @@ -using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views { - public partial class Hotkeys : Window + public partial class Hotkeys : ChromelessWindow { public Hotkeys() { diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 16e242bc..255d57bd 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -1,48 +1,39 @@ - - - - - - + + - + - - - + DoubleTapped="OnTitleBarDoubleTapped" + PointerPressed="BeginMoveWindow" + PointerMoved="MoveWindow" + PointerReleased="EndMoveWindow"/> @@ -92,10 +83,9 @@ @@ -260,62 +250,46 @@ - + - - + + - - - - - + + + + - - - - - - - - - - - - - - -