mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-19 19:34:58 +00:00
Merge branch 'release/v2025.18'
This commit is contained in:
commit
7dd1389c25
59 changed files with 2347 additions and 749 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -37,3 +37,5 @@ build/*.deb
|
|||
build/*.rpm
|
||||
build/*.AppImage
|
||||
SourceGit.app/
|
||||
build.command
|
||||
src/Properties/launchSettings.json
|
||||
|
|
149
TRANSLATION.md
149
TRANSLATION.md
|
@ -6,19 +6,41 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
### 
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in de_DE.axaml</summary>
|
||||
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
### 
|
||||
<details>
|
||||
<summary>Missing keys in es_ES.axaml</summary>
|
||||
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in fr_FR.axaml</summary>
|
||||
|
@ -42,13 +64,25 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
|
@ -61,52 +95,19 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in it_IT.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ja_JP.axaml</summary>
|
||||
|
@ -130,15 +131,27 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.Tags.OrderByNameDes
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
|
@ -151,7 +164,7 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in pt_BR.axaml</summary>
|
||||
|
@ -204,10 +217,15 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
|
@ -231,6 +249,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.Repository.Notifications.Clear
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.Skip
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByNameAsc
|
||||
|
@ -247,6 +266,12 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.Stash.AutoRestore
|
||||
- Text.Stash.AutoRestore.Tip
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
|
@ -261,9 +286,17 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
### 
|
||||
<details>
|
||||
<summary>Missing keys in ru_RU.axaml</summary>
|
||||
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Launcher.Pages
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ta_IN.axaml</summary>
|
||||
|
@ -287,13 +320,25 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.UpdateSubmodules.Target
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
|
@ -306,7 +351,7 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in uk_UA.axaml</summary>
|
||||
|
@ -326,13 +371,25 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfigureWorkspace.Name
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2025.17
|
||||
2025.18
|
|
@ -107,13 +107,24 @@ namespace SourceGit
|
|||
#region Utility Functions
|
||||
public static void ShowWindow(object data, bool showAsDialog)
|
||||
{
|
||||
var impl = (Views.ChromelessWindow target, bool isDialog) =>
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
{
|
||||
if (isDialog)
|
||||
target.ShowDialog(owner);
|
||||
else
|
||||
target.Show(owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Show();
|
||||
}
|
||||
};
|
||||
|
||||
if (data is Views.ChromelessWindow window)
|
||||
{
|
||||
if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
else
|
||||
window.Show();
|
||||
|
||||
impl(window, showAsDialog);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -130,10 +141,7 @@ namespace SourceGit
|
|||
if (window != null)
|
||||
{
|
||||
window.DataContext = data;
|
||||
if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
else
|
||||
window.Show();
|
||||
impl(window, showAsDialog);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace SourceGit.Commands
|
|||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
new Restore(repo) { Log = log }.Exec();
|
||||
if (includeIgnored)
|
||||
new Clean(repo) { Log = log }.Exec();
|
||||
|
|
|
@ -17,8 +17,10 @@ namespace SourceGit.Commands
|
|||
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
|
||||
}
|
||||
|
||||
public List<Models.Branch> Result()
|
||||
public List<Models.Branch> Result(out int localBranchesCount)
|
||||
{
|
||||
localBranchesCount = 0;
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
|
@ -34,6 +36,8 @@ namespace SourceGit.Commands
|
|||
branches.Add(b);
|
||||
if (!b.IsLocal)
|
||||
remoteBranches.Add(b.FullName);
|
||||
else
|
||||
localBranchesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -22,7 +23,10 @@ namespace SourceGit.Commands
|
|||
var outs = new List<Models.Change>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
|
||||
return outs;
|
||||
}
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -6,12 +7,12 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QuerySubmodules : Command
|
||||
{
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
[GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
|
||||
private static partial Regex REG_FORMAT_STATUS();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT_DIRTY();
|
||||
[GeneratedRegex(@"^submodule\.(\S*)\.(\w+)=(.*)$")]
|
||||
private static partial Regex REG_FORMAT_MODULE_INFO();
|
||||
|
||||
public QuerySubmodules(string repo)
|
||||
{
|
||||
|
@ -25,52 +26,117 @@ namespace SourceGit.Commands
|
|||
var submodules = new List<Models.Submodule>();
|
||||
var rs = ReadToEnd();
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var map = new Dictionary<string, Models.Submodule>();
|
||||
var needCheckLocalChanges = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT1().Match(line);
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
builder.Append($"\"{path}\" ");
|
||||
submodules.Add(new Models.Submodule() { Path = path });
|
||||
continue;
|
||||
}
|
||||
var stat = match.Groups[1].Value;
|
||||
var sha = match.Groups[2].Value;
|
||||
var path = match.Groups[3].Value;
|
||||
|
||||
match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
builder.Append($"\"{path}\" ");
|
||||
submodules.Add(new Models.Submodule() { Path = path });
|
||||
var module = new Models.Submodule() { Path = path, SHA = sha };
|
||||
switch (stat[0])
|
||||
{
|
||||
case '-':
|
||||
module.Status = Models.SubmoduleStatus.NotInited;
|
||||
break;
|
||||
case '+':
|
||||
module.Status = Models.SubmoduleStatus.RevisionChanged;
|
||||
break;
|
||||
case 'U':
|
||||
module.Status = Models.SubmoduleStatus.Unmerged;
|
||||
break;
|
||||
default:
|
||||
module.Status = Models.SubmoduleStatus.Normal;
|
||||
needCheckLocalChanges = true;
|
||||
break;
|
||||
}
|
||||
|
||||
map.Add(path, module);
|
||||
submodules.Add(module);
|
||||
}
|
||||
}
|
||||
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = "config --file .gitmodules --list";
|
||||
rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var modules = new Dictionary<string, ModuleInfo>();
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_MODULE_INFO().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups[1].Value;
|
||||
var key = match.Groups[2].Value;
|
||||
var val = match.Groups[3].Value;
|
||||
|
||||
if (!modules.TryGetValue(name, out var m))
|
||||
{
|
||||
m = new ModuleInfo();
|
||||
modules.Add(name, m);
|
||||
}
|
||||
|
||||
if (key.Equals("path", StringComparison.Ordinal))
|
||||
m.Path = val;
|
||||
else if (key.Equals("url", StringComparison.Ordinal))
|
||||
m.URL = val;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in modules)
|
||||
{
|
||||
if (map.TryGetValue(kv.Value.Path, out var m))
|
||||
m.URL = kv.Value.URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needCheckLocalChanges)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kv in map)
|
||||
{
|
||||
if (kv.Value.Status == Models.SubmoduleStatus.Normal)
|
||||
{
|
||||
builder.Append('"');
|
||||
builder.Append(kv.Key);
|
||||
builder.Append("\" ");
|
||||
}
|
||||
}
|
||||
|
||||
Args = $"--no-optional-locks status -uno --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
var dirty = new HashSet<string>();
|
||||
lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
var match = REG_FORMAT_DIRTY().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
dirty.Add(path);
|
||||
if (map.TryGetValue(path, out var m))
|
||||
m.Status = Models.SubmoduleStatus.Modified;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var submodule in submodules)
|
||||
submodule.IsDirty = dirty.Contains(submodule.Path);
|
||||
}
|
||||
|
||||
return submodules;
|
||||
}
|
||||
|
||||
private class ModuleInfo
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string URL { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
|
||||
Context = repo;
|
||||
WorkingDirectory = repo;
|
||||
Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
|
||||
Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
|
||||
}
|
||||
|
||||
public List<Models.Tag> Result()
|
||||
|
@ -25,16 +25,21 @@ namespace SourceGit.Commands
|
|||
foreach (var record in records)
|
||||
{
|
||||
var subs = record.Split('\0', StringSplitOptions.None);
|
||||
if (subs.Length != 5)
|
||||
if (subs.Length != 6)
|
||||
continue;
|
||||
|
||||
var message = subs[4].Trim();
|
||||
var name = subs[0].Substring(10);
|
||||
var message = subs[5].Trim();
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals(name, StringComparison.Ordinal))
|
||||
message = null;
|
||||
|
||||
tags.Add(new Models.Tag()
|
||||
{
|
||||
Name = subs[0].Substring(10),
|
||||
SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2],
|
||||
CreatorDate = ulong.Parse(subs[3]),
|
||||
Message = string.IsNullOrEmpty(message) ? null : message,
|
||||
Name = name,
|
||||
IsAnnotated = subs[1].Equals("tag", StringComparison.Ordinal),
|
||||
SHA = string.IsNullOrEmpty(subs[3]) ? subs[2] : subs[3],
|
||||
CreatorDate = ulong.Parse(subs[4]),
|
||||
Message = message,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace SourceGit.Commands
|
|||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag {name} {basedOn}";
|
||||
cmd.Args = $"tag --no-sign {name} {basedOn}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Converters
|
|||
public static class ListConverters
|
||||
{
|
||||
public static readonly FuncValueConverter<IList, string> ToCount =
|
||||
new FuncValueConverter<IList, string>(v => v == null ? " (0)" : $" ({v.Count})");
|
||||
new FuncValueConverter<IList, string>(v => v == null ? "(0)" : $"({v.Count})");
|
||||
|
||||
public static readonly FuncValueConverter<IList, bool> IsNullOrEmpty =
|
||||
new FuncValueConverter<IList, bool>(v => v == null || v.Count == 0);
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum SubmoduleStatus
|
||||
{
|
||||
Normal = 0,
|
||||
NotInited,
|
||||
RevisionChanged,
|
||||
Unmerged,
|
||||
Modified,
|
||||
}
|
||||
|
||||
public class Submodule
|
||||
{
|
||||
public string Path { get; set; } = "";
|
||||
public bool IsDirty { get; set; } = false;
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string SHA { get; set; } = string.Empty;
|
||||
public string URL { get; set; } = string.Empty;
|
||||
public SubmoduleStatus Status { get; set; } = SubmoduleStatus.Normal;
|
||||
public bool IsDirty => Status > SubmoduleStatus.NotInited;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace SourceGit.Models
|
|||
public class Tag : ObservableObject
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public bool IsAnnotated { get; set; } = false;
|
||||
public string SHA { get; set; } = string.Empty;
|
||||
public ulong CreatorDate { get; set; } = 0;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
|
|
@ -5,6 +5,8 @@ using System.IO;
|
|||
using System.Runtime.Versioning;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace SourceGit.Native
|
||||
{
|
||||
|
@ -16,6 +18,21 @@ namespace SourceGit.Native
|
|||
builder.With(new X11PlatformOptions() { EnableIme = true });
|
||||
}
|
||||
|
||||
public void SetupWindow(Window window)
|
||||
{
|
||||
if (OS.UseSystemWindowFrame)
|
||||
{
|
||||
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
|
||||
window.ExtendClientAreaToDecorationsHint = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
|
||||
window.ExtendClientAreaToDecorationsHint = true;
|
||||
window.Classes.Add("custom_window_frame");
|
||||
}
|
||||
}
|
||||
|
||||
public string FindGitExecutable()
|
||||
{
|
||||
return FindExecutable("git");
|
||||
|
|
|
@ -5,6 +5,8 @@ using System.IO;
|
|||
using System.Runtime.Versioning;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace SourceGit.Native
|
||||
{
|
||||
|
@ -36,6 +38,12 @@ namespace SourceGit.Native
|
|||
Environment.SetEnvironmentVariable("PATH", path);
|
||||
}
|
||||
|
||||
public void SetupWindow(Window window)
|
||||
{
|
||||
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome;
|
||||
window.ExtendClientAreaToDecorationsHint = true;
|
||||
}
|
||||
|
||||
public string FindGitExecutable()
|
||||
{
|
||||
var gitPathVariants = new List<string>() {
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Text;
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Native
|
||||
{
|
||||
|
@ -14,6 +15,7 @@ namespace SourceGit.Native
|
|||
public interface IBackend
|
||||
{
|
||||
void SetupApp(AppBuilder builder);
|
||||
void SetupWindow(Window window);
|
||||
|
||||
string FindGitExecutable();
|
||||
string FindTerminal(Models.ShellOrTerminal shell);
|
||||
|
@ -68,6 +70,12 @@ namespace SourceGit.Native
|
|||
set;
|
||||
} = [];
|
||||
|
||||
public static bool UseSystemWindowFrame
|
||||
{
|
||||
get => OperatingSystem.IsLinux() && _enableSystemWindowFrame;
|
||||
set => _enableSystemWindowFrame = value;
|
||||
}
|
||||
|
||||
static OS()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
|
@ -121,6 +129,11 @@ namespace SourceGit.Native
|
|||
ExternalTools = _backend.FindExternalTools();
|
||||
}
|
||||
|
||||
public static void SetupForWindow(Window window)
|
||||
{
|
||||
_backend.SetupWindow(window);
|
||||
}
|
||||
|
||||
public static string FindGitExecutable()
|
||||
{
|
||||
return _backend.FindGitExecutable();
|
||||
|
@ -225,5 +238,6 @@ namespace SourceGit.Native
|
|||
|
||||
private static IBackend _backend = null;
|
||||
private static string _gitExecutable = string.Empty;
|
||||
private static bool _enableSystemWindowFrame = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Text;
|
|||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Native
|
||||
|
@ -15,16 +16,12 @@ namespace SourceGit.Native
|
|||
[SupportedOSPlatform("windows")]
|
||||
internal class Windows : OS.IBackend
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RTL_OSVERSIONINFOEX
|
||||
internal struct RECT
|
||||
{
|
||||
internal uint dwOSVersionInfoSize;
|
||||
internal uint dwMajorVersion;
|
||||
internal uint dwMinorVersion;
|
||||
internal uint dwBuildNumber;
|
||||
internal uint dwPlatformId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
internal string szCSDVersion;
|
||||
public int left;
|
||||
public int top;
|
||||
public int right;
|
||||
public int bottom;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -36,9 +33,6 @@ namespace SourceGit.Native
|
|||
public int cyBottomHeight;
|
||||
}
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
|
||||
|
||||
|
@ -54,18 +48,79 @@ namespace SourceGit.Native
|
|||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
|
||||
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
|
||||
|
||||
public void SetupApp(AppBuilder builder)
|
||||
{
|
||||
// Fix drop shadow issue on Windows 10
|
||||
RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX();
|
||||
v.dwOSVersionInfoSize = (uint)Marshal.SizeOf<RTL_OSVERSIONINFOEX>();
|
||||
if (RtlGetVersion(ref v) == 0 && (v.dwMajorVersion < 10 || v.dwBuildNumber < 22000))
|
||||
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 22000, 0))
|
||||
{
|
||||
Window.WindowStateProperty.Changed.AddClassHandler<Window>((w, _) => FixWindowFrameOnWin10(w));
|
||||
Control.LoadedEvent.AddClassHandler<Window>((w, _) => FixWindowFrameOnWin10(w));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetupWindow(Window window)
|
||||
{
|
||||
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
|
||||
window.ExtendClientAreaToDecorationsHint = true;
|
||||
window.Classes.Add("fix_maximized_padding");
|
||||
|
||||
Win32Properties.AddWndProcHookCallback(window, (IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
|
||||
{
|
||||
// Custom WM_NCHITTEST
|
||||
if (msg == 0x0084)
|
||||
{
|
||||
handled = true;
|
||||
|
||||
if (window.WindowState == WindowState.FullScreen || window.WindowState == WindowState.Maximized)
|
||||
return 1; // HTCLIENT
|
||||
|
||||
var p = IntPtrToPixelPoint(lParam);
|
||||
GetWindowRect(hWnd, out var rcWindow);
|
||||
|
||||
var borderThinkness = (int)(4 * window.RenderScaling);
|
||||
int y = 1;
|
||||
int x = 1;
|
||||
if (p.X >= rcWindow.left && p.X < rcWindow.left + borderThinkness)
|
||||
x = 0;
|
||||
else if (p.X < rcWindow.right && p.X >= rcWindow.right - borderThinkness)
|
||||
x = 2;
|
||||
|
||||
if (p.Y >= rcWindow.top && p.Y < rcWindow.top + borderThinkness)
|
||||
y = 0;
|
||||
else if (p.Y < rcWindow.bottom && p.Y >= rcWindow.bottom - borderThinkness)
|
||||
y = 2;
|
||||
|
||||
var zone = y * 3 + x;
|
||||
switch (zone)
|
||||
{
|
||||
case 0:
|
||||
return 13; // HTTOPLEFT
|
||||
case 1:
|
||||
return 12; // HTTOP
|
||||
case 2:
|
||||
return 14; // HTTOPRIGHT
|
||||
case 3:
|
||||
return 10; // HTLEFT
|
||||
case 4:
|
||||
return 1; // HTCLIENT
|
||||
case 5:
|
||||
return 11; // HTRIGHT
|
||||
case 6:
|
||||
return 16; // HTBOTTOMLEFT
|
||||
case 7:
|
||||
return 15; // HTBOTTOM
|
||||
default:
|
||||
return 17; // HTBOTTOMRIGHT
|
||||
}
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
});
|
||||
}
|
||||
|
||||
public string FindGitExecutable()
|
||||
{
|
||||
var reg = Microsoft.Win32.RegistryKey.OpenBaseKey(
|
||||
|
@ -228,6 +283,12 @@ namespace SourceGit.Native
|
|||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
private PixelPoint IntPtrToPixelPoint(IntPtr param)
|
||||
{
|
||||
var v = IntPtr.Size == 4 ? param.ToInt32() : (int)(param.ToInt64() & 0xFFFFFFFF);
|
||||
return new PixelPoint((short)(v & 0xffff), (short)(v >> 16));
|
||||
}
|
||||
|
||||
#region EXTERNAL_EDITOR_FINDER
|
||||
private string FindVSCode()
|
||||
{
|
||||
|
|
|
@ -386,6 +386,8 @@
|
|||
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">Go to previous page</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Create new page</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.OpenPreferences" xml:space="preserve">Open Preferences dialog</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchWorkspace" xml:space="preserve">Switch active workspace</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchTab" xml:space="preserve">Switch active page</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>
|
||||
|
@ -406,6 +408,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Close search panel</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoNextMatch" xml:space="preserve">Find next match</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">Find previous match</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.OpenExternalMergeTool" xml:space="preserve">Open with external diff/merge tool</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.Search" xml:space="preserve">Open search panel</x:String>
|
||||
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">Discard</x:String>
|
||||
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">Stage</x:String>
|
||||
|
@ -427,6 +430,8 @@
|
|||
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Open in Browser</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String>
|
||||
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Workspaces</x:String>
|
||||
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">Pages</x:String>
|
||||
<x:String x:Key="Text.Merge" xml:space="preserve">Merge Branch</x:String>
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">Into:</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">Merge Option:</x:String>
|
||||
|
@ -616,6 +621,7 @@
|
|||
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">Message</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">Current Branch</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowSubmodulesAsTree" xml:space="preserve">Show Submodules as Tree</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Show Tags as Tree</x:String>
|
||||
<x:String x:Key="Text.Repository.Skip" xml:space="preserve">SKIP</x:String>
|
||||
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Statistics</x:String>
|
||||
|
@ -704,6 +710,12 @@
|
|||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">Relative Path:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">Relative folder to store this module.</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">Delete Submodule</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status" xml:space="preserve">STATUS</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Modified" xml:space="preserve">modified</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.NotInited" xml:space="preserve">not initialized</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.RevisionChanged" xml:space="preserve">revision changed</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Unmerged" xml:space="preserve">unmerged</x:String>
|
||||
<x:String x:Key="Text.Submodule.URL" xml:space="preserve">URL</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">OK</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">Copy Tag Name</x:String>
|
||||
<x:String x:Key="Text.TagCM.CopyMessage" xml:space="preserve">Copy Tag Message</x:String>
|
||||
|
|
|
@ -410,6 +410,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Cerrar panel de búsqueda</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoNextMatch" xml:space="preserve">Buscar siguiente coincidencia</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">Buscar coincidencia anterior</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.OpenExternalMergeTool" xml:space="preserve">Abrir con herramienta diff/merge externa</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.Search" xml:space="preserve">Abrir panel de búsqueda</x:String>
|
||||
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">Stage</x:String>
|
||||
|
@ -620,6 +621,7 @@
|
|||
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">Mensaje</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">Rama Actual</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowSubmodulesAsTree" xml:space="preserve">Mostrar Submódulos como Árbol</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Mostrar Etiquetas como Árbol</x:String>
|
||||
<x:String x:Key="Text.Repository.Skip" xml:space="preserve">OMITIR</x:String>
|
||||
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Estadísticas</x:String>
|
||||
|
@ -708,6 +710,12 @@
|
|||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">Ruta Relativa:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">Carpeta relativa para almacenar este módulo.</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">Eliminar Submódulo</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status" xml:space="preserve">ESTADO</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Modified" xml:space="preserve">modificado</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.NotInited" xml:space="preserve">no inicializado</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.RevisionChanged" xml:space="preserve">revisión cambiada</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Unmerged" xml:space="preserve">unmerged</x:String>
|
||||
<x:String x:Key="Text.Submodule.URL" xml:space="preserve">URL</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">OK</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">Copiar Nombre de la Etiqueta</x:String>
|
||||
<x:String x:Key="Text.TagCM.CopyMessage" xml:space="preserve">Copiar Mensaje de la Etiqueta</x:String>
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">NESSUN FILE ASSUNTO COME INVARIATO</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">RIMUOVI</x:String>
|
||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">FILE BINARIO NON SUPPORTATO!!!</x:String>
|
||||
<x:String x:Key="Text.Bisect" xml:space="preserve">Biseca</x:String>
|
||||
<x:String x:Key="Text.Bisect.Abort" xml:space="preserve">Annulla</x:String>
|
||||
<x:String x:Key="Text.Bisect.Bad" xml:space="preserve">Cattiva</x:String>
|
||||
<x:String x:Key="Text.Bisect.Detecting" xml:space="preserve">Bisecando. La HEAD corrente è buona o cattiva?</x:String>
|
||||
<x:String x:Key="Text.Bisect.Good" xml:space="preserve">Buona</x:String>
|
||||
<x:String x:Key="Text.Bisect.Skip" xml:space="preserve">Salta</x:String>
|
||||
<x:String x:Key="Text.Bisect.WaitingForRange" xml:space="preserve">Bisecando. Marca il commit corrente come buono o cattivo e fai checkout di un altro.</x:String>
|
||||
<x:String x:Key="Text.Blame" xml:space="preserve">Attribuisci</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">L'ATTRIBUZIONE SU QUESTO FILE NON È SUPPORTATA!!!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout ${0}$...</x:String>
|
||||
|
@ -79,6 +86,7 @@
|
|||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Modifiche Locali:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Scarta</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stasha e Ripristina</x:String>
|
||||
<x:String x:Key="Text.Checkout.RecurseSubmodules" xml:space="preserve">Aggiorna tutti i sottomoduli</x:String>
|
||||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Aggiungi sorgente al messaggio di commit</x:String>
|
||||
|
@ -103,8 +111,11 @@
|
|||
<x:String x:Key="Text.CommitCM.CherryPickMultiple" xml:space="preserve">Cherry-Pick...</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Confronta con HEAD</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Confronta con Worktree</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyAuthor" xml:space="preserve">Autore</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyCommitter" xml:space="preserve">Committer</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyInfo" xml:space="preserve">Informazioni</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySubject" xml:space="preserve">Oggetto</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CustomAction" xml:space="preserve">Azione Personalizzata</x:String>
|
||||
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Riallinea Interattivamente ${0}$ fino a Qui</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Merge" xml:space="preserve">Unisci a ${0}$</x:String>
|
||||
|
@ -136,6 +147,7 @@
|
|||
<x:String x:Key="Text.CommitDetail.Info.SHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">Apri nel Browser</x:String>
|
||||
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Descrizione</x:String>
|
||||
<x:String x:Key="Text.CommitMessageTextBox.SubjectCount" xml:space="preserve">OGGETTO</x:String>
|
||||
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Inserisci l'oggetto del commit</x:String>
|
||||
<x:String x:Key="Text.Configure" xml:space="preserve">Configura Repository</x:String>
|
||||
<x:String x:Key="Text.Configure.CommitMessageTemplate" xml:space="preserve">TEMPLATE DI COMMIT</x:String>
|
||||
|
@ -157,6 +169,7 @@
|
|||
<x:String x:Key="Text.Configure.Git.AutoFetch" xml:space="preserve">Recupera automaticamente i remoti</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.AutoFetchIntervalSuffix" xml:space="preserve">Minuto/i</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.DefaultRemote" xml:space="preserve">Remoto Predefinito</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.PreferredMergeMode" xml:space="preserve">Modalità di Merge Preferita</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">TRACCIAMENTO ISSUE</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker.AddSampleAzure" xml:space="preserve">Aggiungi una regola di esempio per Azure DevOps</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGiteeIssue" xml:space="preserve">Aggiungi una regola di esempio per un Issue Gitee</x:String>
|
||||
|
@ -181,6 +194,10 @@
|
|||
<x:String x:Key="Text.ConfigureWorkspace.Color" xml:space="preserve">Colore</x:String>
|
||||
<x:String x:Key="Text.ConfigureWorkspace.Name" xml:space="preserve">Nome</x:String>
|
||||
<x:String x:Key="Text.ConfigureWorkspace.Restore" xml:space="preserve">Ripristina schede all'avvio</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.Continue" xml:space="preserve">CONTINUA</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.NoLocalChanges" xml:space="preserve">Trovato un commit vuoto! Vuoi procedere (--allow-empty)?</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.StageAllThenCommit" xml:space="preserve">STAGE DI TUTTO E COMMITTA</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.WithLocalChanges" xml:space="preserve">Trovato un commit vuoto! Vuoi procedere (--allow-empty) o fare lo stage di tutto e committare?</x:String>
|
||||
<x:String x:Key="Text.ConventionalCommit" xml:space="preserve">Guida Commit Convenzionali</x:String>
|
||||
<x:String x:Key="Text.ConventionalCommit.BreakingChanges" xml:space="preserve">Modifica Sostanziale:</x:String>
|
||||
<x:String x:Key="Text.ConventionalCommit.ClosedIssue" xml:space="preserve">Issue Chiusa:</x:String>
|
||||
|
@ -190,6 +207,7 @@
|
|||
<x:String x:Key="Text.ConventionalCommit.Type" xml:space="preserve">Tipo di Modifica:</x:String>
|
||||
<x:String x:Key="Text.Copy" xml:space="preserve">Copia</x:String>
|
||||
<x:String x:Key="Text.CopyAllText" xml:space="preserve">Copia Tutto il Testo</x:String>
|
||||
<x:String x:Key="Text.CopyFullPath" xml:space="preserve">Copia Intero Percorso</x:String>
|
||||
<x:String x:Key="Text.CopyPath" xml:space="preserve">Copia Percorso</x:String>
|
||||
<x:String x:Key="Text.CreateBranch" xml:space="preserve">Crea Branch...</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.BasedOn" xml:space="preserve">Basato Su:</x:String>
|
||||
|
@ -309,6 +327,8 @@
|
|||
<x:String x:Key="Text.GitFlow.FinishHotfix" xml:space="preserve">FLOW - Completa Hotfix</x:String>
|
||||
<x:String x:Key="Text.GitFlow.FinishRelease" xml:space="preserve">FLOW - Completa Rilascio</x:String>
|
||||
<x:String x:Key="Text.GitFlow.FinishTarget" xml:space="preserve">Target:</x:String>
|
||||
<x:String x:Key="Text.GitFlow.FinishWithPush" xml:space="preserve">Invia al remote dopo aver finito</x:String>
|
||||
<x:String x:Key="Text.GitFlow.FinishWithSquash" xml:space="preserve">Esegui squash durante il merge</x:String>
|
||||
<x:String x:Key="Text.GitFlow.Hotfix" xml:space="preserve">Hotfix:</x:String>
|
||||
<x:String x:Key="Text.GitFlow.HotfixPrefix" xml:space="preserve">Prefisso Hotfix:</x:String>
|
||||
<x:String x:Key="Text.GitFlow.Init" xml:space="preserve">Inizializza Git-Flow</x:String>
|
||||
|
@ -390,6 +410,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Chiudi il pannello di ricerca</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoNextMatch" xml:space="preserve">Trova il prossimo risultato</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">Trova il risultato precedente</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.OpenExternalMergeTool" xml:space="preserve">Apri con uno strumento di diff/merge esterno</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.Search" xml:space="preserve">Apri il pannello di ricerca</x:String>
|
||||
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">Scarta</x:String>
|
||||
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">Aggiungi in stage</x:String>
|
||||
|
@ -476,6 +497,7 @@
|
|||
<x:String x:Key="Text.Preferences.General.MaxHistoryCommits" xml:space="preserve">Numero massimo di commit nella cronologia</x:String>
|
||||
<x:String x:Key="Text.Preferences.General.ShowAuthorTime" xml:space="preserve">Mostra nel grafico l'orario dell'autore anziché quello del commit</x:String>
|
||||
<x:String x:Key="Text.Preferences.General.ShowChildren" xml:space="preserve">Mostra i figli nei dettagli del commit</x:String>
|
||||
<x:String x:Key="Text.Preferences.General.ShowTagsInGraph" xml:space="preserve">Mostra i tag nel grafico dei commit</x:String>
|
||||
<x:String x:Key="Text.Preferences.General.SubjectGuideLength" xml:space="preserve">Lunghezza Guida Oggetto</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git" xml:space="preserve">GIT</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.CRLF" xml:space="preserve">Abilita Auto CRLF</x:String>
|
||||
|
@ -483,6 +505,7 @@
|
|||
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">Email Utente</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">Email utente Git globale</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">Abilita --prune durante il fetch</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.IgnoreCRAtEOLInDiff" xml:space="preserve">Abilita --ignore-cr-at-eol nel diff</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Invalid" xml:space="preserve">Questa applicazione richiede Git (>= 2.23.0)</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">Percorso Installazione</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">Abilita la verifica HTTP SSL</x:String>
|
||||
|
@ -556,6 +579,9 @@
|
|||
<x:String x:Key="Text.RenameBranch.Target" xml:space="preserve">Branch:</x:String>
|
||||
<x:String x:Key="Text.Repository.Abort" xml:space="preserve">ANNULLA</x:String>
|
||||
<x:String x:Key="Text.Repository.AutoFetching" xml:space="preserve">Recupero automatico delle modifiche dai remoti...</x:String>
|
||||
<x:String x:Key="Text.Repository.BranchSort" xml:space="preserve">Ordina</x:String>
|
||||
<x:String x:Key="Text.Repository.BranchSort.ByCommitterDate" xml:space="preserve">Per data del committer</x:String>
|
||||
<x:String x:Key="Text.Repository.BranchSort.ByName" xml:space="preserve">Per nome</x:String>
|
||||
<x:String x:Key="Text.Repository.Clean" xml:space="preserve">Pulizia (GC e Potatura)</x:String>
|
||||
<x:String x:Key="Text.Repository.CleanTips" xml:space="preserve">Esegui il comando `git gc` per questo repository.</x:String>
|
||||
<x:String x:Key="Text.Repository.ClearAllCommitsFilter" xml:space="preserve">Cancella tutto</x:String>
|
||||
|
@ -589,11 +615,13 @@
|
|||
<x:String x:Key="Text.Repository.Remotes.Add" xml:space="preserve">AGGIUNGI REMOTO</x:String>
|
||||
<x:String x:Key="Text.Repository.Search" xml:space="preserve">Cerca Commit</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.ByAuthor" xml:space="preserve">Autore</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.ByCommitter" xml:space="preserve">Committente</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.ByCommitter" xml:space="preserve">Committer</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.ByContent" xml:space="preserve">Contenuto</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.ByFile" xml:space="preserve">File</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">Messaggio</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">Branch Corrente</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowSubmodulesAsTree" xml:space="preserve">Mostra i Sottomoduli Come Albero</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Mostra Tag come Albero</x:String>
|
||||
<x:String x:Key="Text.Repository.Skip" xml:space="preserve">SALTA</x:String>
|
||||
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Statistiche</x:String>
|
||||
|
@ -608,6 +636,8 @@
|
|||
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">Ordina</x:String>
|
||||
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Apri nel Terminale</x:String>
|
||||
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">Usa tempo relativo nello storico</x:String>
|
||||
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">Visualizza i Log</x:String>
|
||||
<x:String x:Key="Text.Repository.Visit" xml:space="preserve">Visita '{0}' nel Browser</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">WORKTREE</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">AGGIUNGI WORKTREE</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">POTATURA</x:String>
|
||||
|
@ -680,6 +710,12 @@
|
|||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">Percorso Relativo:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">Cartella relativa per memorizzare questo modulo.</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">Elimina Sottomodulo</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status" xml:space="preserve">STATO</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Modified" xml:space="preserve">modificato</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.NotInited" xml:space="preserve">non inizializzato</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.RevisionChanged" xml:space="preserve">revisione cambiata</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Unmerged" xml:space="preserve">non unito</x:String>
|
||||
<x:String x:Key="Text.Submodule.URL" xml:space="preserve">URL</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">OK</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">Copia Nome Tag</x:String>
|
||||
<x:String x:Key="Text.TagCM.CopyMessage" xml:space="preserve">Copia Messaggio Tag</x:String>
|
||||
|
@ -693,6 +729,10 @@
|
|||
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Sottomodulo:</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Usa opzione --remote</x:String>
|
||||
<x:String x:Key="Text.URL" xml:space="preserve">URL:</x:String>
|
||||
<x:String x:Key="Text.ViewLogs" xml:space="preserve">Log</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Clear" xml:space="preserve">CANCELLA TUTTO</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.CopyLog" xml:space="preserve">Copia</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Delete" xml:space="preserve">Elimina</x:String>
|
||||
<x:String x:Key="Text.Warn" xml:space="preserve">Avviso</x:String>
|
||||
<x:String x:Key="Text.Welcome" xml:space="preserve">Pagina di Benvenuto</x:String>
|
||||
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Crea Gruppo</x:String>
|
||||
|
@ -722,8 +762,13 @@
|
|||
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">Attiva evento click</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">Commit (Modifica)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">Stage di tutte le modifiche e fai il commit</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter" xml:space="preserve">Hai stageato {0} file ma solo {1} file mostrati ({2} file sono stati filtrati). Vuoi procedere?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLITTI RILEVATI</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeTool" xml:space="preserve">APRI STRUMENTO DI MERGE ESTERNO</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts" xml:space="preserve">APRI TUTTI I CONFLITTI NELLO STRUMENTO DI MERGE ESTERNO</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">CONFLITTI NEI FILE RISOLTI</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseMine" xml:space="preserve">USO IL MIO</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseTheirs" xml:space="preserve">USO IL LORO</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDI FILE NON TRACCIATI</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NESSUN MESSAGGIO RECENTE INSERITO</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">NESSUN TEMPLATE DI COMMIT</x:String>
|
||||
|
|
|
@ -390,6 +390,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">Перейти на предыдущую вкладку</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Создать новую вкладку</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.OpenPreferences" xml:space="preserve">Открыть диалоговое окно настроек</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchWorkspace" 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>
|
||||
|
@ -410,6 +411,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Закрыть панель поиска</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoNextMatch" xml:space="preserve">Найти следующее совпадение</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">Найти предыдущее совпадение</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.OpenExternalMergeTool" xml:space="preserve">Открыть с внешним инструментом сравнения/слияние</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.Search" xml:space="preserve">Открыть панель поиска</x:String>
|
||||
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">Отклонить</x:String>
|
||||
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">Сформировать</x:String>
|
||||
|
@ -431,6 +433,7 @@
|
|||
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Открыть в браузере</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ОШИБКА</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">УВЕДОМЛЕНИЕ</x:String>
|
||||
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Рабочие места</x:String>
|
||||
<x:String x:Key="Text.Merge" xml:space="preserve">Влить ветку</x:String>
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">В:</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">Опции слияния:</x:String>
|
||||
|
@ -620,6 +623,7 @@
|
|||
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">Сообщение</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">Текущая ветка</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowSubmodulesAsTree" xml:space="preserve">Показывать подмодули как дерево</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Показывать метки как катлог</x:String>
|
||||
<x:String x:Key="Text.Repository.Skip" xml:space="preserve">ПРОПУСТИТЬ</x:String>
|
||||
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Статистикa </x:String>
|
||||
|
@ -708,6 +712,12 @@
|
|||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">Каталог:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">Относительный путь для хранения подмодуля.</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">Удалить подмодуль</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status" xml:space="preserve">СОСТОЯНИЕ</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Modified" xml:space="preserve">изменён</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.NotInited" xml:space="preserve">не создан</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.RevisionChanged" xml:space="preserve">ревизия изменена</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Unmerged" xml:space="preserve">не слита</x:String>
|
||||
<x:String x:Key="Text.Submodule.URL" xml:space="preserve">URL-адрес</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">ОК</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">Копировать имя метки</x:String>
|
||||
<x:String x:Key="Text.TagCM.CopyMessage" xml:space="preserve">Копировать сообщение с метки</x:String>
|
||||
|
|
|
@ -390,6 +390,8 @@
|
|||
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">切换到上一个页面</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新建页面</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.OpenPreferences" xml:space="preserve">打开偏好设置面板</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchWorkspace" xml:space="preserve">切换工作区</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchTab" 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>
|
||||
|
@ -410,6 +412,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">关闭搜索</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoNextMatch" xml:space="preserve">定位到下一个匹配搜索的位置</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">定位到上一个匹配搜索的位置</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.OpenExternalMergeTool" xml:space="preserve">使用外部比对工具查看</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.Search" xml:space="preserve">打开搜索</x:String>
|
||||
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">丢弃</x:String>
|
||||
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">暂存</x:String>
|
||||
|
@ -431,6 +434,8 @@
|
|||
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在浏览器中访问</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">出错了</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系统提示</x:String>
|
||||
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">工作区列表</x:String>
|
||||
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">页面列表</x:String>
|
||||
<x:String x:Key="Text.Merge" xml:space="preserve">合并分支</x:String>
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目标分支 :</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">合并方式 :</x:String>
|
||||
|
@ -620,6 +625,7 @@
|
|||
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">提交信息</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">提交指纹</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">仅在当前分支中查找</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowSubmodulesAsTree" xml:space="preserve">以树型结构展示</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">以树型结构展示</x:String>
|
||||
<x:String x:Key="Text.Repository.Skip" xml:space="preserve">跳过此提交</x:String>
|
||||
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">提交统计</x:String>
|
||||
|
@ -708,6 +714,12 @@
|
|||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">相对仓库路径 :</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">本地存放的相对路径。</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">删除子模块</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status" xml:space="preserve">状态</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Modified" xml:space="preserve">未提交修改</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.NotInited" xml:space="preserve">未初始化</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.RevisionChanged" xml:space="preserve">SHA变更</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Unmerged" xml:space="preserve">未解决冲突</x:String>
|
||||
<x:String x:Key="Text.Submodule.URL" xml:space="preserve">仓库</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">确 定</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">复制标签名</x:String>
|
||||
<x:String x:Key="Text.TagCM.CopyMessage" xml:space="preserve">复制标签信息</x:String>
|
||||
|
|
|
@ -390,6 +390,8 @@
|
|||
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">切換到上一個頁面</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新增頁面</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.OpenPreferences" xml:space="preserve">開啟偏好設定面板</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchWorkspace" xml:space="preserve">切換工作區</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.SwitchTab" 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>
|
||||
|
@ -410,6 +412,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">關閉搜尋面板</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoNextMatch" xml:space="preserve">前往下一個搜尋相符的位置</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">前往上一個搜尋相符的位置</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.OpenExternalMergeTool" xml:space="preserve">使用外部比對工具檢視</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.TextEditor.Search" xml:space="preserve">開啟搜尋面板</x:String>
|
||||
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">捨棄</x:String>
|
||||
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">暫存</x:String>
|
||||
|
@ -431,6 +434,8 @@
|
|||
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在瀏覽器中開啟連結</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">發生錯誤</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系統提示</x:String>
|
||||
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">工作區列表</x:String>
|
||||
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">頁面列表</x:String>
|
||||
<x:String x:Key="Text.Merge" xml:space="preserve">合併分支</x:String>
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目標分支:</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">合併方式:</x:String>
|
||||
|
@ -620,6 +625,7 @@
|
|||
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">提交訊息</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">提交編號</x:String>
|
||||
<x:String x:Key="Text.Repository.Search.InCurrentBranch" xml:space="preserve">僅搜尋目前分支</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowSubmodulesAsTree" xml:space="preserve">以樹型結構展示</x:String>
|
||||
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">以樹型結構展示</x:String>
|
||||
<x:String x:Key="Text.Repository.Skip" xml:space="preserve">跳過此提交</x:String>
|
||||
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">提交統計</x:String>
|
||||
|
@ -708,6 +714,12 @@
|
|||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">相對存放庫路徑:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">本機存放的相對路徑。</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">刪除子模組</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status" xml:space="preserve">狀態</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Modified" xml:space="preserve">未提交變更</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.NotInited" xml:space="preserve">未初始化</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.RevisionChanged" xml:space="preserve">SHA 變更</x:String>
|
||||
<x:String x:Key="Text.Submodule.Status.Unmerged" xml:space="preserve">未解決的衝突</x:String>
|
||||
<x:String x:Key="Text.Submodule.URL" xml:space="preserve">存放庫</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">確 定</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">複製標籤名稱</x:String>
|
||||
<x:String x:Key="Text.TagCM.CopyMessage" xml:space="preserve">複製標籤訊息</x:String>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</Style>
|
||||
|
||||
<Style Selector="Window[WindowState=Maximized].fix_maximized_padding">
|
||||
<Setter Property="Padding" Value="6"/>
|
||||
<Setter Property="Padding" Value="8,6,8,8"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Window.custom_window_frame">
|
||||
|
@ -1235,7 +1235,7 @@
|
|||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ToggleButton.tag_display_mode">
|
||||
<Style Selector="ToggleButton.show_as_tree">
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace SourceGit.ViewModels
|
|||
public int Depth { get; set; } = 0;
|
||||
public bool IsSelected { get; set; } = false;
|
||||
public List<BranchTreeNode> Children { get; private set; } = new List<BranchTreeNode>();
|
||||
public int Counter { get; set; } = 0;
|
||||
|
||||
public Models.FilterMode FilterMode
|
||||
{
|
||||
|
@ -48,9 +49,23 @@ namespace SourceGit.ViewModels
|
|||
get => Backend is Models.Branch { IsUpstreamGone: true };
|
||||
}
|
||||
|
||||
public string BranchesCount
|
||||
{
|
||||
get => Counter > 0 ? $"({Counter})" : string.Empty;
|
||||
}
|
||||
|
||||
public string Tooltip
|
||||
{
|
||||
get => Backend is Models.Branch b ? b.FriendlyName : null;
|
||||
get
|
||||
{
|
||||
if (Backend is Models.Branch b)
|
||||
return b.FriendlyName;
|
||||
|
||||
if (Backend is Models.Remote r)
|
||||
return r.URL;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Models.FilterMode _filterMode = Models.FilterMode.None;
|
||||
|
@ -102,12 +117,14 @@ namespace SourceGit.ViewModels
|
|||
if (branch.IsLocal)
|
||||
{
|
||||
MakeBranchNode(branch, _locals, folders, "refs/heads", bForceExpanded);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
var rk = $"refs/remotes/{branch.Remote}";
|
||||
if (folders.TryGetValue(rk, out var remote))
|
||||
{
|
||||
var remote = _remotes.Find(x => x.Name == branch.Remote);
|
||||
if (remote != null)
|
||||
MakeBranchNode(branch, remote.Children, folders, $"refs/remotes/{remote.Name}", bForceExpanded);
|
||||
remote.Counter++;
|
||||
MakeBranchNode(branch, remote.Children, folders, rk, bForceExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,6 +175,7 @@ namespace SourceGit.ViewModels
|
|||
if (folders.TryGetValue(folder, out var val))
|
||||
{
|
||||
lastFolder = val;
|
||||
lastFolder.Counter++;
|
||||
lastFolder.TimeToSort = Math.Max(lastFolder.TimeToSort, time);
|
||||
if (!lastFolder.IsExpanded)
|
||||
lastFolder.IsExpanded |= (branch.IsCurrent || _expanded.Contains(folder));
|
||||
|
@ -170,6 +188,7 @@ namespace SourceGit.ViewModels
|
|||
Path = folder,
|
||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||
TimeToSort = time,
|
||||
Counter = 1,
|
||||
};
|
||||
roots.Add(lastFolder);
|
||||
folders.Add(folder, lastFolder);
|
||||
|
@ -182,6 +201,7 @@ namespace SourceGit.ViewModels
|
|||
Path = folder,
|
||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||
TimeToSort = time,
|
||||
Counter = 1,
|
||||
};
|
||||
lastFolder.Children.Add(cur);
|
||||
folders.Add(folder, cur);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -387,7 +387,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var c in selected)
|
||||
builder.AppendLine($"{c.SHA.Substring(0, 10)} - {c.Subject}");
|
||||
builder.AppendLine($"{c.SHA.AsSpan(0, 10)} - {c.Subject}");
|
||||
|
||||
App.CopyText(builder.ToString());
|
||||
e.Handled = true;
|
||||
|
@ -780,7 +780,7 @@ namespace SourceGit.ViewModels
|
|||
copyInfo.Icon = App.CreateMenuIcon("Icons.Info");
|
||||
copyInfo.Click += (_, e) =>
|
||||
{
|
||||
App.CopyText($"{commit.SHA.Substring(0, 10)} - {commit.Subject}");
|
||||
App.CopyText($"{commit.SHA.AsSpan(0, 10)} - {commit.Subject}");
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -44,6 +44,12 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public IDisposable Switcher
|
||||
{
|
||||
get => _switcher;
|
||||
private set => SetProperty(ref _switcher, value);
|
||||
}
|
||||
|
||||
public Launcher(string startupRepo)
|
||||
{
|
||||
_ignoreIndexChange = true;
|
||||
|
@ -130,6 +136,85 @@ namespace SourceGit.ViewModels
|
|||
_ignoreIndexChange = false;
|
||||
}
|
||||
|
||||
public void OpenWorkspaceSwitcher()
|
||||
{
|
||||
Switcher = new WorkspaceSwitcher(this);
|
||||
}
|
||||
|
||||
public void OpenTabSwitcher()
|
||||
{
|
||||
Switcher = new LauncherPageSwitcher(this);
|
||||
}
|
||||
|
||||
public void CancelSwitcher()
|
||||
{
|
||||
Switcher?.Dispose();
|
||||
Switcher = null;
|
||||
}
|
||||
|
||||
public void SwitchWorkspace(Workspace to)
|
||||
{
|
||||
if (to == null || to.IsActive)
|
||||
return;
|
||||
|
||||
foreach (var one in Pages)
|
||||
{
|
||||
if (!one.CanCreatePopup() || one.Data is Repository { IsAutoFetching: true })
|
||||
{
|
||||
App.RaiseException(null, "You have unfinished task(s) in opened pages. Please wait!!!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ignoreIndexChange = true;
|
||||
|
||||
var pref = Preferences.Instance;
|
||||
foreach (var w in pref.Workspaces)
|
||||
w.IsActive = false;
|
||||
|
||||
ActiveWorkspace = to;
|
||||
to.IsActive = true;
|
||||
|
||||
foreach (var one in Pages)
|
||||
CloseRepositoryInTab(one, false);
|
||||
|
||||
Pages.Clear();
|
||||
AddNewTab();
|
||||
|
||||
var repos = to.Repositories.ToArray();
|
||||
foreach (var repo in repos)
|
||||
{
|
||||
var node = pref.FindNode(repo);
|
||||
if (node == null)
|
||||
{
|
||||
node = new RepositoryNode()
|
||||
{
|
||||
Id = repo,
|
||||
Name = Path.GetFileName(repo),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
}
|
||||
|
||||
OpenRepositoryInTab(node, null);
|
||||
}
|
||||
|
||||
var activeIdx = to.ActiveIdx;
|
||||
if (activeIdx >= 0 && activeIdx < Pages.Count)
|
||||
{
|
||||
ActivePage = Pages[activeIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivePage = Pages[0];
|
||||
to.ActiveIdx = 0;
|
||||
}
|
||||
|
||||
_ignoreIndexChange = false;
|
||||
Preferences.Instance.Save();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
public void AddNewTab()
|
||||
{
|
||||
var page = new LauncherPage();
|
||||
|
@ -494,66 +579,6 @@ namespace SourceGit.ViewModels
|
|||
return new Commands.QueryGitDir(repo).Result();
|
||||
}
|
||||
|
||||
private void SwitchWorkspace(Workspace to)
|
||||
{
|
||||
foreach (var one in Pages)
|
||||
{
|
||||
if (!one.CanCreatePopup() || one.Data is Repository { IsAutoFetching: true })
|
||||
{
|
||||
App.RaiseException(null, "You have unfinished task(s) in opened pages. Please wait!!!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_ignoreIndexChange = true;
|
||||
|
||||
var pref = Preferences.Instance;
|
||||
foreach (var w in pref.Workspaces)
|
||||
w.IsActive = false;
|
||||
|
||||
ActiveWorkspace = to;
|
||||
to.IsActive = true;
|
||||
|
||||
foreach (var one in Pages)
|
||||
CloseRepositoryInTab(one, false);
|
||||
|
||||
Pages.Clear();
|
||||
AddNewTab();
|
||||
|
||||
var repos = to.Repositories.ToArray();
|
||||
foreach (var repo in repos)
|
||||
{
|
||||
var node = pref.FindNode(repo);
|
||||
if (node == null)
|
||||
{
|
||||
node = new RepositoryNode()
|
||||
{
|
||||
Id = repo,
|
||||
Name = Path.GetFileName(repo),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
}
|
||||
|
||||
OpenRepositoryInTab(node, null);
|
||||
}
|
||||
|
||||
var activeIdx = to.ActiveIdx;
|
||||
if (activeIdx >= 0 && activeIdx < Pages.Count)
|
||||
{
|
||||
ActivePage = Pages[activeIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivePage = Pages[0];
|
||||
to.ActiveIdx = 0;
|
||||
}
|
||||
|
||||
_ignoreIndexChange = false;
|
||||
Preferences.Instance.Save();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void CloseRepositoryInTab(LauncherPage page, bool removeFromWorkspace = true)
|
||||
{
|
||||
if (page.Data is Repository repo)
|
||||
|
@ -573,7 +598,7 @@ namespace SourceGit.ViewModels
|
|||
return;
|
||||
|
||||
var workspace = _activeWorkspace.Name;
|
||||
if (_activePage is { Data: Repository repo })
|
||||
if (_activePage is { Data: Repository })
|
||||
{
|
||||
var node = _activePage.Node;
|
||||
var name = node.Name;
|
||||
|
@ -599,5 +624,6 @@ namespace SourceGit.ViewModels
|
|||
private LauncherPage _activePage = null;
|
||||
private bool _ignoreIndexChange = false;
|
||||
private string _title = string.Empty;
|
||||
private IDisposable _switcher = null;
|
||||
}
|
||||
}
|
||||
|
|
83
src/ViewModels/LauncherPageSwitcher.cs
Normal file
83
src/ViewModels/LauncherPageSwitcher.cs
Normal file
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class LauncherPageSwitcher : ObservableObject, IDisposable
|
||||
{
|
||||
public List<LauncherPage> VisiblePages
|
||||
{
|
||||
get => _visiblePages;
|
||||
private set => SetProperty(ref _visiblePages, value);
|
||||
}
|
||||
|
||||
public string SearchFilter
|
||||
{
|
||||
get => _searchFilter;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _searchFilter, value))
|
||||
UpdateVisiblePages();
|
||||
}
|
||||
}
|
||||
|
||||
public LauncherPage SelectedPage
|
||||
{
|
||||
get => _selectedPage;
|
||||
set => SetProperty(ref _selectedPage, value);
|
||||
}
|
||||
|
||||
public LauncherPageSwitcher(Launcher launcher)
|
||||
{
|
||||
_launcher = launcher;
|
||||
UpdateVisiblePages();
|
||||
}
|
||||
|
||||
public void ClearFilter()
|
||||
{
|
||||
SearchFilter = string.Empty;
|
||||
}
|
||||
|
||||
public void Switch()
|
||||
{
|
||||
_launcher.ActivePage = _selectedPage ?? _launcher.ActivePage;
|
||||
_launcher.CancelSwitcher();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_visiblePages.Clear();
|
||||
_selectedPage = null;
|
||||
_searchFilter = string.Empty;
|
||||
}
|
||||
|
||||
private void UpdateVisiblePages()
|
||||
{
|
||||
var visible = new List<LauncherPage>();
|
||||
if (string.IsNullOrEmpty(_searchFilter))
|
||||
{
|
||||
visible.AddRange(_launcher.Pages);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var page in _launcher.Pages)
|
||||
{
|
||||
if (page.Node.Name.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase) ||
|
||||
(page.Node.IsRepository && page.Node.Id.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
visible.Add(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VisiblePages = visible;
|
||||
SelectedPage = visible.Count > 0 ? visible[0] : null;
|
||||
}
|
||||
|
||||
private Launcher _launcher = null;
|
||||
private List<LauncherPage> _visiblePages = [];
|
||||
private string _searchFilter = string.Empty;
|
||||
private LauncherPage _selectedPage = null;
|
||||
}
|
||||
}
|
|
@ -91,8 +91,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public bool UseSystemWindowFrame
|
||||
{
|
||||
get => _useSystemWindowFrame;
|
||||
set => SetProperty(ref _useSystemWindowFrame, value);
|
||||
get => Native.OS.UseSystemWindowFrame;
|
||||
set => Native.OS.UseSystemWindowFrame = value;
|
||||
}
|
||||
|
||||
public double DefaultFontSize
|
||||
|
@ -178,9 +178,9 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public bool ShowTagsAsTree
|
||||
{
|
||||
get => _showTagsAsTree;
|
||||
set => SetProperty(ref _showTagsAsTree, value);
|
||||
}
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public bool ShowTagsInGraph
|
||||
{
|
||||
|
@ -188,6 +188,12 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _showTagsInGraph, value);
|
||||
}
|
||||
|
||||
public bool ShowSubmodulesAsTree
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public bool UseTwoColumnsLayoutInHistories
|
||||
{
|
||||
get => _useTwoColumnsLayoutInHistories;
|
||||
|
@ -656,7 +662,6 @@ namespace SourceGit.ViewModels
|
|||
private string _defaultFontFamily = string.Empty;
|
||||
private string _monospaceFontFamily = string.Empty;
|
||||
private bool _onlyUseMonoFontInEditor = false;
|
||||
private bool _useSystemWindowFrame = false;
|
||||
private double _defaultFontSize = 13;
|
||||
private double _editorFontSize = 13;
|
||||
private int _editorTabWidth = 4;
|
||||
|
@ -672,7 +677,6 @@ namespace SourceGit.ViewModels
|
|||
private double _lastCheckUpdateTime = 0;
|
||||
private string _ignoreUpdateTag = string.Empty;
|
||||
|
||||
private bool _showTagsAsTree = false;
|
||||
private bool _showTagsInGraph = true;
|
||||
private bool _useTwoColumnsLayoutInHistories = false;
|
||||
private bool _displayTimeAsPeriodInHistories = false;
|
||||
|
|
|
@ -198,7 +198,21 @@ namespace SourceGit.ViewModels
|
|||
private set => SetProperty(ref _tags, value);
|
||||
}
|
||||
|
||||
public List<Models.Tag> VisibleTags
|
||||
public bool ShowTagsAsTree
|
||||
{
|
||||
get => Preferences.Instance.ShowTagsAsTree;
|
||||
set
|
||||
{
|
||||
if (value != Preferences.Instance.ShowTagsAsTree)
|
||||
{
|
||||
Preferences.Instance.ShowTagsAsTree = value;
|
||||
VisibleTags = BuildVisibleTags();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object VisibleTags
|
||||
{
|
||||
get => _visibleTags;
|
||||
private set => SetProperty(ref _visibleTags, value);
|
||||
|
@ -210,7 +224,21 @@ namespace SourceGit.ViewModels
|
|||
private set => SetProperty(ref _submodules, value);
|
||||
}
|
||||
|
||||
public List<Models.Submodule> VisibleSubmodules
|
||||
public bool ShowSubmodulesAsTree
|
||||
{
|
||||
get => Preferences.Instance.ShowSubmodulesAsTree;
|
||||
set
|
||||
{
|
||||
if (value != Preferences.Instance.ShowSubmodulesAsTree)
|
||||
{
|
||||
Preferences.Instance.ShowSubmodulesAsTree = value;
|
||||
VisibleSubmodules = BuildVisibleSubmodules();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object VisibleSubmodules
|
||||
{
|
||||
get => _visibleSubmodules;
|
||||
private set => SetProperty(ref _visibleSubmodules, value);
|
||||
|
@ -228,6 +256,12 @@ namespace SourceGit.ViewModels
|
|||
private set => SetProperty(ref _stashesCount, value);
|
||||
}
|
||||
|
||||
public int LocalBranchesCount
|
||||
{
|
||||
get => _localBranchesCount;
|
||||
private set => SetProperty(ref _localBranchesCount, value);
|
||||
}
|
||||
|
||||
public bool IncludeUntracked
|
||||
{
|
||||
get => _settings.IncludeUntrackedInLocalChanges;
|
||||
|
@ -528,9 +562,9 @@ namespace SourceGit.ViewModels
|
|||
_localBranchTrees.Clear();
|
||||
_remoteBranchTrees.Clear();
|
||||
_tags.Clear();
|
||||
_visibleTags.Clear();
|
||||
_visibleTags = null;
|
||||
_submodules.Clear();
|
||||
_visibleSubmodules.Clear();
|
||||
_visibleSubmodules = null;
|
||||
_searchedCommits.Clear();
|
||||
_selectedSearchedCommit = null;
|
||||
|
||||
|
@ -926,7 +960,7 @@ namespace SourceGit.ViewModels
|
|||
if (!changed)
|
||||
return;
|
||||
|
||||
if (isLocal && !string.IsNullOrEmpty(branch.Upstream))
|
||||
if (isLocal && !string.IsNullOrEmpty(branch.Upstream) && !branch.IsUpstreamGone)
|
||||
_settings.UpdateHistoriesFilter(branch.Upstream, Models.FilterType.RemoteBranch, mode);
|
||||
}
|
||||
else
|
||||
|
@ -1020,7 +1054,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void RefreshBranches()
|
||||
{
|
||||
var branches = new Commands.QueryBranches(_fullpath).Result();
|
||||
var branches = new Commands.QueryBranches(_fullpath).Result(out var localBranchesCount);
|
||||
var remotes = new Commands.QueryRemotes(_fullpath).Result();
|
||||
var builder = BuildBranchTree(branches, remotes);
|
||||
|
||||
|
@ -1033,6 +1067,7 @@ namespace SourceGit.ViewModels
|
|||
CurrentBranch = branches.Find(x => x.IsCurrent);
|
||||
LocalBranchTrees = builder.Locals;
|
||||
RemoteBranchTrees = builder.Remotes;
|
||||
LocalBranchesCount = localBranchesCount;
|
||||
|
||||
if (_workingCopy != null)
|
||||
_workingCopy.HasRemotes = remotes.Count > 0;
|
||||
|
@ -2323,14 +2358,15 @@ namespace SourceGit.ViewModels
|
|||
return menu;
|
||||
}
|
||||
|
||||
public ContextMenu CreateContextMenuForSubmodule(string submodule)
|
||||
public ContextMenu CreateContextMenuForSubmodule(Models.Submodule submodule)
|
||||
{
|
||||
var open = new MenuItem();
|
||||
open.Header = App.Text("Submodule.Open");
|
||||
open.Icon = App.CreateMenuIcon("Icons.Folder.Open");
|
||||
open.IsEnabled = submodule.Status != Models.SubmoduleStatus.NotInited;
|
||||
open.Click += (_, ev) =>
|
||||
{
|
||||
OpenSubmodule(submodule);
|
||||
OpenSubmodule(submodule.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -2339,7 +2375,7 @@ namespace SourceGit.ViewModels
|
|||
copy.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copy.Click += (_, ev) =>
|
||||
{
|
||||
App.CopyText(submodule);
|
||||
App.CopyText(submodule.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -2349,7 +2385,7 @@ namespace SourceGit.ViewModels
|
|||
rm.Click += (_, ev) =>
|
||||
{
|
||||
if (CanCreatePopup())
|
||||
ShowPopup(new DeleteSubmodule(this, submodule));
|
||||
ShowPopup(new DeleteSubmodule(this, submodule.Path));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -2470,7 +2506,7 @@ namespace SourceGit.ViewModels
|
|||
return builder;
|
||||
}
|
||||
|
||||
private List<Models.Tag> BuildVisibleTags()
|
||||
private object BuildVisibleTags()
|
||||
{
|
||||
switch (_settings.TagSortMode)
|
||||
{
|
||||
|
@ -2501,10 +2537,14 @@ namespace SourceGit.ViewModels
|
|||
|
||||
var historiesFilters = _settings.CollectHistoriesFilters();
|
||||
UpdateTagFilterMode(historiesFilters);
|
||||
return visible;
|
||||
|
||||
if (Preferences.Instance.ShowTagsAsTree)
|
||||
return TagCollectionAsTree.Build(visible, _visibleTags as TagCollectionAsTree);
|
||||
else
|
||||
return new TagCollectionAsList() { Tags = visible };
|
||||
}
|
||||
|
||||
private List<Models.Submodule> BuildVisibleSubmodules()
|
||||
private object BuildVisibleSubmodules()
|
||||
{
|
||||
var visible = new List<Models.Submodule>();
|
||||
if (string.IsNullOrEmpty(_filter))
|
||||
|
@ -2519,7 +2559,11 @@ namespace SourceGit.ViewModels
|
|||
visible.Add(s);
|
||||
}
|
||||
}
|
||||
return visible;
|
||||
|
||||
if (Preferences.Instance.ShowSubmodulesAsTree)
|
||||
return SubmoduleCollectionAsTree.Build(visible, _visibleSubmodules as SubmoduleCollectionAsTree);
|
||||
else
|
||||
return new SubmoduleCollectionAsList() { Submodules = visible };
|
||||
}
|
||||
|
||||
private void RefreshHistoriesFilters(bool refresh)
|
||||
|
@ -2588,7 +2632,7 @@ namespace SourceGit.ViewModels
|
|||
if (node.Path.Equals(path, StringComparison.Ordinal))
|
||||
return node;
|
||||
|
||||
if (path!.StartsWith(node.Path, StringComparison.Ordinal))
|
||||
if (path.StartsWith(node.Path, StringComparison.Ordinal))
|
||||
{
|
||||
var founded = FindBranchNode(node.Children, path);
|
||||
if (founded != null)
|
||||
|
@ -2726,6 +2770,7 @@ namespace SourceGit.ViewModels
|
|||
private int _selectedViewIndex = 0;
|
||||
private object _selectedView = null;
|
||||
|
||||
private int _localBranchesCount = 0;
|
||||
private int _localChangesCount = 0;
|
||||
private int _stashesCount = 0;
|
||||
|
||||
|
@ -2748,9 +2793,9 @@ namespace SourceGit.ViewModels
|
|||
private List<BranchTreeNode> _remoteBranchTrees = new List<BranchTreeNode>();
|
||||
private List<Models.Worktree> _worktrees = new List<Models.Worktree>();
|
||||
private List<Models.Tag> _tags = new List<Models.Tag>();
|
||||
private List<Models.Tag> _visibleTags = new List<Models.Tag>();
|
||||
private object _visibleTags = null;
|
||||
private List<Models.Submodule> _submodules = new List<Models.Submodule>();
|
||||
private List<Models.Submodule> _visibleSubmodules = new List<Models.Submodule>();
|
||||
private object _visibleSubmodules = null;
|
||||
|
||||
private bool _isAutoFetching = false;
|
||||
private Timer _autoFetchTimer = null;
|
||||
|
|
212
src/ViewModels/SubmoduleCollection.cs
Normal file
212
src/ViewModels/SubmoduleCollection.cs
Normal file
|
@ -0,0 +1,212 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia.Collections;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class SubmoduleTreeNode : ObservableObject
|
||||
{
|
||||
public string FullPath { get; set; } = string.Empty;
|
||||
public int Depth { get; private set; } = 0;
|
||||
public Models.Submodule Module { get; private set; } = null;
|
||||
public List<SubmoduleTreeNode> Children { get; private set; } = [];
|
||||
public int Counter = 0;
|
||||
|
||||
public bool IsFolder
|
||||
{
|
||||
get => Module == null;
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public string ChildCounter
|
||||
{
|
||||
get => Counter > 0 ? $"({Counter})" : string.Empty;
|
||||
}
|
||||
|
||||
public bool IsDirty
|
||||
{
|
||||
get => Module?.IsDirty ?? false;
|
||||
}
|
||||
|
||||
public SubmoduleTreeNode(Models.Submodule module, int depth)
|
||||
{
|
||||
FullPath = module.Path;
|
||||
Depth = depth;
|
||||
Module = module;
|
||||
IsExpanded = false;
|
||||
}
|
||||
|
||||
public SubmoduleTreeNode(string path, int depth, bool isExpanded)
|
||||
{
|
||||
FullPath = path;
|
||||
Depth = depth;
|
||||
IsExpanded = isExpanded;
|
||||
Counter = 1;
|
||||
}
|
||||
|
||||
public static List<SubmoduleTreeNode> Build(IList<Models.Submodule> submodules, HashSet<string> expaneded)
|
||||
{
|
||||
var nodes = new List<SubmoduleTreeNode>();
|
||||
var folders = new Dictionary<string, SubmoduleTreeNode>();
|
||||
|
||||
foreach (var module in submodules)
|
||||
{
|
||||
var sepIdx = module.Path.IndexOf('/', StringComparison.Ordinal);
|
||||
if (sepIdx == -1)
|
||||
{
|
||||
nodes.Add(new SubmoduleTreeNode(module, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmoduleTreeNode lastFolder = null;
|
||||
int depth = 0;
|
||||
|
||||
while (sepIdx != -1)
|
||||
{
|
||||
var folder = module.Path.Substring(0, sepIdx);
|
||||
if (folders.TryGetValue(folder, out var value))
|
||||
{
|
||||
lastFolder = value;
|
||||
lastFolder.Counter++;
|
||||
}
|
||||
else if (lastFolder == null)
|
||||
{
|
||||
lastFolder = new SubmoduleTreeNode(folder, depth, expaneded.Contains(folder));
|
||||
folders.Add(folder, lastFolder);
|
||||
InsertFolder(nodes, lastFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cur = new SubmoduleTreeNode(folder, depth, expaneded.Contains(folder));
|
||||
folders.Add(folder, cur);
|
||||
InsertFolder(lastFolder.Children, cur);
|
||||
lastFolder = cur;
|
||||
}
|
||||
|
||||
depth++;
|
||||
sepIdx = module.Path.IndexOf('/', sepIdx + 1);
|
||||
}
|
||||
|
||||
lastFolder?.Children.Add(new SubmoduleTreeNode(module, depth));
|
||||
}
|
||||
}
|
||||
|
||||
folders.Clear();
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private static void InsertFolder(List<SubmoduleTreeNode> collection, SubmoduleTreeNode subFolder)
|
||||
{
|
||||
for (int i = 0; i < collection.Count; i++)
|
||||
{
|
||||
if (!collection[i].IsFolder)
|
||||
{
|
||||
collection.Insert(i, subFolder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
collection.Add(subFolder);
|
||||
}
|
||||
|
||||
private bool _isExpanded = false;
|
||||
}
|
||||
|
||||
public class SubmoduleCollectionAsTree
|
||||
{
|
||||
public List<SubmoduleTreeNode> Tree
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public AvaloniaList<SubmoduleTreeNode> Rows
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public static SubmoduleCollectionAsTree Build(List<Models.Submodule> submodules, SubmoduleCollectionAsTree old)
|
||||
{
|
||||
var oldExpanded = new HashSet<string>();
|
||||
if (old != null)
|
||||
{
|
||||
foreach (var row in old.Rows)
|
||||
{
|
||||
if (row.IsFolder && row.IsExpanded)
|
||||
oldExpanded.Add(row.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
var collection = new SubmoduleCollectionAsTree();
|
||||
collection.Tree = SubmoduleTreeNode.Build(submodules, oldExpanded);
|
||||
|
||||
var rows = new List<SubmoduleTreeNode>();
|
||||
MakeTreeRows(rows, collection.Tree);
|
||||
collection.Rows.AddRange(rows);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public void ToggleExpand(SubmoduleTreeNode node)
|
||||
{
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
|
||||
var rows = Rows;
|
||||
var depth = node.Depth;
|
||||
var idx = rows.IndexOf(node);
|
||||
if (idx == -1)
|
||||
return;
|
||||
|
||||
if (node.IsExpanded)
|
||||
{
|
||||
var subrows = new List<SubmoduleTreeNode>();
|
||||
MakeTreeRows(subrows, node.Children);
|
||||
rows.InsertRange(idx + 1, subrows);
|
||||
}
|
||||
else
|
||||
{
|
||||
var removeCount = 0;
|
||||
for (int i = idx + 1; i < rows.Count; i++)
|
||||
{
|
||||
var row = rows[i];
|
||||
if (row.Depth <= depth)
|
||||
break;
|
||||
|
||||
removeCount++;
|
||||
}
|
||||
rows.RemoveRange(idx + 1, removeCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static void MakeTreeRows(List<SubmoduleTreeNode> rows, List<SubmoduleTreeNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
rows.Add(node);
|
||||
|
||||
if (!node.IsExpanded || !node.IsFolder)
|
||||
continue;
|
||||
|
||||
MakeTreeRows(rows, node.Children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SubmoduleCollectionAsList
|
||||
{
|
||||
public List<Models.Submodule> Submodules
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
}
|
||||
}
|
|
@ -1,21 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia.Collections;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class TagTreeNodeToolTip
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public bool IsAnnotated { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
|
||||
public TagTreeNodeToolTip(Models.Tag t)
|
||||
{
|
||||
Name = t.Name;
|
||||
IsAnnotated = t.IsAnnotated;
|
||||
Message = t.Message;
|
||||
}
|
||||
}
|
||||
|
||||
public class TagTreeNode : ObservableObject
|
||||
{
|
||||
public string FullPath { get; set; }
|
||||
public int Depth { get; private set; } = 0;
|
||||
public Models.Tag Tag { get; private set; } = null;
|
||||
public TagTreeNodeToolTip ToolTip { get; private set; } = null;
|
||||
public List<TagTreeNode> Children { get; private set; } = [];
|
||||
|
||||
public object ToolTip
|
||||
{
|
||||
get => Tag?.Message;
|
||||
}
|
||||
public int Counter { get; set; } = 0;
|
||||
|
||||
public bool IsFolder
|
||||
{
|
||||
|
@ -28,11 +41,17 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public string TagsCount
|
||||
{
|
||||
get => Counter > 0 ? $"({Counter})" : string.Empty;
|
||||
}
|
||||
|
||||
public TagTreeNode(Models.Tag t, int depth)
|
||||
{
|
||||
FullPath = t.Name;
|
||||
Depth = depth;
|
||||
Tag = t;
|
||||
ToolTip = new TagTreeNodeToolTip(t);
|
||||
IsExpanded = false;
|
||||
}
|
||||
|
||||
|
@ -41,9 +60,10 @@ namespace SourceGit.ViewModels
|
|||
FullPath = path;
|
||||
Depth = depth;
|
||||
IsExpanded = isExpanded;
|
||||
Counter = 1;
|
||||
}
|
||||
|
||||
public static List<TagTreeNode> Build(IList<Models.Tag> tags, HashSet<string> expaneded)
|
||||
public static List<TagTreeNode> Build(List<Models.Tag> tags, HashSet<string> expaneded)
|
||||
{
|
||||
var nodes = new List<TagTreeNode>();
|
||||
var folders = new Dictionary<string, TagTreeNode>();
|
||||
|
@ -66,6 +86,7 @@ namespace SourceGit.ViewModels
|
|||
if (folders.TryGetValue(folder, out var value))
|
||||
{
|
||||
lastFolder = value;
|
||||
lastFolder.Counter++;
|
||||
}
|
||||
else if (lastFolder == null)
|
||||
{
|
||||
|
@ -112,7 +133,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public class TagCollectionAsList
|
||||
{
|
||||
public AvaloniaList<Models.Tag> Tags
|
||||
public List<Models.Tag> Tags
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
@ -132,5 +153,71 @@ namespace SourceGit.ViewModels
|
|||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public static TagCollectionAsTree Build(List<Models.Tag> tags, TagCollectionAsTree old)
|
||||
{
|
||||
var oldExpanded = new HashSet<string>();
|
||||
if (old != null)
|
||||
{
|
||||
foreach (var row in old.Rows)
|
||||
{
|
||||
if (row.IsFolder && row.IsExpanded)
|
||||
oldExpanded.Add(row.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
var collection = new TagCollectionAsTree();
|
||||
collection.Tree = TagTreeNode.Build(tags, oldExpanded);
|
||||
|
||||
var rows = new List<TagTreeNode>();
|
||||
MakeTreeRows(rows, collection.Tree);
|
||||
collection.Rows.AddRange(rows);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public void ToggleExpand(TagTreeNode node)
|
||||
{
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
|
||||
var rows = Rows;
|
||||
var depth = node.Depth;
|
||||
var idx = rows.IndexOf(node);
|
||||
if (idx == -1)
|
||||
return;
|
||||
|
||||
if (node.IsExpanded)
|
||||
{
|
||||
var subrows = new List<TagTreeNode>();
|
||||
MakeTreeRows(subrows, node.Children);
|
||||
rows.InsertRange(idx + 1, subrows);
|
||||
}
|
||||
else
|
||||
{
|
||||
var removeCount = 0;
|
||||
for (int i = idx + 1; i < rows.Count; i++)
|
||||
{
|
||||
var row = rows[i];
|
||||
if (row.Depth <= depth)
|
||||
break;
|
||||
|
||||
removeCount++;
|
||||
}
|
||||
rows.RemoveRange(idx + 1, removeCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static void MakeTreeRows(List<TagTreeNode> rows, List<TagTreeNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
rows.Add(node);
|
||||
|
||||
if (!node.IsExpanded || !node.IsFolder)
|
||||
continue;
|
||||
|
||||
MakeTreeRows(rows, node.Children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
80
src/ViewModels/WorkspaceSwitcher.cs
Normal file
80
src/ViewModels/WorkspaceSwitcher.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class WorkspaceSwitcher : ObservableObject, IDisposable
|
||||
{
|
||||
public List<Workspace> VisibleWorkspaces
|
||||
{
|
||||
get => _visibleWorkspaces;
|
||||
private set => SetProperty(ref _visibleWorkspaces, value);
|
||||
}
|
||||
|
||||
public string SearchFilter
|
||||
{
|
||||
get => _searchFilter;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _searchFilter, value))
|
||||
UpdateVisibleWorkspaces();
|
||||
}
|
||||
}
|
||||
|
||||
public Workspace SelectedWorkspace
|
||||
{
|
||||
get => _selectedWorkspace;
|
||||
set => SetProperty(ref _selectedWorkspace, value);
|
||||
}
|
||||
|
||||
public WorkspaceSwitcher(Launcher launcher)
|
||||
{
|
||||
_launcher = launcher;
|
||||
UpdateVisibleWorkspaces();
|
||||
}
|
||||
|
||||
public void ClearFilter()
|
||||
{
|
||||
SearchFilter = string.Empty;
|
||||
}
|
||||
|
||||
public void Switch()
|
||||
{
|
||||
_launcher.SwitchWorkspace(_selectedWorkspace);
|
||||
_launcher.CancelSwitcher();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_visibleWorkspaces.Clear();
|
||||
_selectedWorkspace = null;
|
||||
_searchFilter = string.Empty;
|
||||
}
|
||||
|
||||
private void UpdateVisibleWorkspaces()
|
||||
{
|
||||
var visible = new List<Workspace>();
|
||||
if (string.IsNullOrEmpty(_searchFilter))
|
||||
{
|
||||
visible.AddRange(Preferences.Instance.Workspaces);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var workspace in Preferences.Instance.Workspaces)
|
||||
{
|
||||
if (workspace.Name.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase))
|
||||
visible.Add(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
VisibleWorkspaces = visible;
|
||||
SelectedWorkspace = visible.Count == 0 ? null : visible[0];
|
||||
}
|
||||
|
||||
private Launcher _launcher = null;
|
||||
private List<Workspace> _visibleWorkspaces = null;
|
||||
private string _searchFilter = string.Empty;
|
||||
private Workspace _selectedWorkspace = null;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
Title="{DynamicResource Text.About}"
|
||||
Width="520" Height="230"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
|
||||
|
@ -67,4 +67,9 @@
|
|||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding #ThisControl.Close}"/>
|
||||
</Window.KeyBindings>
|
||||
|
||||
</v:ChromelessWindow>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
@ -12,14 +13,6 @@ namespace SourceGit.Views
|
|||
{
|
||||
public class Avatar : Control, Models.IAvatarHost
|
||||
{
|
||||
private static readonly GradientStops[] FALLBACK_GRADIENTS = [
|
||||
new GradientStops() { new GradientStop(Colors.Orange, 0), new GradientStop(Color.FromRgb(255, 213, 134), 1) },
|
||||
new GradientStops() { new GradientStop(Colors.DodgerBlue, 0), new GradientStop(Colors.LightSkyBlue, 1) },
|
||||
new GradientStops() { new GradientStop(Colors.LimeGreen, 0), new GradientStop(Color.FromRgb(124, 241, 124), 1) },
|
||||
new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) },
|
||||
new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) },
|
||||
];
|
||||
|
||||
public static readonly StyledProperty<Models.User> UserProperty =
|
||||
AvaloniaProperty.Register<Avatar, Models.User>(nameof(User));
|
||||
|
||||
|
@ -29,11 +22,6 @@ namespace SourceGit.Views
|
|||
set => SetValue(UserProperty, value);
|
||||
}
|
||||
|
||||
static Avatar()
|
||||
{
|
||||
UserProperty.Changed.AddClassHandler<Avatar>(OnUserPropertyChanged);
|
||||
}
|
||||
|
||||
public Avatar()
|
||||
{
|
||||
var refetch = new MenuItem() { Header = App.Text("RefetchAvatar") };
|
||||
|
@ -55,25 +43,72 @@ namespace SourceGit.Views
|
|||
return;
|
||||
|
||||
var corner = (float)Math.Max(2, Bounds.Width / 16);
|
||||
var img = Models.AvatarManager.Instance.Request(User.Email, false);
|
||||
if (img != null)
|
||||
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
|
||||
var clip = context.PushClip(new RoundedRect(rect, corner));
|
||||
|
||||
if (_img != null)
|
||||
{
|
||||
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
|
||||
context.PushClip(new RoundedRect(rect, corner));
|
||||
context.DrawImage(img, rect);
|
||||
context.DrawImage(_img, rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
Point textOrigin = new Point((Bounds.Width - _fallbackLabel.Width) * 0.5, (Bounds.Height - _fallbackLabel.Height) * 0.5);
|
||||
context.DrawRectangle(_fallbackBrush, null, new Rect(0, 0, Bounds.Width, Bounds.Height), corner, corner);
|
||||
context.DrawText(_fallbackLabel, textOrigin);
|
||||
context.DrawRectangle(Brushes.White, new Pen(new SolidColorBrush(Colors.Black, 0.3f), 0.65f), rect, corner, corner);
|
||||
|
||||
var offsetX = Bounds.Width / 10.0;
|
||||
var offsetY = Bounds.Height / 10.0;
|
||||
|
||||
var stepX = (Bounds.Width - offsetX * 2) / 5.0;
|
||||
var stepY = (Bounds.Height - offsetY * 2) / 5.0;
|
||||
|
||||
var user = User;
|
||||
var lowered = user.Email.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered));
|
||||
|
||||
var brush = new SolidColorBrush(new Color(255, hash[0], hash[1], hash[2]));
|
||||
var switches = new bool[15];
|
||||
for (int i = 0; i < switches.Length; i++)
|
||||
switches[i] = hash[i + 1] % 2 == 1;
|
||||
|
||||
for (int row = 0; row < 5; row++)
|
||||
{
|
||||
var x = offsetX + stepX * 2;
|
||||
var y = offsetY + stepY * row;
|
||||
var idx = row * 3;
|
||||
|
||||
if (switches[idx])
|
||||
context.FillRectangle(brush, new Rect(x, y, stepX, stepY));
|
||||
|
||||
if (switches[idx + 1])
|
||||
context.FillRectangle(brush, new Rect(x + stepX, y, stepX, stepY));
|
||||
|
||||
if (switches[idx + 2])
|
||||
context.FillRectangle(brush, new Rect(x + stepX * 2, y, stepX, stepY));
|
||||
}
|
||||
|
||||
for (int row = 0; row < 5; row++)
|
||||
{
|
||||
var x = offsetX;
|
||||
var y = offsetY + stepY * row;
|
||||
var idx = row * 3 + 2;
|
||||
|
||||
if (switches[idx])
|
||||
context.FillRectangle(brush, new Rect(x, y, stepX, stepY));
|
||||
|
||||
if (switches[idx - 1])
|
||||
context.FillRectangle(brush, new Rect(x + stepX, y, stepX, stepY));
|
||||
}
|
||||
}
|
||||
|
||||
clip.Dispose();
|
||||
}
|
||||
|
||||
public void OnAvatarResourceChanged(string email)
|
||||
{
|
||||
if (User.Email.Equals(email, StringComparison.Ordinal))
|
||||
{
|
||||
_img = Models.AvatarManager.Instance.Request(User.Email, false);
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
|
@ -88,53 +123,21 @@ namespace SourceGit.Views
|
|||
Models.AvatarManager.Instance.Unsubscribe(this);
|
||||
}
|
||||
|
||||
private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e)
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
if (avatar.User == null)
|
||||
return;
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
var fallback = GetFallbackString(avatar.User.Name);
|
||||
var chars = fallback.ToCharArray();
|
||||
var sum = 0;
|
||||
foreach (var c in chars)
|
||||
sum += Math.Abs(c);
|
||||
|
||||
avatar._fallbackBrush = new LinearGradientBrush
|
||||
if (change.Property == UserProperty)
|
||||
{
|
||||
GradientStops = FALLBACK_GRADIENTS[sum % FALLBACK_GRADIENTS.Length],
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
};
|
||||
var user = User;
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
var typeface = new Typeface("fonts:SourceGit#JetBrains Mono");
|
||||
avatar._fallbackLabel = new FormattedText(
|
||||
fallback,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
avatar.Width * 0.65,
|
||||
Brushes.White);
|
||||
|
||||
avatar.InvalidateVisual();
|
||||
_img = Models.AvatarManager.Instance.Request(User.Email, false);
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFallbackString(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return "?";
|
||||
|
||||
var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var chars = new List<char>();
|
||||
foreach (var part in parts)
|
||||
chars.Add(part[0]);
|
||||
|
||||
if (chars.Count >= 2 && char.IsAsciiLetterOrDigit(chars[0]) && char.IsAsciiLetterOrDigit(chars[^1]))
|
||||
return string.Format("{0}{1}", chars[0], chars[^1]);
|
||||
|
||||
return name.Substring(0, 1);
|
||||
}
|
||||
|
||||
private FormattedText _fallbackLabel = null;
|
||||
private LinearGradientBrush _fallbackBrush = null;
|
||||
private Bitmap _img = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,11 +32,13 @@
|
|||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:BranchTreeNode">
|
||||
<Border Background="Transparent" PointerPressed="OnNodePointerPressed">
|
||||
<Border Background="Transparent"
|
||||
PointerPressed="OnNodePointerPressed"
|
||||
ToolTip.Tip="{Binding Tooltip}"
|
||||
ToolTip.Placement="Right">
|
||||
<Grid Height="24"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
ColumnDefinitions="16,*"
|
||||
ToolTip.Tip="{Binding Tooltip}">
|
||||
ColumnDefinitions="16,*">
|
||||
|
||||
<!-- Tree Expander -->
|
||||
<v:BranchTreeNodeToggleButton Grid.Column="0"
|
||||
|
@ -53,16 +55,16 @@
|
|||
DoubleTapped="OnDoubleTappedBranchNode">
|
||||
|
||||
<!-- Icon -->
|
||||
<v:BranchTreeNodeIcon Grid.Column="0"
|
||||
Node="{Binding}"
|
||||
IsExpanded="{Binding IsExpanded}"/>
|
||||
<v:BranchTreeNodeIcon Grid.Column="0" IsExpanded="{Binding IsExpanded}"/>
|
||||
|
||||
<!-- Name -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
Text="{Binding Name}"
|
||||
FontWeight="{Binding IsCurrent, Converter={x:Static c:BoolConverters.IsBoldToFontWeight}}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{Binding Name}"/>
|
||||
<Run Text="{Binding BranchesCount}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Upstream invalid tip -->
|
||||
<Border Grid.Column="2"
|
||||
|
|
|
@ -17,15 +17,6 @@ namespace SourceGit.Views
|
|||
{
|
||||
public class BranchTreeNodeIcon : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<ViewModels.BranchTreeNode> NodeProperty =
|
||||
AvaloniaProperty.Register<BranchTreeNodeIcon, ViewModels.BranchTreeNode>(nameof(Node));
|
||||
|
||||
public ViewModels.BranchTreeNode Node
|
||||
{
|
||||
get => GetValue(NodeProperty);
|
||||
set => SetValue(NodeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsExpandedProperty =
|
||||
AvaloniaProperty.Register<BranchTreeNodeIcon, bool>(nameof(IsExpanded));
|
||||
|
||||
|
@ -35,16 +26,23 @@ namespace SourceGit.Views
|
|||
set => SetValue(IsExpandedProperty, value);
|
||||
}
|
||||
|
||||
static BranchTreeNodeIcon()
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
NodeProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||
IsExpandedProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||
base.OnDataContextChanged(e);
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == IsExpandedProperty)
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
private void UpdateContent()
|
||||
{
|
||||
var node = Node;
|
||||
if (node == null)
|
||||
if (DataContext is not ViewModels.BranchTreeNode node)
|
||||
{
|
||||
Content = null;
|
||||
return;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
|
@ -11,7 +10,7 @@ namespace SourceGit.Views
|
|||
{
|
||||
public bool UseSystemWindowFrame
|
||||
{
|
||||
get => OperatingSystem.IsLinux() && ViewModels.Preferences.Instance.UseSystemWindowFrame;
|
||||
get => Native.OS.UseSystemWindowFrame;
|
||||
}
|
||||
|
||||
protected override Type StyleKeyOverride => typeof(Window);
|
||||
|
@ -19,32 +18,7 @@ namespace SourceGit.Views
|
|||
public ChromelessWindow()
|
||||
{
|
||||
Focusable = true;
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
if (UseSystemWindowFrame)
|
||||
{
|
||||
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
|
||||
ExtendClientAreaToDecorationsHint = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
|
||||
ExtendClientAreaToDecorationsHint = true;
|
||||
Classes.Add("custom_window_frame");
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
|
||||
ExtendClientAreaToDecorationsHint = true;
|
||||
Classes.Add("fix_maximized_padding");
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome;
|
||||
ExtendClientAreaToDecorationsHint = true;
|
||||
}
|
||||
Native.OS.SetupForWindow(this);
|
||||
}
|
||||
|
||||
public void BeginMoveWindow(object _, PointerPressedEventArgs e)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
Title="{DynamicResource Text.Warn}"
|
||||
SizeToContent="WidthAndHeight"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||
<!-- TitleBar -->
|
||||
|
@ -64,6 +65,7 @@
|
|||
Height="30"
|
||||
Margin="4,0"
|
||||
Click="CloseWindow"
|
||||
IsCancel="True"
|
||||
Content="{DynamicResource Text.Cancel}"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"/>
|
||||
|
|
|
@ -175,7 +175,17 @@
|
|||
<Path Width="12" Height="12" Data="{StaticResource Icons.Layout}" Margin="0,2,0,0"/>
|
||||
</ToggleButton>
|
||||
|
||||
<Button Classes="icon_button" Width="28" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
|
||||
<Button Classes="icon_button"
|
||||
Width="28"
|
||||
Command="{Binding OpenExternalMergeTool}"
|
||||
HotKey="{OnPlatform Ctrl+D, macOS=⌘+D}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock>
|
||||
<Run Text="{DynamicResource Text.Diff.UseMerger}"/>
|
||||
<Run Text=" "/>
|
||||
<Run Text="{OnPlatform Ctrl+D, macOS=⌘+D}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</TextBlock>
|
||||
</ToolTip.Tip>
|
||||
<Path Width="12" Height="12" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<Grid RowDefinitions="20,20,20,20,20,20,20,20" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+Shift+P, macOS=⌘+\,}"/>
|
||||
<Grid RowDefinitions="20,20,20,20,20,20,20,20,20,20" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+\,, macOS=⌘+\,}"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.OpenPreferences}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+T, macOS=⌘+T}"/>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<TextBlock Grid.Row="2" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+W, macOS=⌘+W}" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.CloseTab}" />
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Shift+Ctrl+Tab, macOS=⌘+⌥+←}"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+Shift+Tab, macOS=⌘+⌥+←}"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.GotoPrevTab}" />
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+Tab, macOS=⌘+⌥+→}"/>
|
||||
|
@ -69,6 +69,12 @@
|
|||
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+Q, macOS=⌘+Q}"/>
|
||||
<TextBlock Grid.Row="7" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Quit}" />
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+Shift+P, macOS=⌘+⇧+P}"/>
|
||||
<TextBlock Grid.Row="8" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.SwitchWorkspace}" />
|
||||
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+P, macOS=⌘+P}"/>
|
||||
<TextBlock Grid.Row="9" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.SwitchTab}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="{DynamicResource Text.Hotkeys.Repo}"
|
||||
|
@ -130,7 +136,7 @@
|
|||
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
|
||||
Margin="0,8"/>
|
||||
|
||||
<Grid RowDefinitions="20,20,20,20" ColumnDefinitions="150,*">
|
||||
<Grid RowDefinitions="20,20,20,20,20" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+F, macOS=⌘+F}"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.TextEditor.Search}" />
|
||||
|
||||
|
@ -142,6 +148,9 @@
|
|||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="primary bold" Text="ESC"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.TextEditor.CloseSearch}" />
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Classes="primary bold" Text="{OnPlatform Ctrl+D, macOS=⌘+D}"/>
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.TextEditor.OpenExternalMergeTool}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<Path Width="12" Height="12" Data="{StaticResource Icons.Menu}"/>
|
||||
</Button>
|
||||
|
||||
<!-- Workspace Switcher -->
|
||||
<!-- Workspace Dropdown Menu -->
|
||||
<Button Grid.Column="1" Classes="icon_button" VerticalAlignment="Bottom" Margin="0,0,0,1" Click="OnOpenWorkspaceMenu">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
@ -85,11 +85,11 @@
|
|||
Fill="{Binding ActiveWorkspace.Brush}"/>
|
||||
</Button>
|
||||
|
||||
<!-- Pages Tabs-->
|
||||
<!-- Pages Tabs -->
|
||||
<v:LauncherTabBar Grid.Column="2" Height="30" Margin="0,0,16,0" VerticalAlignment="Bottom"/>
|
||||
|
||||
<!-- Caption Buttons (Windows/Linux)-->
|
||||
<Border Grid.Column="3" Margin="16,0,0,0" IsVisible="{Binding #ThisControl.IsRightCaptionButtonsVisible}">
|
||||
<!-- Caption Buttons (Windows/Linux) -->
|
||||
<Border Grid.Column="3" Margin="16,0,0,0" IsVisible="{Binding #ThisControl.HasRightCaptionButton}">
|
||||
<v:CaptionButtons Height="30" VerticalAlignment="Top"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
@ -102,5 +102,27 @@
|
|||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
|
||||
<!-- Workspace/Pages Switcher -->
|
||||
<Border Grid.Row="0" Grid.RowSpan="2"
|
||||
Background="Transparent"
|
||||
IsVisible="{Binding Switcher, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
PointerPressed="OnCancelSwitcher">
|
||||
<Border HorizontalAlignment="Center" VerticalAlignment="Center" Effect="drop-shadow(0 0 12 #A0000000)">
|
||||
<Border Background="{DynamicResource Brush.Popup}" CornerRadius="8">
|
||||
<ContentControl Margin="16,10,16,12" Content="{Binding Switcher}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:WorkspaceSwitcher">
|
||||
<v:WorkspaceSwitcher/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="vm:LauncherPageSwitcher">
|
||||
<v:LauncherPageSwitcher/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Border>
|
||||
</Border>
|
||||
</Border>
|
||||
</Grid>
|
||||
</v:ChromelessWindow>
|
||||
|
|
|
@ -29,12 +29,13 @@ namespace SourceGit.Views
|
|||
set => SetValue(HasLeftCaptionButtonProperty, value);
|
||||
}
|
||||
|
||||
public bool IsRightCaptionButtonsVisible
|
||||
public bool HasRightCaptionButton
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
return !ViewModels.Preferences.Instance.UseSystemWindowFrame;
|
||||
return !Native.OS.UseSystemWindowFrame;
|
||||
|
||||
return OperatingSystem.IsWindows();
|
||||
}
|
||||
}
|
||||
|
@ -52,8 +53,7 @@ namespace SourceGit.Views
|
|||
{
|
||||
HasLeftCaptionButton = true;
|
||||
CaptionHeight = new GridLength(34);
|
||||
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome |
|
||||
ExtendClientAreaChromeHints.OSXThickTitleBar;
|
||||
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
|
||||
}
|
||||
else if (UseSystemWindowFrame)
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ namespace SourceGit.Views
|
|||
|
||||
if (change.Property == WindowStateProperty)
|
||||
{
|
||||
_lastWindowState = (WindowState)change.OldValue;
|
||||
_lastWindowState = (WindowState)change.OldValue!;
|
||||
|
||||
var state = (WindowState)change.NewValue!;
|
||||
if (!OperatingSystem.IsMacOS() && !UseSystemWindowFrame)
|
||||
|
@ -133,8 +133,8 @@ namespace SourceGit.Views
|
|||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar)
|
||||
if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: (KeyModifiers.Control | KeyModifiers.Shift), Key: Key.P })
|
||||
// Ctrl+, opens preference dialog (macOS use hotkeys in system menu bar)
|
||||
if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: KeyModifiers.Control, Key: Key.OemComma })
|
||||
{
|
||||
App.ShowWindow(new Preferences(), true);
|
||||
e.Handled = true;
|
||||
|
@ -149,7 +149,7 @@ namespace SourceGit.Views
|
|||
}
|
||||
|
||||
// Ctrl+Q quits the application (macOS use hotkeys in system menu bar)
|
||||
if (!OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Control && e.Key == Key.Q)
|
||||
if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: KeyModifiers.Control, Key: Key.Q })
|
||||
{
|
||||
App.Quit(0);
|
||||
return;
|
||||
|
@ -157,6 +157,20 @@ namespace SourceGit.Views
|
|||
|
||||
if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
|
||||
{
|
||||
if (e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.P)
|
||||
{
|
||||
vm.OpenWorkspaceSwitcher();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.P)
|
||||
{
|
||||
vm.OpenTabSwitcher();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.W)
|
||||
{
|
||||
vm.CloseTab(null);
|
||||
|
@ -251,6 +265,7 @@ namespace SourceGit.Views
|
|||
else if (e.Key == Key.Escape)
|
||||
{
|
||||
vm.ActivePage.CancelPopup();
|
||||
vm.CancelSwitcher();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
@ -307,6 +322,13 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCancelSwitcher(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.Source == sender)
|
||||
(DataContext as ViewModels.Launcher)?.CancelSwitcher();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private KeyModifiers _unhandledModifiers = KeyModifiers.None;
|
||||
private WindowState _lastWindowState = WindowState.Normal;
|
||||
}
|
||||
|
|
|
@ -3,19 +3,27 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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.LauncherTabsSelector"
|
||||
x:Name="ThisControl">
|
||||
<Grid RowDefinitions="28,Auto">
|
||||
<TextBox Grid.Row="0"
|
||||
x:Class="SourceGit.Views.LauncherPageSwitcher"
|
||||
x:DataType="vm:LauncherPageSwitcher">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{DynamicResource Text.Launcher.Pages}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"/>
|
||||
|
||||
<TextBox Grid.Row="1"
|
||||
Height="24"
|
||||
Margin="4,0"
|
||||
Margin="4,8,4,0"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Text="{Binding #ThisControl.SearchFilter, Mode=TwoWay}"
|
||||
Text="{Binding SearchFilter, Mode=TwoWay}"
|
||||
KeyDown="OnSearchBoxKeyDown"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
VerticalContentAlignment="Center">
|
||||
VerticalContentAlignment="Center"
|
||||
v:AutoFocusBehaviour.IsEnabled="True">
|
||||
<TextBox.InnerLeftContent>
|
||||
<Path Width="14" Height="14"
|
||||
Margin="6,0,0,0"
|
||||
|
@ -27,8 +35,8 @@
|
|||
<Button Classes="icon_button"
|
||||
Width="16"
|
||||
Margin="0,0,6,0"
|
||||
Click="OnClearSearchFilter"
|
||||
IsVisible="{Binding #ThisControl.SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding ClearFilter}"
|
||||
IsVisible="{Binding SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
HorizontalAlignment="Right">
|
||||
<Path Width="14" Height="14"
|
||||
Margin="0,1,0,0"
|
||||
|
@ -38,19 +46,22 @@
|
|||
</TextBox.InnerRightContent>
|
||||
</TextBox>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
Width="200"
|
||||
<ListBox Grid.Row="2"
|
||||
x:Name="PagesListBox"
|
||||
Width="300"
|
||||
MaxHeight="400"
|
||||
Margin="0,4,0,0"
|
||||
Background="Transparent"
|
||||
Margin="4,8,4,0"
|
||||
BorderThickness="0"
|
||||
SelectionMode="Single"
|
||||
Background="Transparent"
|
||||
Focusable="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ItemsSource="{Binding #ThisControl.VisiblePages}"
|
||||
SelectionChanged="OnPageSelectionChanged">
|
||||
ItemsSource="{Binding VisiblePages, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedPage, Mode=TwoWay}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Padding" Value="8,0"/>
|
||||
<Setter Property="MinHeight" Value="26"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
|
@ -72,30 +83,28 @@
|
|||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:LauncherPage">
|
||||
<Grid ColumnDefinitions="Auto,*" VerticalAlignment="Center">
|
||||
<Grid ColumnDefinitions="Auto,6,*" Background="Transparent" DoubleTapped="OnItemDoubleTapped">
|
||||
<Path Grid.Column="0"
|
||||
Width="12" Height="12" Margin="12,0"
|
||||
Width="12" Height="12"
|
||||
Fill="{Binding Node.Bookmark, Converter={x:Static c:IntConverters.ToBookmarkBrush}}"
|
||||
Data="{StaticResource Icons.Bookmark}"
|
||||
IsVisible="{Binding Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
<Path Grid.Column="0"
|
||||
Width="12" Height="12" Margin="12,0"
|
||||
Width="12" Height="12"
|
||||
Fill="{DynamicResource Brush.FG1}"
|
||||
Data="{StaticResource Icons.Repositories}"
|
||||
IsVisible="{Binding !Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
|
||||
Text="{Binding Node.Name}"
|
||||
IsVisible="{Binding Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
|
||||
Text="{DynamicResource Text.PageTabBar.Welcome.Title}"
|
||||
IsVisible="{Binding !Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
49
src/Views/LauncherPageSwitcher.axaml.cs
Normal file
49
src/Views/LauncherPageSwitcher.axaml.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class LauncherPageSwitcher : UserControl
|
||||
{
|
||||
public LauncherPageSwitcher()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Enter && DataContext is ViewModels.LauncherPageSwitcher switcher)
|
||||
{
|
||||
switcher.Switch();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.LauncherPageSwitcher switcher)
|
||||
{
|
||||
switcher.Switch();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Down && PagesListBox.ItemCount > 0)
|
||||
{
|
||||
PagesListBox.Focus(NavigationMethod.Directional);
|
||||
|
||||
if (PagesListBox.SelectedIndex < 0)
|
||||
PagesListBox.SelectedIndex = 0;
|
||||
else if (PagesListBox.SelectedIndex < PagesListBox.ItemCount)
|
||||
PagesListBox.SelectedIndex++;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:LauncherPage}">
|
||||
<DataTemplate DataType="vm:LauncherPage">
|
||||
<Border Height="30"
|
||||
Background="Transparent"
|
||||
PointerPressed="OnPointerPressedTab"
|
||||
|
@ -141,8 +141,106 @@
|
|||
|
||||
<Button x:Name="PageSelector" Classes="icon_button" Width="16" Height="16" Margin="8,0">
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<v:LauncherTabsSelector Pages="{Binding Pages}" PageSelected="OnGotoSelectedPage"/>
|
||||
<Flyout Opened="OnTabsDropdownOpened" Closed="OnTabsDropdownClosed">
|
||||
<Grid RowDefinitions="28,Auto" KeyDown="OnTabsDropdownKeyDown" LostFocus="OnTabsDropdownLostFocus">
|
||||
<TextBox Grid.Row="0"
|
||||
Height="24"
|
||||
Margin="4,0"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Text="{Binding #ThisControl.SearchFilter, Mode=TwoWay}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
VerticalContentAlignment="Center"
|
||||
KeyDown="OnTabsDropdownSearchBoxKeyDown"
|
||||
v:AutoFocusBehaviour.IsEnabled="True">
|
||||
<TextBox.InnerLeftContent>
|
||||
<Path Width="14" Height="14"
|
||||
Margin="6,0,0,0"
|
||||
Fill="{DynamicResource Brush.FG2}"
|
||||
Data="{StaticResource Icons.Search}"/>
|
||||
</TextBox.InnerLeftContent>
|
||||
|
||||
<TextBox.InnerRightContent>
|
||||
<Button Classes="icon_button"
|
||||
Width="16"
|
||||
Margin="0,0,6,0"
|
||||
Click="OnClearSearchFilter"
|
||||
IsVisible="{Binding #ThisControl.SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
HorizontalAlignment="Right">
|
||||
<Path Width="14" Height="14"
|
||||
Margin="0,1,0,0"
|
||||
Fill="{DynamicResource Brush.FG1}"
|
||||
Data="{StaticResource Icons.Clear}"/>
|
||||
</Button>
|
||||
</TextBox.InnerRightContent>
|
||||
</TextBox>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
x:Name="TabsDropdownList"
|
||||
Focusable="True"
|
||||
Width="200"
|
||||
MaxHeight="400"
|
||||
Margin="0,4,0,0"
|
||||
Background="Transparent"
|
||||
SelectionMode="Single"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ItemsSource="{Binding #ThisControl.SelectablePages}"
|
||||
SelectedItem="{Binding ActivePage, Mode=OneWay}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="MinHeight" Value="26"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox">
|
||||
<Setter Property="FocusAdorner">
|
||||
<FocusAdornerTemplate>
|
||||
<Grid/>
|
||||
</FocusAdornerTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:LauncherPage">
|
||||
<Grid ColumnDefinitions="Auto,*" Background="Transparent" DoubleTapped="OnTabsDropdownItemDoubleTapped">
|
||||
<Path Grid.Column="0"
|
||||
Width="12" Height="12" Margin="12,0"
|
||||
Fill="{Binding Node.Bookmark, Converter={x:Static c:IntConverters.ToBookmarkBrush}}"
|
||||
Data="{StaticResource Icons.Bookmark}"
|
||||
IsVisible="{Binding Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
<Path Grid.Column="0"
|
||||
Width="12" Height="12" Margin="12,0"
|
||||
Fill="{DynamicResource Brush.FG1}"
|
||||
Data="{StaticResource Icons.Repositories}"
|
||||
IsVisible="{Binding !Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Node.Name}"
|
||||
IsVisible="{Binding Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.PageTabBar.Welcome.Title}"
|
||||
IsVisible="{Binding !Node.IsRepository}"
|
||||
IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.CircleDown}"/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
|
@ -19,6 +20,20 @@ namespace SourceGit.Views
|
|||
set => SetValue(IsScrollerVisibleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> SearchFilterProperty =
|
||||
AvaloniaProperty.Register<LauncherTabBar, string>(nameof(SearchFilter));
|
||||
|
||||
public string SearchFilter
|
||||
{
|
||||
get => GetValue(SearchFilterProperty);
|
||||
set => SetValue(SearchFilterProperty, value);
|
||||
}
|
||||
|
||||
public AvaloniaList<ViewModels.LauncherPage> SelectablePages
|
||||
{
|
||||
get;
|
||||
} = [];
|
||||
|
||||
public LauncherTabBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
@ -126,6 +141,14 @@ namespace SourceGit.Views
|
|||
context.DrawGeometry(fill, stroke, geo);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == SearchFilterProperty)
|
||||
UpdateSelectablePages();
|
||||
}
|
||||
|
||||
private void ScrollTabs(object _, PointerWheelEventArgs e)
|
||||
{
|
||||
if (!e.KeyModifiers.HasFlag(KeyModifiers.Shift))
|
||||
|
@ -248,13 +271,95 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGotoSelectedPage(object sender, LauncherTabSelectedEventArgs e)
|
||||
private void OnTabsDropdownOpened(object sender, EventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Launcher vm)
|
||||
vm.ActivePage = e.Page;
|
||||
UpdateSelectablePages();
|
||||
}
|
||||
|
||||
PageSelector.Flyout?.Hide();
|
||||
e.Handled = true;
|
||||
private void OnTabsDropdownClosed(object sender, EventArgs e)
|
||||
{
|
||||
SelectablePages.Clear();
|
||||
SearchFilter = string.Empty;
|
||||
}
|
||||
|
||||
private void OnTabsDropdownKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
PageSelector.Flyout?.Hide();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Enter)
|
||||
{
|
||||
if (TabsDropdownList.SelectedItem is ViewModels.LauncherPage page &&
|
||||
DataContext is ViewModels.Launcher vm)
|
||||
{
|
||||
vm.ActivePage = page;
|
||||
PageSelector.Flyout?.Hide();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTabsDropdownSearchBoxKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Down && TabsDropdownList.ItemCount > 0)
|
||||
{
|
||||
TabsDropdownList.Focus(NavigationMethod.Directional);
|
||||
|
||||
if (TabsDropdownList.SelectedIndex < 0)
|
||||
TabsDropdownList.SelectedIndex = 0;
|
||||
else if (TabsDropdownList.SelectedIndex < TabsDropdownList.ItemCount)
|
||||
TabsDropdownList.SelectedIndex++;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTabsDropdownLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Control { IsFocused: false, IsKeyboardFocusWithin: false })
|
||||
PageSelector.Flyout?.Hide();
|
||||
}
|
||||
|
||||
private void OnClearSearchFilter(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SearchFilter = string.Empty;
|
||||
}
|
||||
|
||||
private void OnTabsDropdownItemDoubleTapped(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is Control { DataContext: ViewModels.LauncherPage page } &&
|
||||
DataContext is ViewModels.Launcher vm)
|
||||
{
|
||||
vm.ActivePage = page;
|
||||
PageSelector.Flyout?.Hide();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelectablePages()
|
||||
{
|
||||
if (DataContext is not ViewModels.Launcher vm)
|
||||
return;
|
||||
|
||||
SelectablePages.Clear();
|
||||
|
||||
var pages = vm.Pages;
|
||||
var filter = SearchFilter?.Trim() ?? "";
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
SelectablePages.AddRange(pages);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var page in pages)
|
||||
{
|
||||
var node = page.Node;
|
||||
if (node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
|
||||
(node.IsRepository && node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase)))
|
||||
SelectablePages.Add(page);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _pressedTab = false;
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
using System;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public class LauncherTabSelectedEventArgs : RoutedEventArgs
|
||||
{
|
||||
public ViewModels.LauncherPage Page { get; }
|
||||
|
||||
public LauncherTabSelectedEventArgs(ViewModels.LauncherPage page)
|
||||
{
|
||||
RoutedEvent = LauncherTabsSelector.PageSelectedEvent;
|
||||
Page = page;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class LauncherTabsSelector : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<AvaloniaList<ViewModels.LauncherPage>> PagesProperty =
|
||||
AvaloniaProperty.Register<LauncherTabsSelector, AvaloniaList<ViewModels.LauncherPage>>(nameof(Pages));
|
||||
|
||||
public AvaloniaList<ViewModels.LauncherPage> Pages
|
||||
{
|
||||
get => GetValue(PagesProperty);
|
||||
set => SetValue(PagesProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> SearchFilterProperty =
|
||||
AvaloniaProperty.Register<LauncherTabsSelector, string>(nameof(SearchFilter));
|
||||
|
||||
public string SearchFilter
|
||||
{
|
||||
get => GetValue(SearchFilterProperty);
|
||||
set => SetValue(SearchFilterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<LauncherTabSelectedEventArgs> PageSelectedEvent =
|
||||
RoutedEvent.Register<LauncherTabsSelector, LauncherTabSelectedEventArgs>(nameof(PageSelected), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<LauncherTabSelectedEventArgs> PageSelected
|
||||
{
|
||||
add { AddHandler(PageSelectedEvent, value); }
|
||||
remove { RemoveHandler(PageSelectedEvent, value); }
|
||||
}
|
||||
|
||||
public AvaloniaList<ViewModels.LauncherPage> VisiblePages
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public LauncherTabsSelector()
|
||||
{
|
||||
VisiblePages = new AvaloniaList<ViewModels.LauncherPage>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == PagesProperty || change.Property == SearchFilterProperty)
|
||||
UpdateVisiblePages();
|
||||
}
|
||||
|
||||
private void OnClearSearchFilter(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SearchFilter = string.Empty;
|
||||
}
|
||||
|
||||
private void OnPageSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox { SelectedItem: ViewModels.LauncherPage page })
|
||||
{
|
||||
_isProcessingSelection = true;
|
||||
RaiseEvent(new LauncherTabSelectedEventArgs(page));
|
||||
_isProcessingSelection = false;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateVisiblePages()
|
||||
{
|
||||
if (_isProcessingSelection)
|
||||
return;
|
||||
|
||||
VisiblePages.Clear();
|
||||
|
||||
if (Pages == null)
|
||||
return;
|
||||
|
||||
var filter = SearchFilter?.Trim() ?? "";
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
foreach (var p in Pages)
|
||||
VisiblePages.Add(p);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
if (!page.Node.IsRepository)
|
||||
continue;
|
||||
|
||||
if (page.Node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
|
||||
page.Node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||
VisiblePages.Add(page);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isProcessingSelection = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -129,23 +129,23 @@
|
|||
<CheckBox Grid.Row="5" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.General.ShowAuthorTime}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowAuthorTimeInGraph, Mode=TwoWay}"/>
|
||||
IsChecked="{Binding ShowAuthorTimeInGraph, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="6" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.General.ShowTagsInGraph}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowTagsInGraph, Mode=TwoWay}"/>
|
||||
IsChecked="{Binding ShowTagsInGraph, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="7" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.General.ShowChildren}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowChildren, Mode=TwoWay}"/>
|
||||
IsChecked="{Binding ShowChildren, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="8" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.General.Check4UpdatesOnStartup}"
|
||||
IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=Check4UpdatesOnStartup, Mode=TwoWay}"/>
|
||||
IsChecked="{Binding Check4UpdatesOnStartup, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
|
@ -257,12 +257,12 @@
|
|||
<CheckBox Grid.Row="7" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.Appearance.UseFixedTabWidth}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseFixedTabWidth, Mode=TwoWay}"/>
|
||||
IsChecked="{Binding UseFixedTabWidth, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="8" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.Appearance.UseNativeWindowFrame}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSystemWindowFrame, Mode=OneTime}"
|
||||
IsChecked="{Binding UseSystemWindowFrame, Mode=OneTime}"
|
||||
IsVisible="{OnPlatform False, Linux=True}"
|
||||
IsCheckedChanged="OnUseNativeWindowFrameChanged"/>
|
||||
</Grid>
|
||||
|
|
|
@ -204,7 +204,10 @@
|
|||
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="16,*,Auto,Auto">
|
||||
<Path Grid.Column="0" Width="11" Height="11" HorizontalAlignment="Left" Data="{StaticResource Icons.Local}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
|
||||
<Run Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
||||
<Run Text="{Binding LocalBranchesCount, StringFormat='({0})'}"/>
|
||||
</TextBlock>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
|
@ -231,7 +234,10 @@
|
|||
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="16,*,Auto,Auto">
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Data="{StaticResource Icons.Remotes}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Remotes}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
|
||||
<Run Text="{DynamicResource Text.Repository.Remotes}"/>
|
||||
<Run Text="{Binding Remotes, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
|
@ -256,16 +262,18 @@
|
|||
|
||||
<!-- Tags -->
|
||||
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto,Auto,Auto">
|
||||
<Grid ColumnDefinitions="16,*,Auto,Auto,Auto">
|
||||
<Path Grid.Column="0" Width="11" Height="11" Margin="2,1,0,0" HorizontalAlignment="Left" Data="{StaticResource Icons.Tags}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Tags}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<ToggleButton Grid.Column="3"
|
||||
Classes="tag_display_mode"
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
|
||||
<Run Text="{DynamicResource Text.Repository.Tags}"/>
|
||||
<Run Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<ToggleButton Grid.Column="2"
|
||||
Classes="show_as_tree"
|
||||
Width="14"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowTagsAsTree, Mode=TwoWay}"
|
||||
IsChecked="{Binding ShowTagsAsTree, Mode=TwoWay}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.ShowTagsAsTree}"/>
|
||||
<Button Grid.Column="4"
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0,0,0"
|
||||
|
@ -273,7 +281,7 @@
|
|||
ToolTip.Tip="{DynamicResource Text.Repository.Tags.Sort}">
|
||||
<Path Width="12" Height="12" Margin="0,2,0,0" Data="{StaticResource Icons.Order}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="5"
|
||||
<Button Grid.Column="4"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0"
|
||||
|
@ -288,8 +296,7 @@
|
|||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ShowTagsAsTree="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowTagsAsTree, Mode=OneWay}"
|
||||
Tags="{Binding VisibleTags}"
|
||||
Content="{Binding VisibleTags}"
|
||||
Focusable="False"
|
||||
IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}"
|
||||
SelectionChanged="OnTagsSelectionChanged"
|
||||
|
@ -297,10 +304,18 @@
|
|||
|
||||
<!-- Submodules -->
|
||||
<ToggleButton Grid.Row="6" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto,Auto">
|
||||
<Grid ColumnDefinitions="16,*,Auto,Auto,Auto">
|
||||
<Path Grid.Column="0" Width="10" Height="10" Margin="2,0,0,0" HorizontalAlignment="Left" Data="{StaticResource Icons.Submodules}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Submodules}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
|
||||
<Run Text="{DynamicResource Text.Repository.Submodules}"/>
|
||||
<Run Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<ToggleButton Grid.Column="2"
|
||||
Classes="show_as_tree"
|
||||
Width="14"
|
||||
IsChecked="{Binding ShowSubmodulesAsTree, Mode=TwoWay}"
|
||||
IsVisible="{Binding Submodules, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.ShowSubmodulesAsTree}"/>
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
|
@ -320,44 +335,24 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<ListBox Grid.Row="7"
|
||||
x:Name="SubmoduleList"
|
||||
Height="0"
|
||||
Margin="12,0,4,0"
|
||||
Classes="repo_left_content_list"
|
||||
ItemsSource="{Binding VisibleSubmodules}"
|
||||
SelectionMode="Single"
|
||||
ContextRequested="OnSubmoduleContextRequested"
|
||||
DoubleTapped="OnDoubleTappedSubmodule"
|
||||
PropertyChanged="OnLeftSidebarListBoxPropertyChanged"
|
||||
IsVisible="{Binding IsSubmoduleGroupExpanded, Mode=OneWay}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Submodule">
|
||||
<Grid ColumnDefinitions="Auto,*,8,8">
|
||||
<Path Grid.Column="0" Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Submodule}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Path}" ClipToBounds="True" Classes="primary" TextTrimming="CharacterEllipsis"/>
|
||||
<Path Grid.Column="2"
|
||||
Width="8" Height="8"
|
||||
Fill="Goldenrod"
|
||||
Data="{StaticResource Icons.Modified}"
|
||||
IsVisible="{Binding IsDirty}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<v:SubmodulesView Grid.Row="7"
|
||||
x:Name="SubmoduleList"
|
||||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Content="{Binding VisibleSubmodules}"
|
||||
RowsChanged="OnSubmodulesRowsChanged"
|
||||
Focusable="False"
|
||||
IsVisible="{Binding IsSubmoduleGroupExpanded, Mode=OneWay}"/>
|
||||
|
||||
<!-- Worktrees -->
|
||||
<ToggleButton Grid.Row="8" Classes="group_expander" IsChecked="{Binding IsWorktreeGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto,Auto">
|
||||
<Grid ColumnDefinitions="16,*,Auto,Auto">
|
||||
<Path Grid.Column="0" Width="11" Height="11" Margin="1,0,0,0" HorizontalAlignment="Left" Data="{StaticResource Icons.Worktrees}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Worktrees}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="3"
|
||||
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
|
||||
<Run Text="{DynamicResource Text.Repository.Worktrees}"/>
|
||||
<Run Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0"
|
||||
|
@ -366,7 +361,7 @@
|
|||
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Prune}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="4"
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="0,0,9,0"
|
||||
|
@ -385,7 +380,7 @@
|
|||
SelectionMode="Single"
|
||||
ContextRequested="OnWorktreeContextRequested"
|
||||
DoubleTapped="OnDoubleTappedWorktree"
|
||||
PropertyChanged="OnLeftSidebarListBoxPropertyChanged"
|
||||
PropertyChanged="OnWorktreeListPropertyChanged"
|
||||
IsVisible="{Binding IsWorktreeGroupExpanded, Mode=OneWay}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
|
@ -394,7 +389,7 @@
|
|||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Worktree">
|
||||
<Grid ColumnDefinitions="Auto,*,22">
|
||||
<Grid ColumnDefinitions="Auto,*,22" Background="Transparent">
|
||||
<Path Grid.Column="0" Width="10" Height="10" Margin="8,0,0,0" Data="{StaticResource Icons.Worktree}"/>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Margin="8,0,0,0" TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{Binding Name}"/>
|
||||
|
|
|
@ -179,24 +179,9 @@ namespace SourceGit.Views
|
|||
RemoteBranchTree.UnselectAll();
|
||||
}
|
||||
|
||||
private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
private void OnSubmodulesRowsChanged(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox { SelectedItem: Models.Submodule submodule } grid && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForSubmodule(submodule.Path);
|
||||
menu?.Open(grid);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox { SelectedItem: Models.Submodule submodule } && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
repo.OpenSubmodule(submodule.Path);
|
||||
}
|
||||
|
||||
UpdateLeftSidebarLayout();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
|
@ -221,7 +206,7 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnLeftSidebarListBoxPropertyChanged(object _, AvaloniaPropertyChangedEventArgs e)
|
||||
private void OnWorktreeListPropertyChanged(object _, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == ListBox.ItemsSourceProperty || e.Property == ListBox.IsVisibleProperty)
|
||||
UpdateLeftSidebarLayout();
|
||||
|
@ -250,26 +235,26 @@ namespace SourceGit.Views
|
|||
var remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0;
|
||||
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
|
||||
var desiredTag = vm.IsTagGroupExpanded ? 24.0 * TagsList.Rows : 0;
|
||||
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? 24.0 * vm.VisibleSubmodules.Count : 0;
|
||||
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? 24.0 * SubmoduleList.Rows : 0;
|
||||
var desiredWorktree = vm.IsWorktreeGroupExpanded ? 24.0 * vm.Worktrees.Count : 0;
|
||||
var desiredOthers = desiredTag + desiredSubmodule + desiredWorktree;
|
||||
var hasOverflow = (desiredBranches + desiredOthers > leftHeight);
|
||||
|
||||
if (vm.IsTagGroupExpanded)
|
||||
if (vm.IsWorktreeGroupExpanded)
|
||||
{
|
||||
var height = desiredTag;
|
||||
var height = desiredWorktree;
|
||||
if (hasOverflow)
|
||||
{
|
||||
var test = leftHeight - desiredBranches - desiredSubmodule - desiredWorktree;
|
||||
var test = leftHeight - desiredBranches - desiredTag - desiredSubmodule;
|
||||
if (test < 0)
|
||||
height = Math.Min(200, height);
|
||||
height = Math.Min(120, height);
|
||||
else
|
||||
height = Math.Max(200, test);
|
||||
height = Math.Max(120, test);
|
||||
}
|
||||
|
||||
leftHeight -= height;
|
||||
TagsList.Height = height;
|
||||
hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight;
|
||||
WorktreeList.Height = height;
|
||||
hasOverflow = (desiredBranches + desiredTag + desiredSubmodule) > leftHeight;
|
||||
}
|
||||
|
||||
if (vm.IsSubmoduleGroupExpanded)
|
||||
|
@ -277,32 +262,32 @@ namespace SourceGit.Views
|
|||
var height = desiredSubmodule;
|
||||
if (hasOverflow)
|
||||
{
|
||||
var test = leftHeight - desiredBranches - desiredWorktree;
|
||||
var test = leftHeight - desiredBranches - desiredTag;
|
||||
if (test < 0)
|
||||
height = Math.Min(200, height);
|
||||
height = Math.Min(120, height);
|
||||
else
|
||||
height = Math.Max(200, test);
|
||||
height = Math.Max(120, test);
|
||||
}
|
||||
|
||||
leftHeight -= height;
|
||||
SubmoduleList.Height = height;
|
||||
hasOverflow = (desiredBranches + desiredWorktree) > leftHeight;
|
||||
hasOverflow = (desiredBranches + desiredTag) > leftHeight;
|
||||
}
|
||||
|
||||
if (vm.IsWorktreeGroupExpanded)
|
||||
if (vm.IsTagGroupExpanded)
|
||||
{
|
||||
var height = desiredWorktree;
|
||||
var height = desiredTag;
|
||||
if (hasOverflow)
|
||||
{
|
||||
var test = leftHeight - desiredBranches;
|
||||
if (test < 0)
|
||||
height = Math.Min(200, height);
|
||||
height = Math.Min(120, height);
|
||||
else
|
||||
height = Math.Max(200, test);
|
||||
height = Math.Max(120, test);
|
||||
}
|
||||
|
||||
leftHeight -= height;
|
||||
WorktreeList.Height = height;
|
||||
TagsList.Height = height;
|
||||
}
|
||||
|
||||
if (leftHeight > 0 && desiredBranches > leftHeight)
|
||||
|
|
|
@ -19,11 +19,13 @@
|
|||
<!-- Left -->
|
||||
<Grid Grid.Column="0" RowDefinitions="28,36,*,28,*">
|
||||
<!-- Stash Bar -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto">
|
||||
<Path Grid.Column="0" Margin="8,0,0,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Stashes}"/>
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.Stashes.Stashes}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Stashes, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="4"
|
||||
<TextBlock Grid.Column="1" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0">
|
||||
<Run Text="{DynamicResource Text.Stashes.Stashes}"/>
|
||||
<Run Text="{Binding Stashes, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="26" Height="14"
|
||||
Padding="0"
|
||||
|
@ -105,10 +107,12 @@
|
|||
|
||||
<!-- Changes Bar -->
|
||||
<Border Grid.Row="3" BorderThickness="0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Path Grid.Column="0" Margin="8,0,0,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Changes}"/>
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.Stashes.Changes}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Changes, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<TextBlock Grid.Column="1" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0">
|
||||
<Run Text="{DynamicResource Text.Stashes.Changes}"/>
|
||||
<Run Text="{Binding Changes, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
|
|
223
src/Views/SubmodulesView.axaml
Normal file
223
src/Views/SubmodulesView.axaml
Normal file
|
@ -0,0 +1,223 @@
|
|||
<UserControl 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:v="using:SourceGit.Views"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.SubmodulesView">
|
||||
<UserControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:SubmoduleCollectionAsTree">
|
||||
<ListBox Classes="repo_left_content_list" ItemsSource="{Binding Rows}" SelectionMode="Single">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate DataType="m:Submodule">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="10" Height="10" Data="{StaticResource Icons.Submodule}"/>
|
||||
<TextBlock FontWeight="Bold" Margin="4,0,0,0" Text="{Binding Path}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid RowDefinitions="24,24" ColumnDefinitions="Auto,Auto" Margin="0,8,0,0">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Classes="info_label"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.CommitDetail.Info.SHA}"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Margin="8,0,0,0">
|
||||
<TextBlock Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Path Margin="6,0,0,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Width="12" Height="12"
|
||||
Data="{StaticResource Icons.Check}"
|
||||
Fill="Green"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.Normal}}"/>
|
||||
<Border Height="16"
|
||||
Margin="6,0,0,0" Padding="4,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Background="DarkOrange"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.NotEqual}, ConverterParameter={x:Static m:SubmoduleStatus.Normal}}">
|
||||
<Grid>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.NotInited}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.NotInited}}"/>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.Modified}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.Modified}}"/>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.RevisionChanged}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.RevisionChanged}}"/>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.Unmerged}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.Unmerged}}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
Classes="info_label"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.URL}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
Text="{Binding URL}"
|
||||
Foreground="{DynamicResource Brush.Link}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:SubmoduleTreeNode">
|
||||
<Border Height="24"
|
||||
Background="Transparent"
|
||||
DoubleTapped="OnItemDoubleTapped"
|
||||
ContextRequested="OnItemContextRequested"
|
||||
ToolTip.Tip="{Binding Module}"
|
||||
ToolTip.Placement="Right">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto,Auto"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
VerticalAlignment="Center">
|
||||
<v:SubmoduleTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||
IsVisible="{Binding IsFolder}"/>
|
||||
|
||||
<v:SubmoduleTreeNodeIcon Grid.Column="1"
|
||||
IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
|
||||
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
Margin="8,0,0,0"
|
||||
TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}"/>
|
||||
<Run Text="{Binding ChildCounter}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</TextBlock>
|
||||
|
||||
<Path Grid.Column="3"
|
||||
Width="8" Height="8"
|
||||
Margin="0,0,12,0"
|
||||
Fill="Goldenrod"
|
||||
Data="{StaticResource Icons.Modified}"
|
||||
IsVisible="{Binding IsDirty}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="vm:SubmoduleCollectionAsList">
|
||||
<ListBox Classes="repo_left_content_list" ItemsSource="{Binding Submodules}" SelectionMode="Single">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Submodule">
|
||||
<Border Height="24"
|
||||
Background="Transparent"
|
||||
DoubleTapped="OnItemDoubleTapped"
|
||||
ContextRequested="OnItemContextRequested"
|
||||
ToolTip.Placement="Right">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="10" Height="10" Data="{StaticResource Icons.Submodule}"/>
|
||||
<TextBlock FontWeight="Bold" Margin="4,0,0,0" Text="{Binding Path}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid RowDefinitions="24,24" ColumnDefinitions="Auto,Auto" Margin="0,8,0,0">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Classes="info_label"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.CommitDetail.Info.SHA}"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Margin="8,0,0,0">
|
||||
<TextBlock Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Path Margin="6,0,0,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Width="12" Height="12"
|
||||
Data="{StaticResource Icons.Check}"
|
||||
Fill="Green"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.Normal}}"/>
|
||||
<Border Height="16"
|
||||
Margin="6,0,0,0" Padding="4,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Background="DarkOrange"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.NotEqual}, ConverterParameter={x:Static m:SubmoduleStatus.Normal}}">
|
||||
<Grid>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.NotInited}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.NotInited}}"/>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.Modified}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.Modified}}"/>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.RevisionChanged}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.RevisionChanged}}"/>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.Status.Unmerged}"
|
||||
Foreground="White"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:SubmoduleStatus.Unmerged}}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
Classes="info_label"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Text="{DynamicResource Text.Submodule.URL}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
Text="{Binding URL}"
|
||||
Foreground="{DynamicResource Brush.Link}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
|
||||
<Grid ColumnDefinitions="16,*,Auto" Margin="8,0,0,0" VerticalAlignment="Center">
|
||||
<Path Grid.Column="0" Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Submodule}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Path}" ClipToBounds="True" Classes="primary" TextTrimming="CharacterEllipsis"/>
|
||||
<Path Grid.Column="2"
|
||||
Width="8" Height="8"
|
||||
Margin="8,0,12,0"
|
||||
Fill="Goldenrod"
|
||||
Data="{StaticResource Icons.Modified}"
|
||||
IsVisible="{Binding IsDirty}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DataTemplate>
|
||||
</UserControl.DataTemplates>
|
||||
</UserControl>
|
182
src/Views/SubmodulesView.axaml.cs
Normal file
182
src/Views/SubmodulesView.axaml.cs
Normal file
|
@ -0,0 +1,182 @@
|
|||
using System;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public class SubmoduleTreeNodeToggleButton : ToggleButton
|
||||
{
|
||||
protected override Type StyleKeyOverride => typeof(ToggleButton);
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
|
||||
DataContext is ViewModels.SubmoduleTreeNode { IsFolder: true } node)
|
||||
{
|
||||
var view = this.FindAncestorOfType<SubmodulesView>();
|
||||
view?.ToggleNodeIsExpanded(node);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SubmoduleTreeNodeIcon : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<bool> IsExpandedProperty =
|
||||
AvaloniaProperty.Register<SubmoduleTreeNodeIcon, bool>(nameof(IsExpanded));
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => GetValue(IsExpandedProperty);
|
||||
set => SetValue(IsExpandedProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == IsExpandedProperty)
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
private void UpdateContent()
|
||||
{
|
||||
if (DataContext is not ViewModels.SubmoduleTreeNode node)
|
||||
{
|
||||
Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.Module != null)
|
||||
CreateContent(new Thickness(0, 0, 0, 0), "Icons.Submodule");
|
||||
else if (node.IsExpanded)
|
||||
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open");
|
||||
else
|
||||
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder");
|
||||
}
|
||||
|
||||
private void CreateContent(Thickness margin, string iconKey)
|
||||
{
|
||||
var geo = this.FindResource(iconKey) as StreamGeometry;
|
||||
if (geo == null)
|
||||
return;
|
||||
|
||||
Content = new Avalonia.Controls.Shapes.Path()
|
||||
{
|
||||
Width = 12,
|
||||
Height = 12,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = margin,
|
||||
Data = geo,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SubmodulesView : UserControl
|
||||
{
|
||||
public static readonly RoutedEvent<RoutedEventArgs> RowsChangedEvent =
|
||||
RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<RoutedEventArgs> RowsChanged
|
||||
{
|
||||
add { AddHandler(RowsChangedEvent, value); }
|
||||
remove { RemoveHandler(RowsChangedEvent, value); }
|
||||
}
|
||||
|
||||
public int Rows
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public SubmodulesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void ToggleNodeIsExpanded(ViewModels.SubmoduleTreeNode node)
|
||||
{
|
||||
if (Content is ViewModels.SubmoduleCollectionAsTree tree)
|
||||
{
|
||||
tree.ToggleExpand(node);
|
||||
Rows = tree.Rows.Count;
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == ContentProperty)
|
||||
{
|
||||
if (Content is ViewModels.SubmoduleCollectionAsTree tree)
|
||||
Rows = tree.Rows.Count;
|
||||
else if (Content is ViewModels.SubmoduleCollectionAsList list)
|
||||
Rows = list.Submodules.Count;
|
||||
else
|
||||
Rows = 0;
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
else if (change.Property == IsVisibleProperty)
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is Control control && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
if (control.DataContext is ViewModels.SubmoduleTreeNode node)
|
||||
{
|
||||
if (node.IsFolder)
|
||||
ToggleNodeIsExpanded(node);
|
||||
else if (node.Module.Status != Models.SubmoduleStatus.NotInited)
|
||||
repo.OpenSubmodule(node.Module.Path);
|
||||
}
|
||||
else if (control.DataContext is Models.Submodule m && m.Status != Models.SubmoduleStatus.NotInited)
|
||||
{
|
||||
repo.OpenSubmodule(m.Path);
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnItemContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (sender is Control control && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
if (control.DataContext is ViewModels.SubmoduleTreeNode node && node.Module != null)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForSubmodule(node.Module);
|
||||
menu?.Open(control);
|
||||
}
|
||||
else if (control.DataContext is Models.Submodule m)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForSubmodule(m);
|
||||
menu?.Open(control);
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,14 +23,36 @@
|
|||
<ListBox Classes="repo_left_content_list"
|
||||
ItemsSource="{Binding Rows}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnRowSelectionChanged">
|
||||
SelectionChanged="OnSelectionChanged">
|
||||
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate DataType="vm:TagTreeNodeToolTip">
|
||||
<StackPanel Orientation="Vertical" Spacing="6">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="10" Height="10" Data="{StaticResource Icons.Tag}"/>
|
||||
<TextBlock FontWeight="Bold" Margin="4,0,0,0" Text="{Binding Name}"/>
|
||||
<Border Background="Green" Margin="4,0,0,0" CornerRadius="4" IsVisible="{Binding IsAnnotated}">
|
||||
<TextBlock Text="{DynamicResource Text.CreateTag.Type.Annotated}" Classes="primary" Margin="4,0" Foreground="White"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="{Binding Message}" IsVisible="{Binding Message, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TagTreeNode">
|
||||
<Border Height="24" Background="Transparent" PointerPressed="OnRowPointerPressed" DoubleTapped="OnDoubleTappedNode" ContextRequested="OnRowContextRequested">
|
||||
<Border Height="24"
|
||||
Background="Transparent"
|
||||
PointerPressed="OnItemPointerPressed"
|
||||
DoubleTapped="OnItemDoubleTapped"
|
||||
ContextRequested="OnItemContextRequested"
|
||||
ToolTip.Tip="{Binding ToolTip}"
|
||||
ToolTip.Placement="Right">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{Binding ToolTip}">
|
||||
VerticalAlignment="Center">
|
||||
<v:TagTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
|
@ -38,14 +60,14 @@
|
|||
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||
IsVisible="{Binding IsFolder}"/>
|
||||
|
||||
<v:TagTreeNodeIcon Grid.Column="1"
|
||||
Node="{Binding .}"
|
||||
IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
|
||||
<v:TagTreeNodeIcon Grid.Column="1" IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
|
||||
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}"
|
||||
Margin="8,0,0,0"/>
|
||||
Margin="8,0,0,0">
|
||||
<Run Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}"/>
|
||||
<Run Text="{Binding TagsCount}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</TextBlock>
|
||||
|
||||
<ContentControl Grid.Column="3" Content="{Binding Tag}">
|
||||
<ContentControl.DataTemplates>
|
||||
|
@ -66,11 +88,29 @@
|
|||
Margin="8,0,0,0"
|
||||
ItemsSource="{Binding Tags}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnRowSelectionChanged">
|
||||
SelectionChanged="OnSelectionChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Tag">
|
||||
<Border Height="24" Background="Transparent" PointerPressed="OnRowPointerPressed" ContextRequested="OnRowContextRequested">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center" ToolTip.Tip="{Binding Message}">
|
||||
<Border Height="24"
|
||||
Background="Transparent"
|
||||
PointerPressed="OnItemPointerPressed"
|
||||
ContextRequested="OnItemContextRequested"
|
||||
ToolTip.Placement="Right">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Vertical" Spacing="6">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="10" Height="10" Data="{StaticResource Icons.Tag}"/>
|
||||
<TextBlock FontWeight="Bold" Margin="4,0,0,0" Text="{Binding Name}"/>
|
||||
<Border Background="Green" Margin="4,0,0,0" CornerRadius="4" IsVisible="{Binding IsAnnotated}">
|
||||
<TextBlock Text="{DynamicResource Text.CreateTag.Type.Annotated}" Classes="primary" Margin="4,0" Foreground="White"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="{Binding Message}" IsVisible="{Binding Message, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
|
||||
<Path Grid.Column="0"
|
||||
Margin="8,0,0,0"
|
||||
Width="12" Height="12"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
@ -31,15 +30,6 @@ namespace SourceGit.Views
|
|||
|
||||
public class TagTreeNodeIcon : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<ViewModels.TagTreeNode> NodeProperty =
|
||||
AvaloniaProperty.Register<TagTreeNodeIcon, ViewModels.TagTreeNode>(nameof(Node));
|
||||
|
||||
public ViewModels.TagTreeNode Node
|
||||
{
|
||||
get => GetValue(NodeProperty);
|
||||
set => SetValue(NodeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsExpandedProperty =
|
||||
AvaloniaProperty.Register<TagTreeNodeIcon, bool>(nameof(IsExpanded));
|
||||
|
||||
|
@ -49,16 +39,23 @@ namespace SourceGit.Views
|
|||
set => SetValue(IsExpandedProperty, value);
|
||||
}
|
||||
|
||||
static TagTreeNodeIcon()
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
NodeProperty.Changed.AddClassHandler<TagTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||
IsExpandedProperty.Changed.AddClassHandler<TagTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||
base.OnDataContextChanged(e);
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == IsExpandedProperty)
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
private void UpdateContent()
|
||||
{
|
||||
var node = Node;
|
||||
if (node == null)
|
||||
if (DataContext is not ViewModels.TagTreeNode node)
|
||||
{
|
||||
Content = null;
|
||||
return;
|
||||
|
@ -92,24 +89,6 @@ namespace SourceGit.Views
|
|||
|
||||
public partial class TagsView : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<bool> ShowTagsAsTreeProperty =
|
||||
AvaloniaProperty.Register<TagsView, bool>(nameof(ShowTagsAsTree));
|
||||
|
||||
public bool ShowTagsAsTree
|
||||
{
|
||||
get => GetValue(ShowTagsAsTreeProperty);
|
||||
set => SetValue(ShowTagsAsTreeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<List<Models.Tag>> TagsProperty =
|
||||
AvaloniaProperty.Register<TagsView, List<Models.Tag>>(nameof(Tags));
|
||||
|
||||
public List<Models.Tag> Tags
|
||||
{
|
||||
get => GetValue(TagsProperty);
|
||||
set => SetValue(TagsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent =
|
||||
RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||
|
||||
|
@ -150,33 +129,7 @@ namespace SourceGit.Views
|
|||
{
|
||||
if (Content is ViewModels.TagCollectionAsTree tree)
|
||||
{
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
|
||||
var depth = node.Depth;
|
||||
var idx = tree.Rows.IndexOf(node);
|
||||
if (idx == -1)
|
||||
return;
|
||||
|
||||
if (node.IsExpanded)
|
||||
{
|
||||
var subrows = new List<ViewModels.TagTreeNode>();
|
||||
MakeTreeRows(subrows, node.Children);
|
||||
tree.Rows.InsertRange(idx + 1, subrows);
|
||||
}
|
||||
else
|
||||
{
|
||||
var removeCount = 0;
|
||||
for (int i = idx + 1; i < tree.Rows.Count; i++)
|
||||
{
|
||||
var row = tree.Rows[i];
|
||||
if (row.Depth <= depth)
|
||||
break;
|
||||
|
||||
removeCount++;
|
||||
}
|
||||
tree.Rows.RemoveRange(idx + 1, removeCount);
|
||||
}
|
||||
|
||||
tree.ToggleExpand(node);
|
||||
Rows = tree.Rows.Count;
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
|
@ -186,9 +139,15 @@ namespace SourceGit.Views
|
|||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == ShowTagsAsTreeProperty || change.Property == TagsProperty)
|
||||
if (change.Property == ContentProperty)
|
||||
{
|
||||
UpdateDataSource();
|
||||
if (Content is ViewModels.TagCollectionAsTree tree)
|
||||
Rows = tree.Rows.Count;
|
||||
else if (Content is ViewModels.TagCollectionAsList list)
|
||||
Rows = list.Tags.Count;
|
||||
else
|
||||
Rows = 0;
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
else if (change.Property == IsVisibleProperty)
|
||||
|
@ -197,7 +156,7 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private void OnDoubleTappedNode(object sender, TappedEventArgs e)
|
||||
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is Control { DataContext: ViewModels.TagTreeNode { IsFolder: true } node })
|
||||
ToggleNodeIsExpanded(node);
|
||||
|
@ -205,7 +164,7 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRowPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
private void OnItemPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
var p = e.GetCurrentPoint(this);
|
||||
if (!p.Properties.IsLeftButtonPressed)
|
||||
|
@ -220,7 +179,7 @@ namespace SourceGit.Views
|
|||
repo.NavigateToCommit(nodeTag.SHA);
|
||||
}
|
||||
|
||||
private void OnRowContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
private void OnItemContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
var control = sender as Control;
|
||||
if (control == null)
|
||||
|
@ -243,7 +202,7 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs _)
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs _)
|
||||
{
|
||||
var selected = (sender as ListBox)?.SelectedItem;
|
||||
var selectedTag = null as Models.Tag;
|
||||
|
@ -255,63 +214,6 @@ namespace SourceGit.Views
|
|||
if (selectedTag != null)
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
|
||||
private void MakeTreeRows(List<ViewModels.TagTreeNode> rows, List<ViewModels.TagTreeNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
rows.Add(node);
|
||||
|
||||
if (!node.IsExpanded || !node.IsFolder)
|
||||
continue;
|
||||
|
||||
MakeTreeRows(rows, node.Children);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDataSource()
|
||||
{
|
||||
var tags = Tags;
|
||||
if (tags == null || tags.Count == 0)
|
||||
{
|
||||
Rows = 0;
|
||||
Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShowTagsAsTree)
|
||||
{
|
||||
var oldExpanded = new HashSet<string>();
|
||||
if (Content is ViewModels.TagCollectionAsTree oldTree)
|
||||
{
|
||||
foreach (var row in oldTree.Rows)
|
||||
{
|
||||
if (row.IsFolder && row.IsExpanded)
|
||||
oldExpanded.Add(row.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
var tree = new ViewModels.TagCollectionAsTree();
|
||||
tree.Tree = ViewModels.TagTreeNode.Build(tags, oldExpanded);
|
||||
|
||||
var rows = new List<ViewModels.TagTreeNode>();
|
||||
MakeTreeRows(rows, tree.Tree);
|
||||
tree.Rows.AddRange(rows);
|
||||
|
||||
Content = tree;
|
||||
Rows = rows.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = new ViewModels.TagCollectionAsList();
|
||||
list.Tags.AddRange(tags);
|
||||
|
||||
Content = list;
|
||||
Rows = tags.Count;
|
||||
}
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,13 +61,15 @@
|
|||
<Grid Grid.Row="1" RowDefinitions="28,*">
|
||||
<!-- Unstaged Toolbar -->
|
||||
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<Path Grid.Column="0" Margin="8,0,0,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Changes}"/>
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Unstaged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Unstaged, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
<v:LoadingIcon Grid.Column="3" Width="14" Height="14" Margin="8,0,0,0" IsVisible="{Binding IsStaging}"/>
|
||||
<TextBlock Grid.Column="1" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0">
|
||||
<Run Text="{DynamicResource Text.WorkingCopy.Unstaged}"/>
|
||||
<Run Text="{Binding Unstaged, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<v:LoadingIcon Grid.Column="2" Width="14" Height="14" Margin="8,0,0,0" IsVisible="{Binding IsStaging}"/>
|
||||
|
||||
<Button Grid.Column="5"
|
||||
<Button Grid.Column="4"
|
||||
Classes="icon_button"
|
||||
Width="26" Height="14"
|
||||
Padding="0"
|
||||
|
@ -75,12 +77,12 @@
|
|||
Command="{Binding OpenAssumeUnchanged}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.File.Ignore}"/>
|
||||
</Button>
|
||||
<ToggleButton Grid.Column="6"
|
||||
<ToggleButton Grid.Column="5"
|
||||
Classes="toggle_untracked"
|
||||
Width="26" Height="14"
|
||||
ToolTip.Tip="{DynamicResource Text.WorkingCopy.IncludeUntracked}"
|
||||
IsChecked="{Binding IncludeUntracked, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="7"
|
||||
<Button Grid.Column="6"
|
||||
Classes="icon_button"
|
||||
Width="26" Height="14"
|
||||
Padding="0"
|
||||
|
@ -89,7 +91,7 @@
|
|||
Command="{Binding OpenExternalMergeToolAllConflicts}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Conflict}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="8"
|
||||
<Button Grid.Column="7"
|
||||
Classes="icon_button"
|
||||
Width="26" Height="14"
|
||||
Padding="0"
|
||||
|
@ -102,7 +104,7 @@
|
|||
</ToolTip.Tip>
|
||||
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Down}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="9"
|
||||
<Button Grid.Column="8"
|
||||
Classes="icon_button"
|
||||
Width="26" Height="14"
|
||||
Padding="0"
|
||||
|
@ -110,7 +112,7 @@
|
|||
Command="{Binding StageAll}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.DoubleDown}"/>
|
||||
</Button>
|
||||
<v:ChangeViewModeSwitcher Grid.Column="10"
|
||||
<v:ChangeViewModeSwitcher Grid.Column="9"
|
||||
Width="26" Height="14"
|
||||
Margin="0,1,0,0"
|
||||
ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}"/>
|
||||
|
@ -142,12 +144,14 @@
|
|||
<Grid Grid.Row="3" RowDefinitions="28,*">
|
||||
<!-- Staged Toolbar -->
|
||||
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto">
|
||||
<Path Grid.Column="0" Margin="8,0,0,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Changes}"/>
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Staged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Staged, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
<v:LoadingIcon Grid.Column="3" Width="14" Height="14" Margin="8,0,0,0" IsVisible="{Binding IsUnstaging}"/>
|
||||
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" Click="OnUnstageSelectedButtonClicked">
|
||||
<TextBlock Grid.Column="1" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="4,0,0,0">
|
||||
<Run Text="{DynamicResource Text.WorkingCopy.Staged}"/>
|
||||
<Run Text="{Binding Staged, Converter={x:Static c:ListConverters.ToCount}}"/>
|
||||
</TextBlock>
|
||||
<v:LoadingIcon Grid.Column="2" Width="14" Height="14" Margin="8,0,0,0" IsVisible="{Binding IsUnstaging}"/>
|
||||
<Button Grid.Column="4" Classes="icon_button" Width="26" Height="14" Padding="0" Click="OnUnstageSelectedButtonClicked">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.WorkingCopy.Staged.Unstage}" VerticalAlignment="Center"/>
|
||||
|
@ -156,10 +160,10 @@
|
|||
</ToolTip.Tip>
|
||||
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="6" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.UnstageAll}" Command="{Binding UnstageAll}">
|
||||
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.UnstageAll}" Command="{Binding UnstageAll}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.DoubleUp}"/>
|
||||
</Button>
|
||||
<v:ChangeViewModeSwitcher Grid.Column="7"
|
||||
<v:ChangeViewModeSwitcher Grid.Column="6"
|
||||
Width="26" Height="14"
|
||||
Margin="0,1,0,0"
|
||||
ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}"/>
|
||||
|
|
111
src/Views/WorkspaceSwitcher.axaml
Normal file
111
src/Views/WorkspaceSwitcher.axaml
Normal file
|
@ -0,0 +1,111 @@
|
|||
<UserControl 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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.WorkspaceSwitcher"
|
||||
x:DataType="vm:WorkspaceSwitcher">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{DynamicResource Text.Launcher.Workspaces}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"/>
|
||||
|
||||
<TextBox Grid.Row="1"
|
||||
Height="24"
|
||||
Margin="4,8,4,0"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Text="{Binding SearchFilter, Mode=TwoWay}"
|
||||
KeyDown="OnSearchBoxKeyDown"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
VerticalContentAlignment="Center"
|
||||
v:AutoFocusBehaviour.IsEnabled="True">
|
||||
<TextBox.InnerLeftContent>
|
||||
<Path Width="14" Height="14"
|
||||
Margin="6,0,0,0"
|
||||
Fill="{DynamicResource Brush.FG2}"
|
||||
Data="{StaticResource Icons.Search}"/>
|
||||
</TextBox.InnerLeftContent>
|
||||
|
||||
<TextBox.InnerRightContent>
|
||||
<Button Classes="icon_button"
|
||||
Width="16"
|
||||
Margin="0,0,6,0"
|
||||
Command="{Binding ClearFilter}"
|
||||
IsVisible="{Binding SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
HorizontalAlignment="Right">
|
||||
<Path Width="14" Height="14"
|
||||
Margin="0,1,0,0"
|
||||
Fill="{DynamicResource Brush.FG1}"
|
||||
Data="{StaticResource Icons.Clear}"/>
|
||||
</Button>
|
||||
</TextBox.InnerRightContent>
|
||||
</TextBox>
|
||||
|
||||
<ListBox Grid.Row="2"
|
||||
x:Name="WorkspaceListBox"
|
||||
Width="300"
|
||||
MaxHeight="400"
|
||||
Margin="4,8,4,0"
|
||||
BorderThickness="0"
|
||||
SelectionMode="Single"
|
||||
Background="Transparent"
|
||||
Focusable="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ItemsSource="{Binding VisibleWorkspaces, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedWorkspace, Mode=TwoWay}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="8,0"/>
|
||||
<Setter Property="MinHeight" Value="26"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox">
|
||||
<Setter Property="FocusAdorner">
|
||||
<FocusAdornerTemplate>
|
||||
<Grid/>
|
||||
</FocusAdornerTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:Workspace">
|
||||
<Grid ColumnDefinitions="Auto,*" Background="Transparent" DoubleTapped="OnItemDoubleTapped">
|
||||
<Path Grid.Column="0"
|
||||
Width="12" Height="12"
|
||||
Fill="{Binding Brush}"
|
||||
Data="{StaticResource Icons.Workspace}"
|
||||
IsVisible="{Binding !IsActive}"
|
||||
IsHitTestVisible="False"/>
|
||||
<Path Grid.Column="0"
|
||||
Width="12" Height="12"
|
||||
Fill="{Binding Brush}"
|
||||
Data="{StaticResource Icons.Check}"
|
||||
IsVisible="{Binding IsActive}"
|
||||
IsHitTestVisible="False"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
Classes="primary"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}"
|
||||
IsHitTestVisible="False"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
49
src/Views/WorkspaceSwitcher.axaml.cs
Normal file
49
src/Views/WorkspaceSwitcher.axaml.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class WorkspaceSwitcher : UserControl
|
||||
{
|
||||
public WorkspaceSwitcher()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Enter && DataContext is ViewModels.WorkspaceSwitcher switcher)
|
||||
{
|
||||
switcher.Switch();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.WorkspaceSwitcher switcher)
|
||||
{
|
||||
switcher.Switch();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Down && WorkspaceListBox.ItemCount > 0)
|
||||
{
|
||||
WorkspaceListBox.Focus(NavigationMethod.Directional);
|
||||
|
||||
if (WorkspaceListBox.SelectedIndex < 0)
|
||||
WorkspaceListBox.SelectedIndex = 0;
|
||||
else if (WorkspaceListBox.SelectedIndex < WorkspaceListBox.ItemCount)
|
||||
WorkspaceListBox.SelectedIndex++;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue