Merge branch 'release/v8.17'

This commit is contained in:
leo 2024-06-17 09:14:22 +08:00
commit 5a1827fdcb
No known key found for this signature in database
GPG key ID: B528468E49CD0E58
73 changed files with 2069 additions and 1544 deletions

View file

@ -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.

View file

@ -1 +1 @@
8.16
8.17

View file

@ -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

View file

@ -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<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version)
public static bool IsEnabled(string repo, List<Models.Branch> branches)
{
var localBrancheNames = new HashSet<string>();
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<Models.Branch> 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<Models.Branch> branches, string branch)
{
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(() =>
var rs = new BranchDetectResult();
var localBrancheNames = new HashSet<string>();
foreach (var b in branches)
{
App.RaiseException(Context, "Bad branch type!!!");
if (b.IsLocal)
localBrancheNames.Add(b.Name);
}
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 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;
}
return Exec();
var start = new Command();
start.WorkingDirectory = repo;
start.Context = repo;
start.Args = $"flow {type} start {name}";
return start.Exec();
}
public bool Finish(Models.GitFlowBranchType type, string name, bool keepBranch)
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;
var finish = new Command();
finish.WorkingDirectory = repo;
finish.Context = repo;
finish.Args = $"flow {type} finish {option} {name}";
return finish.Exec();
}
return Exec();
}
private static readonly List<string> SUPPORTED_BRANCH_TYPES = new List<string>()
{
"feature",
"release",
"bugfix",
"hotfix",
"support",
};
}
}

16
src/Commands/GitIgnore.cs Normal file
View file

@ -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 ]);
}
}
}

View file

@ -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;
}
}

View file

@ -63,6 +63,9 @@ namespace SourceGit.Commands
end = rs.StdOut.IndexOf('\n', start);
}
if (start < rs.StdOut.Length)
_current.Subject = rs.StdOut.Substring(start);
if (_findFirstMerged && !_isHeadFounded && _commits.Count > 0)
MarkFirstMerged();

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SourceGit.Commands
{

View file

@ -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<WindowState, Thickness> ToContentMargin =
new FuncValueConverter<WindowState, Thickness>(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<WindowState, GridLength> ToTitleBarHeight =
new FuncValueConverter<WindowState, GridLength>(state =>
{
if (state == WindowState.Maximized)
return new GridLength(OperatingSystem.IsMacOS() ? 34 : 30);
else
return new GridLength(38);
});
public static readonly FuncValueConverter<WindowState, bool> IsNormal =
new FuncValueConverter<WindowState, bool>(state => state == WindowState.Normal);
}
}

View file

@ -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;
}
}
}

View file

@ -98,4 +98,6 @@
<StreamGeometry x:Key="Icons.Lines.Decr">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Compare">M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z</StreamGeometry>
<StreamGeometry x:Key="Icons.WordWrap">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Detached">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</StreamGeometry>
<StreamGeometry x:Key="Icons.GitIgnore">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</StreamGeometry>
</ResourceDictionary>

View file

@ -33,6 +33,7 @@
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME ON THIS FILE IS NOT SUPPORTED!!!</x:String>
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout${0}$</x:String>
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">Compare with Branch</x:String>
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Compare with HEAD</x:String>
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Compare with Worktree</x:String>
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Copy Branch Name</x:String>
@ -49,6 +50,7 @@
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Rename${0}$</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Tracking ...</x:String>
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">Unset Upstream</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Branch Compare</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
@ -241,6 +243,8 @@
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Create new page</x:String>
<x:String x:Key="Text.Hotkeys.Global.OpenPreference" xml:space="preserve">Open preference dialog</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">REPOSITORY</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Commit" xml:space="preserve">Commit staged changes</x:String>
<x:String x:Key="Text.Hotkeys.Repo.CommitAndPush" xml:space="preserve">Commit and push staged changes</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">Force to reload this repository</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Stage/Unstage selected changes</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Open commit search</x:String>
@ -473,6 +477,11 @@
<x:String x:Key="Text.Welcome.Search" xml:space="preserve">Search Repositories ...</x:String>
<x:String x:Key="Text.Welcome.Sort" xml:space="preserve">Sort</x:String>
<x:String x:Key="Text.WorkingCopy" xml:space="preserve">Changes</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore" xml:space="preserve">Add To .gitignore ...</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.Extension" xml:space="preserve">Ignore all *{0} files</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.ExtensionInSameFolder" xml:space="preserve">Ignore *{0} files in the same folder</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">Ignore files in the same folder</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignore this file only</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>

View file

@ -36,6 +36,7 @@
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">选中文件不支持该操作!!!</x:String>
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">检出(checkout)${0}$</x:String>
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">与其他分支对比</x:String>
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">与当前HEAD比较</x:String>
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">与本地工作树比较</x:String>
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">复制分支名</x:String>
@ -52,6 +53,7 @@
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重命名${0}$</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切换上游分支...</x:String>
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">取消追踪</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比较</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">字节</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
@ -244,6 +246,8 @@
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新建页面</x:String>
<x:String x:Key="Text.Hotkeys.Global.OpenPreference" xml:space="preserve">打开偏好设置面板</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">仓库页面快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Commit" xml:space="preserve">提交暂存区更改</x:String>
<x:String x:Key="Text.Hotkeys.Repo.CommitAndPush" xml:space="preserve">提交暂存区更改并推送</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">重新加载仓库状态</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">将选中的变更暂存或从暂存列表中移除</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">打开历史搜索</x:String>
@ -476,6 +480,11 @@
<x:String x:Key="Text.Welcome.Search" xml:space="preserve">快速查找仓库...</x:String>
<x:String x:Key="Text.Welcome.Sort" xml:space="preserve">排序</x:String>
<x:String x:Key="Text.WorkingCopy" xml:space="preserve">本地更改</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore" xml:space="preserve">添加至 .gitignore 忽略列表 ...</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.Extension" xml:space="preserve">忽略所有 *{0} 文件</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.ExtensionInSameFolder" xml:space="preserve">忽略同目录下所有 *{0} 文件</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同目录下所有文件</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本文件</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>

View file

@ -36,6 +36,7 @@
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">選中檔案不支援該操作!!!</x:String>
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">檢出(checkout)${0}$</x:String>
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">與其他分支比較</x:String>
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">與當前HEAD比較</x:String>
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">與本地工作樹比較</x:String>
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">複製分支名</x:String>
@ -52,6 +53,7 @@
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重新命名${0}$</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切換上游分支...</x:String>
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">取消追蹤</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比較</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">位元組</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
@ -244,6 +246,8 @@
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新建頁面</x:String>
<x:String x:Key="Text.Hotkeys.Global.OpenPreference" xml:space="preserve">開啟偏好設定面板</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">倉庫頁面快捷鍵</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Commit" xml:space="preserve">提交暫存區變更</x:String>
<x:String x:Key="Text.Hotkeys.Repo.CommitAndPush" xml:space="preserve">提交暫存區變更併推送</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">重新載入倉庫狀態</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">將選中的變更暫存或從暫存列表中移除</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">開啟歷史搜尋</x:String>
@ -476,6 +480,11 @@
<x:String x:Key="Text.Welcome.Search" xml:space="preserve">快速查詢倉庫...</x:String>
<x:String x:Key="Text.Welcome.Sort" xml:space="preserve">排序</x:String>
<x:String x:Key="Text.WorkingCopy" xml:space="preserve">本地更改</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore" xml:space="preserve">添加至 .gitignore 忽略清單 ...</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.Extension" xml:space="preserve">忽略所有 *{0} 檔案</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.ExtensionInSameFolder" xml:space="preserve">忽略同路徑下所有 *{0} 檔案</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同路徑下所有檔案</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本檔案</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修補(--amend)</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">現在您已可將其加入暫存區中</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>

View file

@ -18,6 +18,143 @@
<Setter Property="HideDelay" Value="0:0:0.2"/>
</Style>
<Style Selector="Window">
<Setter Property="Background" Value="{DynamicResource Brush.Window}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border0}"/>
<Setter Property="ExtendClientAreaChromeHints" Value="NoChrome"/>
<Setter Property="ExtendClientAreaToDecorationsHint" Value="True"/>
<Setter Property="SystemDecorations" Value="Full"/>
<Setter Property="Padding" Value="0"/>
<Style.Resources>
<SolidColorBrush x:Key="SystemControlErrorTextForegroundBrush" Color="Red"/>
<SolidColorBrush x:Key="SystemErrorTextColor" Color="Red"/>
</Style.Resources>
</Style>
<Style Selector="Window[WindowState=Maximized]">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
</Style>
<Style Selector="Window[WindowState=Maximized].fix_maximized_padding">
<Setter Property="Padding" Value="6"/>
</Style>
<Style Selector="Window.custom_window_frame">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="SystemDecorations" Value="None"/>
<Setter Property="Padding" Value="12"/>
<Setter Property="Template">
<ControlTemplate>
<Grid>
<Border x:Name="PART_BorderTopLeft"
Classes="resize_border"
Width="12" Height="12"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Top"
Cursor="TopLeftCorner"
Tag="{x:Static WindowEdge.NorthWest}"/>
<Border x:Name="PART_BorderTop"
Classes="resize_border"
Height="12" Margin="12,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
Cursor="TopSide"
Tag="{x:Static WindowEdge.North}"/>
<Border x:Name="PART_BorderTopRight"
Classes="resize_border"
Width="12" Height="12"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Top"
Cursor="TopRightCorner"
Tag="{x:Static WindowEdge.NorthEast}"/>
<Border x:Name="PART_BorderLeft"
Classes="resize_border"
Width="12" Margin="0,12"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Cursor="LeftSide"
Tag="{x:Static WindowEdge.West}"/>
<Border x:Name="PART_BorderRight"
Classes="resize_border"
Width="12" Margin="0,12"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Stretch"
Cursor="RightSide"
Tag="{x:Static WindowEdge.East}"/>
<Border x:Name="PART_BorderBottomLeft"
Classes="resize_border"
Width="12" Height="12"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Cursor="BottomLeftCorner"
Tag="{x:Static WindowEdge.SouthWest}"/>
<Border x:Name="PART_BorderBottom"
Classes="resize_border"
Height="12" Margin="12,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Cursor="BottomSide"
Tag="{x:Static WindowEdge.South}"/>
<Border x:Name="PART_BorderBottomRight"
Classes="resize_border"
Width="12" Height="12"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Cursor="BottomRightCorner"
Tag="{x:Static WindowEdge.SouthEast}"/>
<Grid Margin="{TemplateBinding Padding}" Effect="drop-shadow(0 0 12 #A0000000)">
<Border x:Name="PART_ContentRoot"
Background="{DynamicResource Brush.Window}"
BorderBrush="{DynamicResource Brush.WindowBorder}"
BorderThickness="1"
CornerRadius="8">
<VisualLayerManager>
<Border CornerRadius="8" ClipToBounds="True">
<ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</VisualLayerManager>
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Window.custom_window_frame[WindowState=Maximized]">
<Setter Property="Padding" Value="0"/>
</Style>
<Style Selector="Window.custom_window_frame[WindowState=Maximized] /template/ Border#PART_ContentRoot">
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style Selector="Window.custom_window_frame[WindowState=Maximized] /template/ Border.resize_border">
<Setter Property="IsVisible" Value="False"/>
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
<Style Selector="Window.custom_window_frame[CanResize=False] /template/ Border.resize_border">
<Setter Property="IsVisible" Value="False"/>
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
<Style Selector="ContentPresenter">
<Setter Property="FontFamily" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFont}"/>
<Setter Property="FontSize" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize}"/>
@ -1163,4 +1300,22 @@
<Style Selector="TreeDataGridExpanderCell[IsExpanded=False] Path.folder_icon">
<Setter Property="Data" Value="{StaticResource Icons.Folder.Fill}"/>
</Style>
<Style Selector="NumericUpDown">
<Style Selector="^ /template/ ButtonSpinner#PART_Spinner">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
</Style>
<Style Selector="^ /template/ TextBox#PART_TextBox">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<Style Selector="^:focus-within /template/ ButtonSpinner#PART_Spinner">
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Accent}"/>
</Style>
<Style Selector="^ /template/ TextBox#PART_TextBox /template/ Border#PART_BorderElement">
<Setter Property="IsVisible" Value="False"/>
</Style>
</Style>
</Styles>

View file

@ -6,6 +6,7 @@
<Color x:Key="Color.MacOS.Minimize">#FFFFBE2F</Color>
<Color x:Key="Color.MacOS.Maximize">#FF29c941</Color>
<Color x:Key="Color.Window">#FFF0F5F9</Color>
<Color x:Key="Color.WindowBorder">#FFAFAFAF</Color>
<Color x:Key="Color.TitleBar">#FFCFDEEA</Color>
<Color x:Key="Color.ToolBar">#FFF0F5F9</Color>
<Color x:Key="Color.Popup">#FFF8F8F8</Color>
@ -14,22 +15,21 @@
<Color x:Key="Color.Decorator">#FF6F6F6F</Color>
<Color x:Key="Color.DecoratorIcon">#FFF8F8F8</Color>
<Color x:Key="Color.Conflict">#FF836C2E</Color>
<Color x:Key="Color.ConflictForeground">#FFFFFFFF</Color>
<Color x:Key="Color.Border0">#FFCFCFCF</Color>
<Color x:Key="Color.Border1">#FF898989</Color>
<Color x:Key="Color.Border2">#FFCFCFCF</Color>
<Color x:Key="Color.Border3">#FFEFEFEF</Color>
<Color x:Key="Color.FlatButton.Background">#FFF8F8F8</Color>
<Color x:Key="Color.FlatButton.BackgroundHovered">White</Color>
<Color x:Key="Color.FlatButton.PrimaryBackground">#FF4295FF</Color>
<Color x:Key="Color.FlatButton.PrimaryBackgroundHovered">#FF529DFB</Color>
<Color x:Key="Color.FG1">#FF1F1F1F</Color>
<Color x:Key="Color.FG2">#FF6F6F6F</Color>
<Color x:Key="Color.FG3">#FFFFFFFF</Color>
<Color x:Key="Color.TextDiffView.LineBG1.EMPTY">#3C000000</Color>
<Color x:Key="Color.TextDiffView.LineBG1.ADD">#3C00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG1.DELETED">#3CFF0000</Color>
<Color x:Key="Color.TextDiffView.LineBG2.ADD">#5A00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG2.DELETED">#50FF0000</Color>
<Color x:Key="Color.Diff.EmptyBG">#3C000000</Color>
<Color x:Key="Color.Diff.AddedBG">#3C00FF00</Color>
<Color x:Key="Color.Diff.DeletedBG">#3CFF0000</Color>
<Color x:Key="Color.Diff.AddedHighlight">#5A00FF00</Color>
<Color x:Key="Color.Diff.DeletedHighlight">#50FF0000</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
@ -37,6 +37,7 @@
<Color x:Key="Color.MacOS.Minimize">#FFFCBB2D</Color>
<Color x:Key="Color.MacOS.Maximize">#FF25C53C</Color>
<Color x:Key="Color.Window">#FF252525</Color>
<Color x:Key="Color.WindowBorder">#FF444444</Color>
<Color x:Key="Color.TitleBar">#FF1F1F1F</Color>
<Color x:Key="Color.ToolBar">#FF2C2C2C</Color>
<Color x:Key="Color.Popup">#FF2B2B2B</Color>
@ -45,22 +46,21 @@
<Color x:Key="Color.Decorator">#FF505050</Color>
<Color x:Key="Color.DecoratorIcon">#FFF8F8F8</Color>
<Color x:Key="Color.Conflict">#FFFAFAD2</Color>
<Color x:Key="Color.ConflictForeground">#FF252525</Color>
<Color x:Key="Color.Border0">#FF181818</Color>
<Color x:Key="Color.Border1">#FF7C7C7C</Color>
<Color x:Key="Color.Border2">#FF404040</Color>
<Color x:Key="Color.Border3">#FF252525</Color>
<Color x:Key="Color.FlatButton.Background">#FF303030</Color>
<Color x:Key="Color.FlatButton.BackgroundHovered">#FF333333</Color>
<Color x:Key="Color.FlatButton.PrimaryBackground">#FF3A3A3A</Color>
<Color x:Key="Color.FlatButton.PrimaryBackgroundHovered">#FF404040</Color>
<Color x:Key="Color.FG1">#FFDDDDDD</Color>
<Color x:Key="Color.FG2">#40F1F1F1</Color>
<Color x:Key="Color.FG3">#FF252525</Color>
<Color x:Key="Color.TextDiffView.LineBG1.EMPTY">#3C000000</Color>
<Color x:Key="Color.TextDiffView.LineBG1.ADD">#3C00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG1.DELETED">#3CFF0000</Color>
<Color x:Key="Color.TextDiffView.LineBG2.ADD">#5A00FF00</Color>
<Color x:Key="Color.TextDiffView.LineBG2.DELETED">#50FF0000</Color>
<Color x:Key="Color.Diff.EmptyBG">#3C000000</Color>
<Color x:Key="Color.Diff.AddedBG">#3C00FF00</Color>
<Color x:Key="Color.Diff.DeletedBG">#3CFF0000</Color>
<Color x:Key="Color.Diff.AddedHighlight">#5A00FF00</Color>
<Color x:Key="Color.Diff.DeletedHighlight">#50FF0000</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
@ -68,6 +68,7 @@
<SolidColorBrush x:Key="Brush.MacOS.Minimize" Color="{DynamicResource Color.MacOS.Minimize}"/>
<SolidColorBrush x:Key="Brush.MacOS.Maximize" Color="{DynamicResource Color.MacOS.Maximize}"/>
<SolidColorBrush x:Key="Brush.Window" Color="{DynamicResource Color.Window}"/>
<SolidColorBrush x:Key="Brush.WindowBorder" Color="{DynamicResource Color.WindowBorder}"/>
<SolidColorBrush x:Key="Brush.TitleBar" Color="{DynamicResource Color.TitleBar}"/>
<SolidColorBrush x:Key="Brush.ToolBar" Color="{DynamicResource Color.ToolBar}"/>
<SolidColorBrush x:Key="Brush.Popup" Color="{DynamicResource Color.Popup}"/>
@ -76,22 +77,21 @@
<SolidColorBrush x:Key="Brush.Decorator" Color="{DynamicResource Color.Decorator}"/>
<SolidColorBrush x:Key="Brush.DecoratorIcon" Color="{DynamicResource Color.DecoratorIcon}"/>
<SolidColorBrush x:Key="Brush.Conflict" Color="{DynamicResource Color.Conflict}"/>
<SolidColorBrush x:Key="Brush.ConflictForeground" Color="{DynamicResource Color.ConflictForeground}"/>
<SolidColorBrush x:Key="Brush.Border0" Color="{DynamicResource Color.Border0}"/>
<SolidColorBrush x:Key="Brush.Border1" Color="{DynamicResource Color.Border1}"/>
<SolidColorBrush x:Key="Brush.Border2" Color="{DynamicResource Color.Border2}"/>
<SolidColorBrush x:Key="Brush.Border3" Color="{DynamicResource Color.Border3}"/>
<SolidColorBrush x:Key="Brush.FlatButton.Background" Color="{DynamicResource Color.FlatButton.Background}"/>
<SolidColorBrush x:Key="Brush.FlatButton.BackgroundHovered" Color="{DynamicResource Color.FlatButton.BackgroundHovered}"/>
<SolidColorBrush x:Key="Brush.FlatButton.PrimaryBackground" Color="{DynamicResource Color.FlatButton.PrimaryBackground}"/>
<SolidColorBrush x:Key="Brush.FlatButton.PrimaryBackgroundHovered" Color="{DynamicResource Color.FlatButton.PrimaryBackgroundHovered}"/>
<SolidColorBrush x:Key="Brush.FG1" Color="{DynamicResource Color.FG1}"/>
<SolidColorBrush x:Key="Brush.FG2" Color="{DynamicResource Color.FG2}"/>
<SolidColorBrush x:Key="Brush.FG3" Color="{DynamicResource Color.FG3}"/>
<SolidColorBrush x:Key="Brush.Accent" Color="{DynamicResource SystemAccentColor}"/>
<SolidColorBrush x:Key="Brush.AccentHovered" Color="{DynamicResource SystemListLowColor}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG1.EMPTY" Color="{DynamicResource Color.TextDiffView.LineBG1.EMPTY}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG1.ADD" Color="{DynamicResource Color.TextDiffView.LineBG1.ADD}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG1.DELETED" Color="{DynamicResource Color.TextDiffView.LineBG1.DELETED}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG2.ADD" Color="{DynamicResource Color.TextDiffView.LineBG2.ADD}"/>
<SolidColorBrush x:Key="Brush.TextDiffView.LineBG2.DELETED" Color="{DynamicResource Color.TextDiffView.LineBG2.DELETED}"/>
<SolidColorBrush x:Key="Brush.Diff.EmptyBG" Color="{DynamicResource Color.Diff.EmptyBG}"/>
<SolidColorBrush x:Key="Brush.Diff.AddedBG" Color="{DynamicResource Color.Diff.AddedBG}"/>
<SolidColorBrush x:Key="Brush.Diff.DeletedBG" Color="{DynamicResource Color.Diff.DeletedBG}"/>
<SolidColorBrush x:Key="Brush.Diff.AddedHighlight" Color="{DynamicResource Color.Diff.AddedHighlight}"/>
<SolidColorBrush x:Key="Brush.Diff.DeletedHighlight" Color="{DynamicResource Color.Diff.DeletedHighlight}"/>
</ResourceDictionary>

View file

@ -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<Models.Change> VisibleChanges
{
get => _visibleChanges;
private set => SetProperty(ref _visibleChanges, value);
}
public List<Models.Change> 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<Models.Change>();
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<Models.Change>();
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<Models.Change> _changes = null;
private List<Models.Change> _visibleChanges = null;
private List<Models.Change> _selectedChanges = null;
private string _searchFilter = string.Empty;
private DiffContext _diffContext = null;
}
}

View file

@ -385,6 +385,7 @@ namespace SourceGit.ViewModels
FullMessage = string.Empty;
VisibleChanges = null;
SelectedChanges = null;
ViewRevisionFileContent = null;
if (_commit == null)
return;

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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<string>();
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;
});

View file

@ -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<LauncherPage> { ActivePage };
OnPropertyChanged(nameof(Pages));
GC.Collect();
}

View file

@ -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<GridLength>
{
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);
}
}
}

View file

@ -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;

View file

@ -51,13 +51,6 @@ namespace SourceGit.ViewModels
set;
} = new AvaloniaList<string>();
[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()
@ -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);
}
var compareWithBranch = CreateMenuItemToCompareBranches(branch);
if (compareWithBranch != null)
{
if (WorkingCopyChangesCount == 0)
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(compare);
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,28 +1236,9 @@ 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);
var hasCompare = false;
if (WorkingCopyChangesCount > 0)
{
var compareWithWorktree = new MenuItem();
@ -1302,11 +1256,18 @@ namespace SourceGit.ViewModels
}
};
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}");
@ -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<Models.Branch> branches, List<Models.Remote> 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;

View file

@ -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<Models.TextDiffLine> New { get; set; } = new List<Models.TextDiffLine>();
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;
}
}

View file

@ -379,27 +379,19 @@ 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));
}
}
}
}
public void Commit()
{
@ -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();
@ -536,6 +528,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");
history.Icon = App.CreateMenuIcon("Icons.Histories");
@ -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<Models.Change> changes)

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -9,20 +9,10 @@
x:DataType="v:About"
Icon="/App.ico"
Title="{DynamicResource Text.About}"
Background="Transparent"
SizeToContent="WidthAndHeight"
CanResize="False"
WindowStartupLocation="CenterScreen"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid RowDefinitions="Auto,*" Margin="{OnPlatform 0, Linux=6}">
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
WindowStartupLocation="CenterScreen">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
<Border Grid.Column="0" Grid.ColumnSpan="3"
@ -59,7 +49,7 @@
</Button>
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="Auto,*" Background="{DynamicResource Brush.Window}">
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
<Image Grid.Column="0"
Width="200" Height="200"
Margin="8,0"
@ -100,4 +90,4 @@
</StackPanel>
</Grid>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -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
{

View file

@ -28,11 +28,11 @@
</DataTemplate>
<DataTemplate DataType="m:Commit">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0"/>
</StackPanel>
<Grid ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="m:Tag">

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -10,20 +10,10 @@
x:DataType="vm:AssumeUnchangedManager"
Icon="/App.ico"
Title="{DynamicResource Text.AssumeUnchanged}"
Background="Transparent"
Width="600" Height="400"
CanResize="False"
WindowStartupLocation="CenterOwner"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid RowDefinitions="Auto,*" Margin="{OnPlatform 0, Linux=6}">
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
<Border Grid.Column="0" Grid.ColumnSpan="3"
@ -61,7 +51,7 @@
</Grid>
<!-- Unchanged Files -->
<Grid Grid.Row="1" Background="{DynamicResource Brush.Window}">
<Grid Grid.Row="1">
<DataGrid Margin="8"
Background="{DynamicResource Brush.Contents}"
ItemsSource="{Binding Files}"
@ -110,4 +100,4 @@
</StackPanel>
</Grid>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -4,7 +4,7 @@ using Avalonia.Interactivity;
namespace SourceGit.Views
{
public partial class AssumeUnchangedManager : Window
public partial class AssumeUnchangedManager : ChromelessWindow
{
public AssumeUnchangedManager()
{

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -12,25 +12,15 @@
x:DataType="vm:Blame"
Icon="/App.ico"
Title="{DynamicResource Text.Blame}"
Background="Transparent"
WindowStartupLocation="CenterOwner"
MinWidth="1280" MinHeight="720"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid Margin="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToContentMargin}}">
MinWidth="1280" MinHeight="720">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="3"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*,Auto">
<!-- Bottom border -->
@ -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"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">
@ -58,12 +50,12 @@
</Grid>
<!-- File -->
<Border Grid.Row="1" Padding="8,0" Background="{DynamicResource Brush.Window}">
<Border Grid.Row="1" Padding="8,0">
<TextBlock Text="{Binding Title}" VerticalAlignment="Center"/>
</Border>
<!-- Body -->
<Grid Grid.Row="2" Background="{DynamicResource Brush.Window}">
<Grid Grid.Row="2">
<!-- Blame View -->
<v:BlameTextEditor HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
@ -86,64 +78,5 @@
<!-- Loading -->
<v:LoadingIcon Width="48" Height="48" IsVisible="{Binding Data, Converter={x:Static ObjectConverters.IsNull}}"/>
</Grid>
<!-- Custom window sizer for Linux -->
<Grid Grid.Row="0" Grid.RowSpan="3" IsVisible="{OnPlatform False, Linux=True}" IsHitTestVisible="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.IsNormal}}">
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Top"
Cursor="TopLeftCorner"
Tag="{x:Static WindowEdge.NorthWest}"
PointerPressed="CustomResizeWindow"/>
<Border Height="4" Margin="4,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
Cursor="TopSide"
Tag="{x:Static WindowEdge.North}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Top"
Cursor="TopRightCorner"
Tag="{x:Static WindowEdge.NorthEast}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Margin="0,4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Cursor="LeftSide"
Tag="{x:Static WindowEdge.West}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Margin="0,4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Stretch"
Cursor="RightSide"
Tag="{x:Static WindowEdge.East}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Cursor="BottomLeftCorner"
Tag="{x:Static WindowEdge.SouthWest}"
PointerPressed="CustomResizeWindow"/>
<Border Height="4" Margin="4,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Cursor="BottomSide"
Tag="{x:Static WindowEdge.South}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Cursor="BottomRightCorner"
Tag="{x:Static WindowEdge.SouthEast}"
PointerPressed="CustomResizeWindow"/>
</Grid>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -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;
}
}

View file

@ -0,0 +1,173 @@
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.BranchCompare"
x:DataType="vm:BranchCompare"
x:Name="me"
Icon="/App.ico"
Title="{DynamicResource Text.BranchCompare}"
MinWidth="1280" MinHeight="720"
WindowStartupLocation="CenterOwner">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="64"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*,Auto">
<!-- Bottom border -->
<Border Grid.Column="0" Grid.ColumnSpan="4"
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"
PointerMoved="MoveWindow"
PointerReleased="EndMoveWindow"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">
<v:CaptionButtonsMacOS/>
</Border>
<!-- Icon -->
<Path Grid.Column="1" Margin="8,0,0,0" Width="12" Height="12" Data="{StaticResource Icons.Compare}"/>
<!-- Title -->
<TextBlock Grid.Column="2" Margin="8,0,0,0" Text="{DynamicResource Text.BranchCompare}" FontWeight="Bold" IsHitTestVisible="False" VerticalAlignment="Center"/>
<!-- Caption Buttons (Windows/Linux) -->
<Border Grid.Column="3" IsVisible="{OnPlatform True, macOS=False}">
<v:CaptionButtons/>
</Border>
</Grid>
<!-- Compare Targets -->
<Border Grid.Row="1">
<Grid Margin="48,8,48,8" ColumnDefinitions="*,48,*">
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<v:Avatar Width="16" Height="16"
VerticalAlignment="Center"
IsHitTestVisible="False"
User="{Binding BaseHead.Author}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding BaseHead.Author.Name}" Margin="8,0,0,0"/>
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{Binding Base, Converter={x:Static c:BranchConverters.ToName}}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
</Border>
<TextBlock Grid.Column="3" Classes="monospace" Text="{Binding BaseHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/>
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding BaseHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid>
<TextBlock Grid.Row="1" Classes="monospace" Text="{Binding BaseHead.Subject}" VerticalAlignment="Bottom"/>
</Grid>
</Border>
<Path Grid.Column="1" Width="16" Height="16" Fill="{DynamicResource Brush.FG2}" Data="{DynamicResource Icons.Down}" RenderTransformOrigin="50%,50%" RenderTransform="rotate(270deg)"/>
<Border Grid.Column="2" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<v:Avatar Width="16" Height="16"
VerticalAlignment="Center"
IsHitTestVisible="False"
User="{Binding ToHead.Author}"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding ToHead.Author.Name}" Margin="8,0,0,0"/>
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{Binding To, Converter={x:Static c:BranchConverters.ToName}}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
</Border>
<TextBlock Grid.Column="3" Classes="monospace" Text="{Binding ToHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/>
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding ToHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid>
<TextBlock Grid.Row="1" Classes="monospace" Text="{Binding ToHead.Subject}" VerticalAlignment="Bottom"/>
</Grid>
</Border>
</Grid>
</Border>
<!-- Changes -->
<Border Grid.Row="2">
<Grid Margin="8,0,8,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="256" MinWidth="200" MaxWidth="480"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" RowDefinitions="26,*">
<!-- Search & Display Mode -->
<Grid Grid.Row="0" ColumnDefinitions="*,18">
<TextBox Grid.Column="0"
Height="26"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="Transparent"
CornerRadius="4"
Watermark="{DynamicResource Text.CommitDetail.Changes.Search}"
Text="{Binding SearchFilter, Mode=TwoWay}">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14" Margin="4,0,0,0" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
IsVisible="{Binding SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding ClearSearchFilter}">
<Path Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<v:ChangeViewModeSwitcher Grid.Column="1"
Width="14" Height="14"
HorizontalAlignment="Right"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}"/>
</Grid>
<!-- Changes -->
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
<v:ChangeCollectionView IsWorkingCopyChange="False"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode}"
Changes="{Binding VisibleChanges}"
SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}"
ContextRequested="OnChangeContextRequested"/>
</Border>
</Grid>
<GridSplitter Grid.Column="1"
MinWidth="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="Transparent"/>
<Grid Grid.Column="2">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<Path Width="64" Height="64" Data="{StaticResource Icons.Diff}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Margin="0,16,0,0"
Text="{DynamicResource Text.Diff.Welcome}"
FontSize="18" FontWeight="Bold"
Foreground="{DynamicResource Brush.FG2}"
HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<ContentControl Content="{Binding DiffContext}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:DiffContext">
<v:DiffView/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</Grid>
</Border>
</Grid>
</v:ChromelessWindow>

View file

@ -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;
}
}

View file

@ -12,22 +12,24 @@
Classes="bold"
Text="{DynamicResource Text.Checkout.Commit}" />
<TextBlock Margin="0,8,0,16"
<StackPanel Orientation="Horizontal" Margin="0,12,0,16">
<Path Width="14" Height="14" Data="{StaticResource Icons.Error}" Fill="DarkOrange"/>
<TextBlock Margin="4,0,0,0"
Text="{DynamicResource Text.Checkout.Commit.Warning}"
TextWrapping="Wrap"
Foreground="{DynamicResource Brush.FG2}"
FontStyle="Italic"/>
</StackPanel>
<Grid RowDefinitions="32,Auto" ColumnDefinitions="Auto,*" ClipToBounds="True">
<Grid RowDefinitions="32,Auto" ColumnDefinitions="Auto,*" ClipToBounds="True" HorizontalAlignment="Center">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Checkout.Commit.Target}" />
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}" />
<TextBlock Classes="monospace" Foreground="DarkOrange" VerticalAlignment="Center" Margin="8,0" Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" />
<TextBlock Text="{Binding Commit.Subject}"/>
</StackPanel>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}" />
<TextBlock Grid.Column="1" Classes="monospace" Foreground="DarkOrange" VerticalAlignment="Center" Margin="8,0" Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" />
<TextBlock Grid.Column="2" Text="{Binding Commit.Subject}" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"

View file

@ -12,17 +12,17 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.CherryPick}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="150,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CherryPick.Commit}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Classes="monospace" VerticalAlignment="Center" Text="{Binding Target.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Target.Subject}" Margin="4,0,0,0"/>
</StackPanel>
<Grid Grid.Column="1" ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Classes="monospace" VerticalAlignment="Center" Text="{Binding Target.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Target.Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
<CheckBox Grid.Row="1" Grid.Column="1"
Content="{DynamicResource Text.CherryPick.CommitChanges}"

View file

@ -0,0 +1,56 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
namespace SourceGit.Views
{
public class ChromelessWindow : Window
{
protected override Type StyleKeyOverride => 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<Border>(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);
}
}
}

View file

@ -10,46 +10,71 @@
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
<TextBlock Classes="bold" FontSize="18" Text="{DynamicResource Text.Clone}"/>
<Grid Margin="8,16,0,0" Height="28" ColumnDefinitions="140,*">
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.RemoteURL}"/>
<TextBox Grid.Column="1" CornerRadius="3" Text="{Binding Remote, Mode=TwoWay}" v:AutoFocusBehaviour.IsEnabled="True"/>
</Grid>
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="140,*" IsVisible="{Binding UseSSH}">
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.SSHKey}"/>
<TextBox Grid.Column="1"
<Grid Margin="8,16,0,0" RowDefinitions="32,Auto,32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.Clone.RemoteURL}"/>
<TextBox Grid.Row="0" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding Remote, Mode=TwoWay}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.SSHKey}"
IsVisible="{Binding UseSSH}"/>
<TextBox Grid.Row="1" Grid.Column="1"
x:Name="TxtSshKey"
Height="28"
CornerRadius="3"
Watermark="{DynamicResource Text.SSHKey.Placeholder}"
Text="{Binding SSHKey, Mode=TwoWay}">
Text="{Binding SSHKey, Mode=TwoWay}"
IsVisible="{Binding UseSSH}">
<TextBox.InnerRightContent>
<Button Classes="icon_button" Width="30" Height="30" Click="SelectSSHKey">
<Button Classes="icon_button" Width="28" Height="28" Click="SelectSSHKey">
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
</Grid>
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="140,*">
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.ParentFolder}"/>
<TextBox Grid.Column="1"
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.Clone.ParentFolder}"/>
<TextBox Grid.Row="2" Grid.Column="1"
x:Name="TxtParentFolder"
Height="28"
CornerRadius="3"
Text="{Binding ParentFolder, Mode=TwoWay}">
<TextBox.InnerRightContent>
<Button Classes="icon_button" Width="30" Height="30" Margin="4,0,0,0" Click="SelectParentFolder">
<Button Classes="icon_button" Width="28" Height="28" Margin="4,0,0,0" Click="SelectParentFolder">
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
</Grid>
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="140,*">
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.LocalName}"/>
<TextBox Grid.Column="1" CornerRadius="3" Watermark="{DynamicResource Text.Clone.LocalName.Placeholder}" Text="{Binding Local, Mode=TwoWay}"/>
</Grid>
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="140,*">
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.AdditionalParam}"/>
<TextBox Grid.Column="1" CornerRadius="3" Watermark="{DynamicResource Text.Clone.AdditionalParam.Placeholder}" Text="{Binding ExtraArgs, Mode=TwoWay}"/>
<TextBlock Grid.Row="3" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.Clone.LocalName}"/>
<TextBox Grid.Row="3" Grid.Column="1"
Height="28"
CornerRadius="3"
Watermark="{DynamicResource Text.Clone.LocalName.Placeholder}"
Text="{Binding Local, Mode=TwoWay}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
HorizontalAlignment="Right"
Margin="0,0,8,0"
Text="{DynamicResource Text.Clone.AdditionalParam}"/>
<TextBox Grid.Row="4" Grid.Column="1"
Height="28"
CornerRadius="3"
Watermark="{DynamicResource Text.Clone.AdditionalParam.Placeholder}"
Text="{Binding ExtraArgs, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</UserControl>

View file

@ -10,7 +10,7 @@
x:DataType="vm:CommitDetail">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="256" MinWidth="200" MaxWidth="480"/>
<ColumnDefinition Width="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.CommitDetailChangesLeftWidth, Mode=TwoWay}" MinWidth="200" MaxWidth="480"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

View file

@ -29,11 +29,11 @@
</DataTemplate>
<DataTemplate DataType="m:Commit">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0"/>
</StackPanel>
<Grid ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="m:Tag">

View file

@ -13,7 +13,7 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.CreateTag}"/>
<Grid Margin="0,16,8,0" RowDefinitions="32,32,32,Auto,Auto,32" ColumnDefinitions="150,*">
<Grid Margin="0,16,8,0" RowDefinitions="32,32,32,Auto,Auto,32" ColumnDefinitions="120,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
@ -28,11 +28,11 @@
</DataTemplate>
<DataTemplate DataType="m:Commit">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0"/>
</StackPanel>
<Grid ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>

View file

@ -234,9 +234,7 @@
<!-- Text Diff -->
<DataTemplate DataType="m:TextDiff">
<v:TextDiffView TextDiff="{Binding}"
SyncScrollOffset="{Binding SyncScrollOffset, Mode=TwoWay}"
UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"/>
<v:TextDiffView UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"/>
</DataTemplate>
<!-- Empty or only EOL changes -->

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -12,23 +12,13 @@
x:Name="me"
Icon="/App.ico"
Title="{DynamicResource Text.FileHistory}"
Background="Transparent"
MinWidth="1280" MinHeight="720"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid Margin="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToContentMargin}}">
MinWidth="1280" MinHeight="720">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*,Auto">
<!-- Bottom border -->
@ -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"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">
@ -56,7 +48,7 @@
</Grid>
<!-- Body -->
<Grid Grid.Row="1" Background="{DynamicResource Brush.Window}">
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" MinWidth="300" MaxWidth="600"/>
<ColumnDefinition Width="4"/>
@ -143,64 +135,5 @@
</Grid>
</Grid>
</Grid>
<!-- Custom window sizer for Linux -->
<Grid Grid.Row="0" Grid.RowSpan="2" IsVisible="{OnPlatform False, Linux=True}" IsHitTestVisible="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.IsNormal}}">
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Top"
Cursor="TopLeftCorner"
Tag="{x:Static WindowEdge.NorthWest}"
PointerPressed="CustomResizeWindow"/>
<Border Height="4" Margin="4,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
Cursor="TopSide"
Tag="{x:Static WindowEdge.North}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Top"
Cursor="TopRightCorner"
Tag="{x:Static WindowEdge.NorthEast}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Margin="0,4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Cursor="LeftSide"
Tag="{x:Static WindowEdge.West}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Margin="0,4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Stretch"
Cursor="RightSide"
Tag="{x:Static WindowEdge.East}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Cursor="BottomLeftCorner"
Tag="{x:Static WindowEdge.SouthWest}"
PointerPressed="CustomResizeWindow"/>
<Border Height="4" Margin="4,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Cursor="BottomSide"
Tag="{x:Static WindowEdge.South}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Cursor="BottomRightCorner"
Tag="{x:Static WindowEdge.SouthEast}"
PointerPressed="CustomResizeWindow"/>
</Grid>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -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;
}
}

View file

@ -1,27 +1,18 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="using:SourceGit.Converters"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.Hotkeys"
Icon="/App.ico"
Title="{DynamicResource Text.Hotkeys}"
Background="Transparent"
SizeToContent="WidthAndHeight"
CanResize="False"
WindowStartupLocation="CenterOwner"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid RowDefinitions="Auto,*" Margin="{OnPlatform 0, Linux=6}">
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
<Border Grid.Column="0" Grid.ColumnSpan="3"
@ -59,7 +50,7 @@
</Grid>
<!-- Body -->
<Border Grid.Row="1" Background="{DynamicResource Brush.Window}">
<Border Grid.Row="1">
<StackPanel Orientation="Vertical" Margin="16,8,16,16">
<TextBlock Text="{DynamicResource Text.Hotkeys.Global}"
Foreground="{DynamicResource Brush.FG2}"
@ -93,7 +84,7 @@
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
Margin="0,8"/>
<Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="150,*">
<Grid RowDefinitions="20,20,20,20,20,20,20,20" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+F, macOS=⌘+F}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.OpenSearchCommits}" />
@ -109,8 +100,14 @@
<TextBlock Grid.Row="4" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Space, macOS=␣}"/>
<TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.StageOrUnstageSelected}" />
<TextBlock Grid.Row="5" Grid.Column="0" Classes="monospace bold" Text="F5"/>
<TextBlock Grid.Row="5" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Refresh}" />
<TextBlock Grid.Row="5" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"/>
<TextBlock Grid.Row="5" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Commit}" />
<TextBlock Grid.Row="6" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+⇧+Enter}"/>
<TextBlock Grid.Row="6" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.CommitAndPush}" />
<TextBlock Grid.Row="7" Grid.Column="0" Classes="monospace bold" Text="F5"/>
<TextBlock Grid.Row="7" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Refresh}" />
</Grid>
<TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}"
@ -135,4 +132,4 @@
</StackPanel>
</Border>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -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()
{

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -13,36 +13,27 @@
x:Name="me"
Icon="/App.ico"
Title="SourceGit"
Background="Transparent"
MinWidth="1280" MinHeight="720"
WindowStartupLocation="CenterScreen"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Window.Resources>
<SolidColorBrush x:Key="SystemControlErrorTextForegroundBrush" Color="Red"/>
</Window.Resources>
<Grid Margin="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToContentMargin}}">
Width="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.LauncherWidth, Mode=TwoWay}"
Height="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.LauncherHeight, Mode=TwoWay}"
WindowState="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.LauncherWindowState, Mode=TwoWay}"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToTitleBarHeight}}"/>
<RowDefinition Height="{Binding #me.TitleBarHeight}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
<!-- Custom TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto">
<!-- Bottom border -->
<Border Grid.Column="0" Grid.ColumnSpan="3"
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"/>
DoubleTapped="OnTitleBarDoubleTapped"
PointerPressed="BeginMoveWindow"
PointerMoved="MoveWindow"
PointerReleased="EndMoveWindow"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" VerticalAlignment="Stretch" Margin="2,0,8,3" IsVisible="{OnPlatform False, macOS=True}">
@ -92,10 +83,9 @@
<ScrollViewer Grid.Column="1"
x:Name="launcherTabsScroller"
HorizontalAlignment="Left"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"
PointerWheelChanged="ScrollTabs"
ScrollChanged="OnTabsScrollChanged">
<StackPanel x:Name="launcherTabsBar" Orientation="Horizontal" SizeChanged="UpdateScrollIndicator">
@ -260,36 +250,21 @@
</ContentControl>
<!-- Popup container -->
<Grid Grid.Row="1" Margin="0,36,0,0" IsVisible="{Binding ActivePage.Popup, Converter={x:Static ObjectConverters.IsNotNull}}">
<Grid Grid.Row="1" Margin="0,36,0,0" IsVisible="{Binding ActivePage.Popup, Converter={x:Static ObjectConverters.IsNotNull}}" ClipToBounds="True">
<Border Background="Transparent" PointerPressed="OnPopupCancelByClickMask"/>
<Grid Width="500" HorizontalAlignment="Center" VerticalAlignment="Top">
<ContentControl Content="{Binding ActivePage.Popup}" ClipToBounds="True">
<Border Width="500" HorizontalAlignment="Center" VerticalAlignment="Top" Effect="drop-shadow(0 0 8 #8F000000)" CornerRadius="0,0,4,4" ClipToBounds="True">
<ContentControl Content="{Binding ActivePage.Popup}" Background="{DynamicResource Brush.Popup}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:Popup">
<Border Margin="8,0,8,8"
Background="{DynamicResource Brush.Popup}"
BorderBrush="{DynamicResource Brush.Border0}">
<Border.CornerRadius>
<OnPlatform Default="0,0,4,4" Linux="0"/>
</Border.CornerRadius>
<Border.BorderThickness>
<OnPlatform Default="0" Linux="1,0,1,1"/>
</Border.BorderThickness>
<Border.Effect>
<OnPlatform Default="drop-shadow(0 0 8 #8F000000)" Linux="{x:Null}"/>
</Border.Effect>
<StackPanel Margin="8" Orientation="Vertical">
<StackPanel Orientation="Vertical" Background="{DynamicResource Brush.Popup}">
<!-- Popup Widget -->
<ContentPresenter Margin="0,8"
<ContentPresenter Margin="8,16,8,8"
Content="{Binding View}"
IsHitTestVisible="{Binding InProgress, Converter={x:Static BoolConverters.Not}}"/>
<!-- Options -->
<StackPanel Margin="0,8,0,0"
<StackPanel Margin="8,4,8,8"
Height="32"
Orientation="Horizontal"
HorizontalAlignment="Right"
@ -307,15 +282,14 @@
</StackPanel>
<!-- Running -->
<v:PopupRunningStatus Margin="8"
<v:PopupRunningStatus Margin="12,8"
Description="{Binding ProgressDescription}"
IsVisible="{Binding InProgress}"/>
</StackPanel>
</Border>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</Border>
</Grid>
<!-- Notification container -->
@ -363,64 +337,5 @@
</ItemsControl>
</ScrollViewer>
</Grid>
<!-- Custom window sizer for Linux -->
<Grid Grid.Row="0" Grid.RowSpan="2" IsVisible="{OnPlatform False, Linux=True}" IsHitTestVisible="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.IsNormal}}">
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Top"
Cursor="TopLeftCorner"
Tag="{x:Static WindowEdge.NorthWest}"
PointerPressed="CustomResizeWindow"/>
<Border Height="4" Margin="4,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
Cursor="TopSide"
Tag="{x:Static WindowEdge.North}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Top"
Cursor="TopRightCorner"
Tag="{x:Static WindowEdge.NorthEast}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Margin="0,4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Cursor="LeftSide"
Tag="{x:Static WindowEdge.West}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Margin="0,4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Stretch"
Cursor="RightSide"
Tag="{x:Static WindowEdge.East}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Cursor="BottomLeftCorner"
Tag="{x:Static WindowEdge.SouthWest}"
PointerPressed="CustomResizeWindow"/>
<Border Height="4" Margin="4,0"
Background="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Cursor="BottomSide"
Tag="{x:Static WindowEdge.South}"
PointerPressed="CustomResizeWindow"/>
<Border Width="4" Height="4"
Background="Transparent"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Cursor="BottomRightCorner"
Tag="{x:Static WindowEdge.SouthEast}"
PointerPressed="CustomResizeWindow"/>
</Grid>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -7,8 +7,17 @@ using Avalonia.Interactivity;
namespace SourceGit.Views
{
public partial class Launcher : Window, Models.INotificationReceiver
public partial class Launcher : ChromelessWindow, Models.INotificationReceiver
{
public static readonly StyledProperty<GridLength> TitleBarHeightProperty =
AvaloniaProperty.Register<Launcher, GridLength>(nameof(TitleBarHeight), new GridLength(38, GridUnitType.Pixel));
public GridLength TitleBarHeight
{
get => GetValue(TitleBarHeightProperty);
set => SetValue(TitleBarHeightProperty, value);
}
public Launcher()
{
DataContext = new ViewModels.Launcher();
@ -34,6 +43,20 @@ namespace SourceGit.Views
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == WindowStateProperty)
{
var state = (WindowState)change.NewValue;
if (state == WindowState.Maximized)
SetCurrentValue(TitleBarHeightProperty, new GridLength(OperatingSystem.IsMacOS() ? 34 : 30));
else
SetCurrentValue(TitleBarHeightProperty, new GridLength(38, GridUnitType.Pixel));
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
var vm = DataContext as ViewModels.Launcher;
@ -136,36 +159,43 @@ namespace SourceGit.Views
base.OnClosing(e);
}
private void MaximizeOrRestoreWindow(object sender, TappedEventArgs e)
private void OnTitleBarDoubleTapped(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)
{
if (e.ClickCount != 2)
{
BeginMoveDrag(e);
_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 ScrollTabs(object sender, PointerWheelEventArgs e)
@ -229,26 +259,26 @@ namespace SourceGit.Views
private void OnPointerPressedTab(object sender, PointerPressedEventArgs e)
{
_pressedTab = true;
_startDrag = false;
_startDragTab = false;
_pressedTabPosition = e.GetPosition(sender as Border);
}
private void OnPointerReleasedTab(object sender, PointerReleasedEventArgs e)
{
_pressedTab = false;
_startDrag = false;
_startDragTab = false;
}
private void OnPointerMovedOverTab(object sender, PointerEventArgs e)
{
if (_pressedTab && !_startDrag && sender is Border border)
if (_pressedTab && !_startDragTab && sender is Border border)
{
var delta = e.GetPosition(border) - _pressedTabPosition;
var sizeSquired = delta.X * delta.X + delta.Y * delta.Y;
if (sizeSquired < 64)
return;
_startDrag = true;
_startDragTab = true;
var data = new DataObject();
data.Set("MovedTab", border.DataContext);
@ -270,7 +300,7 @@ namespace SourceGit.Views
}
_pressedTab = false;
_startDrag = false;
_startDragTab = false;
e.Handled = true;
}
@ -297,8 +327,9 @@ namespace SourceGit.Views
OnPopupCancel(sender, e);
}
private bool _pressedTitleBar = false;
private bool _pressedTab = false;
private Point _pressedTabPosition = new Point();
private bool _startDrag = false;
private bool _startDragTab = false;
}
}

View file

@ -8,7 +8,7 @@
x:Class="SourceGit.Views.PopupRunningStatus"
x:Name="me">
<StackPanel Orientation="Vertical">
<Rectangle Height="1" Margin="-8,0" HorizontalAlignment="Stretch" Fill="{DynamicResource Brush.Border1}" />
<Rectangle Height="1" HorizontalAlignment="Stretch" Fill="{DynamicResource Brush.Border1}" />
<StackPanel Orientation="Horizontal" Margin="0,8">
<ContentPresenter x:Name="icon" Width="12" Height="12"/>

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -14,20 +14,10 @@
x:Name="me"
Icon="/App.ico"
Title="{DynamicResource Text.Preference}"
Background="Transparent"
Width="600" SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterScreen"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid RowDefinitions="Auto,Auto" Margin="{OnPlatform 0, Linux=6}">
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
WindowStartupLocation="CenterScreen">
<Grid RowDefinitions="Auto,Auto">
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
<Border Grid.Column="0" Grid.ColumnSpan="3"
@ -62,7 +52,7 @@
</Grid>
<!-- Body -->
<Border Grid.Row="1" Background="{DynamicResource Brush.Window}">
<Border Grid.Row="1">
<TabControl>
<TabItem>
<TabItem.Header>
@ -210,20 +200,7 @@
Padding="4"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}"
CornerRadius="3"
Value="{Binding DefaultFontSize, Mode=TwoWay}">
<NumericUpDown.Styles>
<Style Selector="NumericUpDown /template/ ButtonSpinner#PART_Spinner">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
</Style>
<Style Selector="NumericUpDown /template/ TextBox#PART_TextBox">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="CornerRadius" Value="3,0,0,3"/>
</Style>
</NumericUpDown.Styles>
</NumericUpDown>
Value="{Binding DefaultFontSize, Mode=TwoWay}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
Text="{DynamicResource Text.Preference.Appearance.ColorOverrides}"
@ -392,20 +369,7 @@
CornerRadius="3"
ParsingNumberStyle="Integer"
FormatString="0"
Value="{Binding GitAutoFetchInterval, Mode=TwoWay, FallbackValue=10}">
<NumericUpDown.Styles>
<Style Selector="NumericUpDown /template/ ButtonSpinner#PART_Spinner">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
</Style>
<Style Selector="NumericUpDown /template/ TextBox#PART_TextBox">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="CornerRadius" Value="3,0,0,3"/>
</Style>
</NumericUpDown.Styles>
</NumericUpDown>
Value="{Binding GitAutoFetchInterval, Mode=TwoWay, FallbackValue=10}"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
@ -553,4 +517,4 @@
</TabControl>
</Border>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -14,7 +13,7 @@ using Avalonia.Threading;
namespace SourceGit.Views
{
public partial class Preference : Window
public partial class Preference : ChromelessWindow
{
public AvaloniaList<FontFamily> InstalledFonts
{

View file

@ -12,7 +12,7 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.Rebase}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="150,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="130,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
@ -36,11 +36,11 @@
</DataTemplate>
<DataTemplate DataType="m:Commit">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0"/>
</StackPanel>
<Grid ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Classes="monospace" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>

View file

@ -103,7 +103,7 @@
<!-- Body -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" MinWidth="200" MaxWidth="400"/>
<ColumnDefinition Width="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.RepositorySidebarWidth, Mode=TwoWay}" MinWidth="200" MaxWidth="400"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
@ -615,19 +615,19 @@
<ContentControl Grid.Column="0" Margin="8,0" Content="{Binding InProgressContext}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:CherryPickInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.CherryPick}"/>
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.ConflictForeground}" Text="{DynamicResource Text.InProgress.CherryPick}"/>
</DataTemplate>
<DataTemplate DataType="vm:RebaseInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.Rebase}"/>
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.ConflictForeground}" Text="{DynamicResource Text.InProgress.Rebase}"/>
</DataTemplate>
<DataTemplate DataType="vm:RevertInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.Revert}"/>
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.ConflictForeground}" Text="{DynamicResource Text.InProgress.Revert}"/>
</DataTemplate>
<DataTemplate DataType="vm:MergeInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.Merge}"/>
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.ConflictForeground}" Text="{DynamicResource Text.InProgress.Merge}"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>

View file

@ -25,11 +25,11 @@
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Reset.MoveTo}"/>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Text="{Binding To.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Text="{Binding To.Subject}" Margin="8,0,0,0"/>
</StackPanel>
<Grid Grid.Row="1" Grid.Column="1" ColumnDefinitions="Auto,Auto,*" Height="20" VerticalAlignment="Center">
<Path Grid.Column="0" Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Text="{Binding To.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Grid.Column="2" Text="{Binding To.Subject}" Margin="8,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"

View file

@ -12,17 +12,17 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.Revert}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="150,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="130,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Revert.Commit}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Classes="monospace" VerticalAlignment="Center" Text="{Binding Target.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Target.Subject}" Margin="4,0,0,0"/>
</StackPanel>
<Grid Grid.Column="1" ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Classes="monospace" VerticalAlignment="Center" Text="{Binding Target.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Target.Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
<CheckBox Grid.Row="1" Grid.Column="1"
Content="{DynamicResource Text.Revert.CommitChanges}"

View file

@ -12,7 +12,7 @@
x:DataType="vm:CommitDetail">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="256" MinWidth="200" MaxWidth="480"/>
<ColumnDefinition Width="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.CommitDetailFilesLeftWidth, Mode=TwoWay}" MinWidth="200" MaxWidth="480"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

View file

@ -12,22 +12,23 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.Reword}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="100,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32" ColumnDefinitions="100,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Reword.On}"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Text="{Binding Head.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Text="{Binding Head.Subject}" Margin="8,0,0,0"/>
</StackPanel>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,Auto,*" Height="20" VerticalAlignment="Center">
<Path Grid.Column="0" Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Text="{Binding Head.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Grid.Column="2" Text="{Binding Head.Subject}" Margin="8,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Reword.Message}"/>
<TextBox Grid.Row="1" Grid.RowSpan="2" Grid.Column="1"
<TextBox Grid.Row="1" Grid.RowSpan="3" Grid.Column="1"
Margin="0,4,0,0"
CornerRadius="2"
AcceptsReturn="True"
VerticalContentAlignment="Top"

View file

@ -1,29 +1,20 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.SelfUpdate"
x:DataType="vm:SelfUpdate"
Title="{DynamicResource Text.SelfUpdate.Title}"
Icon="/App.ico"
Background="Transparent"
SizeToContent="WidthAndHeight"
CanResize="False"
WindowStartupLocation="CenterOwner"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid RowDefinitions="Auto,*" Margin="{OnPlatform 0, Linux=6}">
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
<Border Grid.Column="0" Grid.ColumnSpan="3"
@ -61,7 +52,7 @@
</Grid>
<!-- Body -->
<Grid Grid.Row="1" Background="{DynamicResource Brush.Window}">
<Grid Grid.Row="1">
<ContentControl Content="{Binding Data}">
<ContentControl.DataTemplates>
<DataTemplate DataType="m:Version">
@ -150,4 +141,4 @@
</ContentControl>
</Grid>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -4,7 +4,7 @@ using Avalonia.Interactivity;
namespace SourceGit.Views
{
public partial class SelfUpdate : Window
public partial class SelfUpdate : ChromelessWindow
{
public SelfUpdate()
{

View file

@ -12,32 +12,33 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.Squash}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32" ColumnDefinitions="100,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32,32" ColumnDefinitions="100,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Squash.Head}"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Text="{Binding Head.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Text="{Binding Head.Subject}" Margin="8,0,0,0"/>
</StackPanel>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,Auto,*" Height="20" VerticalAlignment="Center">
<Path Grid.Column="0" Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Text="{Binding Head.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Grid.Column="2" Text="{Binding Head.Subject}" Margin="8,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Squash.To}"/>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Text="{Binding Parent.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Text="{Binding Parent.Subject}" Margin="8,0,0,0"/>
</StackPanel>
<Grid Grid.Row="1" Grid.Column="1" ColumnDefinitions="Auto,Auto,*" Height="20" VerticalAlignment="Center">
<Path Grid.Column="0" Margin="0,6,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" Text="{Binding Parent.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Grid.Column="2" Text="{Binding Parent.Subject}" Margin="8,0,0,0" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Squash.Message}"/>
<TextBox Grid.Row="2" Grid.RowSpan="2" Grid.Column="1"
<TextBox Grid.Row="2" Grid.RowSpan="3" Grid.Column="1"
Margin="0,4,0,0"
CornerRadius="2"
AcceptsReturn="True"
VerticalContentAlignment="Top"

View file

@ -11,7 +11,7 @@
x:DataType="vm:StashesPage">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" MinWidth="300"/>
<ColumnDefinition Width="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.StashesLeftWidth, Mode=TwoWay}" MinWidth="300"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

View file

@ -1,4 +1,4 @@
<Window xmlns="https://github.com/avaloniaui"
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -9,25 +9,10 @@
x:Class="SourceGit.Views.Statistics"
x:DataType="vm:Statistics"
Title="{DynamicResource Text.Statistics}"
Background="Transparent"
Width="800" Height="450"
WindowStartupLocation="CenterOwner"
CanResize="False"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
SystemDecorations="{OnPlatform Full, Linux=None}">
<Grid RowDefinitions="Auto,Auto,*" Margin="{OnPlatform 0, Linux=6}">
<!-- Custom window shadow for Linux -->
<Border Grid.Row="0" Grid.RowSpan="3"
Background="{DynamicResource Brush.Window}"
Effect="drop-shadow(0 0 6 #A0000000)"
IsVisible="{OnPlatform False, Linux=True}"/>
<!-- Window BG -->
<Border Grid.Row="1" Grid.RowSpan="2"
Background="{DynamicResource Brush.Window}"
IsVisible="{OnPlatform True, Linux=False}"/>
CanResize="False">
<Grid RowDefinitions="Auto,Auto,*">
<!-- Title bar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
<Border Grid.Column="0" Grid.ColumnSpan="3"
@ -185,4 +170,4 @@
HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsLoading}"/>
</Grid>
</Window>
</v:ChromelessWindow>

View file

@ -226,7 +226,7 @@ namespace SourceGit.Views
private int _lastHitIdx = -1;
}
public partial class Statistics : Window
public partial class Statistics : ChromelessWindow
{
public Statistics()
{

View file

@ -11,18 +11,16 @@
Background="{DynamicResource Brush.Contents}">
<UserControl.DataTemplates>
<DataTemplate DataType="m:TextDiff">
<v:CombinedTextDiffPresenter BorderBrush="{DynamicResource Brush.Border2}"
BorderThickness="0"
LineBGEmpty = "{DynamicResource Brush.TextDiffView.LineBG1.EMPTY}"
LineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG1.ADD}"
LineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG1.DELETED}"
SecondaryLineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG2.ADD}"
SecondaryLineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG2.DELETED}"
<v:CombinedTextDiffPresenter FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}"
SecondaryFG="{DynamicResource Brush.FG2}"
LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
DiffData="{Binding}"
SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/>
</DataTemplate>
@ -30,40 +28,36 @@
<DataTemplate DataType="vm:TwoSideTextDiff">
<Grid ColumnDefinitions="*,1,*">
<v:SingleSideTextDiffPresenter Grid.Column="0"
SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
IsOld="True"
BorderBrush="{DynamicResource Brush.Border2}"
BorderThickness="0"
LineBGEmpty = "{DynamicResource Brush.TextDiffView.LineBG1.EMPTY}"
LineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG1.ADD}"
LineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG1.DELETED}"
SecondaryLineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG2.ADD}"
SecondaryLineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG2.DELETED}"
FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}"
SecondaryFG="{DynamicResource Brush.FG2}"
LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
DiffData="{Binding}"/>
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/>
<Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
<v:SingleSideTextDiffPresenter Grid.Column="2"
SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
IsOld="False"
BorderBrush="{DynamicResource Brush.Border2}"
BorderThickness="0"
LineBGEmpty = "{DynamicResource Brush.TextDiffView.LineBG1.EMPTY}"
LineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG1.ADD}"
LineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG1.DELETED}"
SecondaryLineBGAdd = "{DynamicResource Brush.TextDiffView.LineBG2.ADD}"
SecondaryLineBGDeleted = "{DynamicResource Brush.TextDiffView.LineBG2.DELETED}"
FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}"
SecondaryFG="{DynamicResource Brush.FG2}"
LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
DiffData="{Binding}"/>
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/>
</Grid>
</DataTemplate>
</UserControl.DataTemplates>

View file

@ -7,6 +7,7 @@ using System.Text;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -21,7 +22,161 @@ using AvaloniaEdit.Utils;
namespace SourceGit.Views
{
public class CombinedTextDiffPresenter : TextEditor
public class IThemedTextDiffPresenter : TextEditor
{
public static readonly StyledProperty<string> FileNameProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, string>(nameof(FileName), string.Empty);
public string FileName
{
get => GetValue(FileNameProperty);
set => SetValue(FileNameProperty, value);
}
public static readonly StyledProperty<IBrush> LineBrushProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(LineBrush), new SolidColorBrush(Colors.DarkGray));
public IBrush LineBrush
{
get => GetValue(LineBrushProperty);
set => SetValue(LineBrushProperty, value);
}
public static readonly StyledProperty<IBrush> EmptyContentBackgroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(EmptyContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public IBrush EmptyContentBackground
{
get => GetValue(EmptyContentBackgroundProperty);
set => SetValue(EmptyContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> AddedContentBackgroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(AddedContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush AddedContentBackground
{
get => GetValue(AddedContentBackgroundProperty);
set => SetValue(AddedContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> DeletedContentBackgroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(DeletedContentBackground), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush DeletedContentBackground
{
get => GetValue(DeletedContentBackgroundProperty);
set => SetValue(DeletedContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> AddedHighlightBrushProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(AddedHighlightBrush), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)));
public IBrush AddedHighlightBrush
{
get => GetValue(AddedHighlightBrushProperty);
set => SetValue(AddedHighlightBrushProperty, value);
}
public static readonly StyledProperty<IBrush> DeletedHighlightBrushProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(DeletedHighlightBrush), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)));
public IBrush DeletedHighlightBrush
{
get => GetValue(DeletedHighlightBrushProperty);
set => SetValue(DeletedHighlightBrushProperty, value);
}
public static readonly StyledProperty<IBrush> IndicatorForegroundProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, IBrush>(nameof(IndicatorForeground), Brushes.Gray);
public IBrush IndicatorForeground
{
get => GetValue(IndicatorForegroundProperty);
set => SetValue(IndicatorForegroundProperty, value);
}
public static readonly StyledProperty<bool> UseSyntaxHighlightingProperty =
AvaloniaProperty.Register<IThemedTextDiffPresenter, bool>(nameof(UseSyntaxHighlighting), false);
public bool UseSyntaxHighlighting
{
get => GetValue(UseSyntaxHighlightingProperty);
set => SetValue(UseSyntaxHighlightingProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public IThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc)
{
IsReadOnly = true;
ShowLineNumbers = false;
BorderThickness = new Thickness(0);
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
UpdateTextMate();
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == UseSyntaxHighlightingProperty)
UpdateTextMate();
else if (change.Property == FileNameProperty)
Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName);
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
Models.TextMateHelper.SetThemeByApp(_textMate);
}
protected void UpdateTextMate()
{
if (UseSyntaxHighlighting)
{
if (_textMate == null)
{
TextArea.TextView.LineTransformers.Remove(_lineStyleTransformer);
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName);
}
}
else
{
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
GC.Collect();
TextArea.TextView.Redraw();
}
}
}
private TextMate.Installation _textMate = null;
protected IVisualLineTransformer _lineStyleTransformer = null;
}
public class CombinedTextDiffPresenter : IThemedTextDiffPresenter
{
public class LineNumberMargin : AbstractMargin
{
@ -104,7 +259,7 @@ namespace SourceGit.Views
public override void Render(DrawingContext context)
{
var pen = new Pen(_editor.BorderBrush, 1);
var pen = new Pen(_editor.LineBrush, 1);
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
}
@ -155,11 +310,11 @@ namespace SourceGit.Views
switch (type)
{
case Models.TextDiffLineType.None:
return _editor.LineBGEmpty;
return _editor.EmptyContentBackground;
case Models.TextDiffLineType.Added:
return _editor.LineBGAdd;
return _editor.AddedContentBackground;
case Models.TextDiffLineType.Deleted:
return _editor.LineBGDeleted;
return _editor.DeletedContentBackground;
default:
return null;
}
@ -186,7 +341,7 @@ namespace SourceGit.Views
{
ChangeLinePart(line.Offset, line.EndOffset, v =>
{
v.TextRunProperties.SetForegroundBrush(_editor.SecondaryFG);
v.TextRunProperties.SetForegroundBrush(_editor.IndicatorForeground);
v.TextRunProperties.SetTypeface(new Typeface(_editor.FontFamily, FontStyle.Italic));
});
@ -195,7 +350,7 @@ namespace SourceGit.Views
if (info.Highlights.Count > 0)
{
var bg = info.Type == Models.TextDiffLineType.Added ? _editor.SecondaryLineBGAdd : _editor.SecondaryLineBGDeleted;
var bg = info.Type == Models.TextDiffLineType.Added ? _editor.AddedHighlightBrush : _editor.DeletedHighlightBrush;
foreach (var highlight in info.Highlights)
{
ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v =>
@ -209,129 +364,57 @@ namespace SourceGit.Views
private readonly CombinedTextDiffPresenter _editor;
}
public static readonly StyledProperty<Models.TextDiff> DiffDataProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, Models.TextDiff>(nameof(DiffData));
public Models.TextDiff DiffData
{
get => GetValue(DiffDataProperty);
set => SetValue(DiffDataProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGEmptyProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(LineBGEmpty), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public IBrush LineBGEmpty
{
get => GetValue(LineBGEmptyProperty);
set => SetValue(LineBGEmptyProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGAddProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(LineBGAdd), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush LineBGAdd
{
get => GetValue(LineBGAddProperty);
set => SetValue(LineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGDeletedProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(LineBGDeleted), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush LineBGDeleted
{
get => GetValue(LineBGDeletedProperty);
set => SetValue(LineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGAddProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(SecondaryLineBGAdd), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)));
public IBrush SecondaryLineBGAdd
{
get => GetValue(SecondaryLineBGAddProperty);
set => SetValue(SecondaryLineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGDeletedProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(SecondaryLineBGDeleted), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)));
public IBrush SecondaryLineBGDeleted
{
get => GetValue(SecondaryLineBGDeletedProperty);
set => SetValue(SecondaryLineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryFGProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, IBrush>(nameof(SecondaryFG), Brushes.Gray);
public IBrush SecondaryFG
{
get => GetValue(SecondaryFGProperty);
set => SetValue(SecondaryFGProperty, value);
}
public static readonly StyledProperty<Vector> SyncScrollOffsetProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, Vector>(nameof(SyncScrollOffset));
public Vector SyncScrollOffset
{
get => GetValue(SyncScrollOffsetProperty);
set => SetValue(SyncScrollOffsetProperty, value);
}
public static readonly StyledProperty<bool> UseSyntaxHighlightingProperty =
AvaloniaProperty.Register<CombinedTextDiffPresenter, bool>(nameof(UseSyntaxHighlighting), false);
public bool UseSyntaxHighlighting
{
get => GetValue(UseSyntaxHighlightingProperty);
set => SetValue(UseSyntaxHighlightingProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public Models.TextDiff DiffData => DataContext as Models.TextDiff;
public CombinedTextDiffPresenter() : base(new TextArea(), new TextDocument())
{
_lineStyleTransformer = new LineStyleTransformer(this);
IsReadOnly = true;
ShowLineNumbers = false;
TextArea.LeftMargins.Add(new LineNumberMargin(this, true) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.LeftMargins.Add(new LineNumberMargin(this, false) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var scroller = (ScrollViewer)e.NameScope.Find("PART_ScrollViewer");
scroller.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.TwoWay));
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
UpdateTextMate();
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.ScrollOffsetChanged += OnTextViewScrollOffsetChanged;
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
TextArea.TextView.ScrollOffsetChanged -= OnTextViewScrollOffsetChanged;
}
if (_textMate != null)
protected override void OnDataContextChanged(EventArgs e)
{
_textMate.Dispose();
_textMate = null;
base.OnDataContextChanged(e);
var textDiff = DataContext as Models.TextDiff;
if (textDiff != null)
{
var builder = new StringBuilder();
foreach (var line in textDiff.Lines)
builder.AppendLine(line.Content);
Text = builder.ToString();
}
else
{
Text = string.Empty;
}
GC.Collect();
@ -346,9 +429,7 @@ namespace SourceGit.Views
var menu = new ContextMenu();
var parentView = this.FindAncestorOfType<TextDiffView>();
if (parentView != null)
{
parentView.FillContextMenuForWorkingCopyChange(menu, selection.StartPosition.Line, selection.EndPosition.Line, false);
}
var copy = new MenuItem();
copy.Header = App.Text("Copy");
@ -364,84 +445,9 @@ namespace SourceGit.Views
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true;
}
private void OnTextViewScrollOffsetChanged(object sender, EventArgs e)
{
SetCurrentValue(SyncScrollOffsetProperty, TextArea.TextView.ScrollOffset);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DiffDataProperty)
{
if (DiffData != null)
{
var builder = new StringBuilder();
foreach (var line in DiffData.Lines)
{
builder.AppendLine(line.Content);
}
Text = builder.ToString();
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
else
{
Text = string.Empty;
}
}
else if (change.Property == SyncScrollOffsetProperty)
{
if (TextArea.TextView.ScrollOffset != SyncScrollOffset)
{
IScrollable scrollable = TextArea.TextView;
scrollable.Offset = SyncScrollOffset;
}
}
else if (change.Property == UseSyntaxHighlightingProperty)
{
UpdateTextMate();
}
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
{
Models.TextMateHelper.SetThemeByApp(_textMate);
}
}
private void UpdateTextMate()
{
if (UseSyntaxHighlighting)
{
if (_textMate == null)
{
TextArea.TextView.LineTransformers.Remove(_lineStyleTransformer);
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
if (DiffData != null)
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
}
else
{
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
GC.Collect();
TextArea.TextView.Redraw();
}
}
}
private TextMate.Installation _textMate;
private readonly LineStyleTransformer _lineStyleTransformer = null;
}
public class SingleSideTextDiffPresenter : TextEditor
public class SingleSideTextDiffPresenter : IThemedTextDiffPresenter
{
public class LineNumberMargin : AbstractMargin
{
@ -523,7 +529,7 @@ namespace SourceGit.Views
public override void Render(DrawingContext context)
{
var pen = new Pen(_editor.BorderBrush, 1);
var pen = new Pen(_editor.LineBrush, 1);
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
}
@ -575,11 +581,11 @@ namespace SourceGit.Views
switch (type)
{
case Models.TextDiffLineType.None:
return _editor.LineBGEmpty;
return _editor.EmptyContentBackground;
case Models.TextDiffLineType.Added:
return _editor.LineBGAdd;
return _editor.AddedContentBackground;
case Models.TextDiffLineType.Deleted:
return _editor.LineBGDeleted;
return _editor.DeletedContentBackground;
default:
return null;
}
@ -607,7 +613,7 @@ namespace SourceGit.Views
{
ChangeLinePart(line.Offset, line.EndOffset, v =>
{
v.TextRunProperties.SetForegroundBrush(_editor.SecondaryFG);
v.TextRunProperties.SetForegroundBrush(_editor.IndicatorForeground);
v.TextRunProperties.SetTypeface(new Typeface(_editor.FontFamily, FontStyle.Italic));
});
@ -616,7 +622,7 @@ namespace SourceGit.Views
if (info.Highlights.Count > 0)
{
var bg = info.Type == Models.TextDiffLineType.Added ? _editor.LineBGAdd : _editor.LineBGDeleted;
var bg = info.Type == Models.TextDiffLineType.Added ? _editor.AddedHighlightBrush : _editor.DeletedHighlightBrush;
foreach (var highlight in info.Highlights)
{
ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v =>
@ -639,103 +645,16 @@ namespace SourceGit.Views
set => SetValue(IsOldProperty, value);
}
public static readonly StyledProperty<ViewModels.TwoSideTextDiff> DiffDataProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, ViewModels.TwoSideTextDiff>(nameof(DiffData));
public ViewModels.TwoSideTextDiff DiffData
{
get => GetValue(DiffDataProperty);
set => SetValue(DiffDataProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGEmptyProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(LineBGEmpty), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public IBrush LineBGEmpty
{
get => GetValue(LineBGEmptyProperty);
set => SetValue(LineBGEmptyProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGAddProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(LineBGAdd), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush LineBGAdd
{
get => GetValue(LineBGAddProperty);
set => SetValue(LineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> LineBGDeletedProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(LineBGDeleted), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush LineBGDeleted
{
get => GetValue(LineBGDeletedProperty);
set => SetValue(LineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGAddProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(SecondaryLineBGAdd), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)));
public IBrush SecondaryLineBGAdd
{
get => GetValue(SecondaryLineBGAddProperty);
set => SetValue(SecondaryLineBGAddProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryLineBGDeletedProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(SecondaryLineBGDeleted), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)));
public IBrush SecondaryLineBGDeleted
{
get => GetValue(SecondaryLineBGDeletedProperty);
set => SetValue(SecondaryLineBGDeletedProperty, value);
}
public static readonly StyledProperty<IBrush> SecondaryFGProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, IBrush>(nameof(SecondaryFG), Brushes.Gray);
public IBrush SecondaryFG
{
get => GetValue(SecondaryFGProperty);
set => SetValue(SecondaryFGProperty, value);
}
public static readonly StyledProperty<Vector> SyncScrollOffsetProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, Vector>(nameof(SyncScrollOffset), Vector.Zero);
public Vector SyncScrollOffset
{
get => GetValue(SyncScrollOffsetProperty);
set => SetValue(SyncScrollOffsetProperty, value);
}
public static readonly StyledProperty<bool> UseSyntaxHighlightingProperty =
AvaloniaProperty.Register<SingleSideTextDiffPresenter, bool>(nameof(UseSyntaxHighlighting), false);
public bool UseSyntaxHighlighting
{
get => GetValue(UseSyntaxHighlightingProperty);
set => SetValue(UseSyntaxHighlightingProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public ViewModels.TwoSideTextDiff DiffData => DataContext as ViewModels.TwoSideTextDiff;
public SingleSideTextDiffPresenter() : base(new TextArea(), new TextDocument())
{
_lineStyleTransformer = new LineStyleTransformer(this);
IsReadOnly = true;
ShowLineNumbers = false;
TextArea.LeftMargins.Add(new LineNumberMargin(this) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
}
protected override void OnLoaded(RoutedEventArgs e)
@ -745,12 +664,10 @@ namespace SourceGit.Views
_scrollViewer = this.FindDescendantOfType<ScrollViewer>();
if (_scrollViewer != null)
{
_scrollViewer.Offset = SyncScrollOffset;
_scrollViewer.ScrollChanged += OnTextViewScrollChanged;
_scrollViewer.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.OneWay));
}
UpdateTextMate();
TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
}
@ -765,18 +682,31 @@ namespace SourceGit.Views
_scrollViewer = null;
}
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
}
TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
GC.Collect();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is ViewModels.TwoSideTextDiff diff)
{
var builder = new StringBuilder();
var lines = IsOld ? diff.Old : diff.New;
foreach (var line in lines)
builder.AppendLine(line.Content);
Text = builder.ToString();
}
else
{
Text = string.Empty;
}
}
private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e)
{
if (!TextArea.IsFocused)
@ -785,8 +715,8 @@ namespace SourceGit.Views
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (TextArea.IsFocused)
SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset);
if (TextArea.IsFocused && DataContext is ViewModels.TwoSideTextDiff diff)
diff.SyncScrollOffset = _scrollViewer.Offset;
}
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
@ -798,9 +728,7 @@ namespace SourceGit.Views
var menu = new ContextMenu();
var parentView = this.FindAncestorOfType<TextDiffView>();
if (parentView != null)
{
parentView.FillContextMenuForWorkingCopyChange(menu, selection.StartPosition.Line, selection.EndPosition.Line, IsOld);
}
var copy = new MenuItem();
copy.Header = App.Text("Copy");
@ -817,96 +745,11 @@ namespace SourceGit.Views
e.Handled = true;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DiffDataProperty)
{
if (DiffData != null)
{
var builder = new StringBuilder();
if (IsOld)
{
foreach (var line in DiffData.Old)
{
builder.AppendLine(line.Content);
}
}
else
{
foreach (var line in DiffData.New)
{
builder.AppendLine(line.Content);
}
}
Text = builder.ToString();
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
else
{
Text = string.Empty;
}
}
else if (change.Property == SyncScrollOffsetProperty)
{
if (!TextArea.IsFocused && _scrollViewer != null)
_scrollViewer.Offset = SyncScrollOffset;
}
else if (change.Property == UseSyntaxHighlightingProperty)
{
UpdateTextMate();
}
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
{
Models.TextMateHelper.SetThemeByApp(_textMate);
}
}
private void UpdateTextMate()
{
if (UseSyntaxHighlighting)
{
if (_textMate == null)
{
TextArea.TextView.LineTransformers.Remove(_lineStyleTransformer);
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
if (DiffData != null)
Models.TextMateHelper.SetGrammarByFileName(_textMate, DiffData.File);
}
}
else
{
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
GC.Collect();
TextArea.TextView.Redraw();
}
}
}
private TextMate.Installation _textMate;
private readonly LineStyleTransformer _lineStyleTransformer = null;
private ScrollViewer _scrollViewer = null;
}
public partial class TextDiffView : UserControl
{
public static readonly StyledProperty<Models.TextDiff> TextDiffProperty =
AvaloniaProperty.Register<TextDiffView, Models.TextDiff>(nameof(TextDiff), null);
public Models.TextDiff TextDiff
{
get => GetValue(TextDiffProperty);
set => SetValue(TextDiffProperty, value);
}
public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
AvaloniaProperty.Register<TextDiffView, bool>(nameof(UseSideBySideDiff), false);
@ -916,13 +759,20 @@ namespace SourceGit.Views
set => SetValue(UseSideBySideDiffProperty, value);
}
public static readonly StyledProperty<Vector> SyncScrollOffsetProperty =
AvaloniaProperty.Register<TextDiffView, Vector>(nameof(SyncScrollOffset));
public Vector SyncScrollOffset
static TextDiffView()
{
get => GetValue(SyncScrollOffsetProperty);
set => SetValue(SyncScrollOffsetProperty, value);
UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, e) =>
{
if (v.DataContext is Models.TextDiff diff)
{
diff.SyncScrollOffset = Vector.Zero;
if (v.UseSideBySideDiff)
v.Content = new ViewModels.TwoSideTextDiff(diff);
else
v.Content = diff;
}
});
}
public TextDiffView()
@ -932,6 +782,10 @@ namespace SourceGit.Views
public void FillContextMenuForWorkingCopyChange(ContextMenu menu, int startLine, int endLine, bool isOldSide)
{
var diff = DataContext as Models.TextDiff;
if (diff == null)
return;
var parentView = this.FindAncestorOfType<DiffView>();
if (parentView == null)
return;
@ -951,7 +805,7 @@ namespace SourceGit.Views
endLine = tmp;
}
var selection = GetUnifiedSelection(startLine, endLine, isOldSide);
var selection = GetUnifiedSelection(diff, startLine, endLine, isOldSide);
if (!selection.HasChanges)
return;
@ -1033,17 +887,17 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked)
{
TextDiff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile);
diff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile);
}
else if (!UseSideBySideDiff)
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile);
diff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile);
}
else
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, isOldSide, tmpFile);
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, isOldSide, tmpFile);
}
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--cache --index").Exec();
@ -1065,17 +919,17 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked)
{
TextDiff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
}
else if (!UseSideBySideDiff)
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
}
else
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
}
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--reverse").Exec();
@ -1103,15 +957,15 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName();
if (change.Index == Models.ChangeState.Added)
{
TextDiff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile);
diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile);
}
else if (!UseSideBySideDiff)
{
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
}
else
{
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
}
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--cache --index --reverse").Exec();
@ -1133,17 +987,17 @@ namespace SourceGit.Views
var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked)
{
TextDiff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile);
}
else if (!UseSideBySideDiff)
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
}
else
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(ctx.RepositoryPath, change.Path).Result();
TextDiff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, isOldSide, tmpFile);
}
new Commands.Apply(ctx.RepositoryPath, tmpFile, true, "nowarn", "--index --reverse").Exec();
@ -1162,44 +1016,29 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem() { Header = "-" });
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
protected override void OnDataContextChanged(EventArgs e)
{
base.OnPropertyChanged(change);
base.OnDataContextChanged(e);
var data = TextDiff;
if (data == null)
var diff = DataContext as Models.TextDiff;
if (diff == null)
{
Content = null;
SyncScrollOffset = Vector.Zero;
GC.Collect();
return;
}
if (change.Property == TextDiffProperty)
{
if (UseSideBySideDiff)
Content = new ViewModels.TwoSideTextDiff(TextDiff);
Content = new ViewModels.TwoSideTextDiff(diff, Content as ViewModels.TwoSideTextDiff);
else
Content = TextDiff;
SetCurrentValue(SyncScrollOffsetProperty, TextDiff.SyncScrollOffset);
}
else if (change.Property == UseSideBySideDiffProperty)
{
if (UseSideBySideDiff)
Content = new ViewModels.TwoSideTextDiff(TextDiff);
else
Content = TextDiff;
SetCurrentValue(SyncScrollOffsetProperty, Vector.Zero);
}
Content = diff;
}
private Models.TextDiffSelection GetUnifiedSelection(int startLine, int endLine, bool isOldSide)
private Models.TextDiffSelection GetUnifiedSelection(Models.TextDiff diff, int startLine, int endLine, bool isOldSide)
{
var rs = new Models.TextDiffSelection();
var diff = TextDiff;
endLine = Math.Min(endLine, TextDiff.Lines.Count);
endLine = Math.Min(endLine, diff.Lines.Count);
if (Content is ViewModels.TwoSideTextDiff twoSides)
{
var target = isOldSide ? twoSides.Old : twoSides.New;
@ -1233,8 +1072,8 @@ namespace SourceGit.Views
var firstContent = target[firstContentLine];
var endContent = target[endContentLine];
startLine = TextDiff.Lines.IndexOf(firstContent) + 1;
endLine = TextDiff.Lines.IndexOf(endContent) + 1;
startLine = diff.Lines.IndexOf(firstContent) + 1;
endLine = diff.Lines.IndexOf(endContent) + 1;
}
rs.StartLine = startLine;

View file

@ -10,7 +10,7 @@
x:DataType="vm:WorkingCopy">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" MinWidth="300"/>
<ColumnDefinition Width="{Binding Source={x:Static vm:Preference.Instance}, Path=Layout.WorkingCopyLeftWidth, Mode=TwoWay}" MinWidth="300"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
@ -198,7 +198,9 @@
Height="28"
Margin="8,0,0,0"
Padding="8,0"
Command="{Binding Commit}"/>
Command="{Binding Commit}"
HotKey="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"
ToolTip.Tip="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"/>
<Button Grid.Column="5"
Classes="flat"
@ -207,6 +209,8 @@
Margin="8,0,0,0"
Padding="8,0"
Command="{Binding CommitWithPush}"
HotKey="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+Shift+Enter}"
ToolTip.Tip="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+Shift+Enter}"
IsVisible="{Binding IsCommitWithPushVisible}"/>
</Grid>
</Grid>