mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-28 23:54:59 +00:00
refactor<*>: rewrite all with AvaloniaUI
This commit is contained in:
parent
0136904612
commit
2a62596999
521 changed files with 19780 additions and 23244 deletions
87
src/Views/About.axaml
Normal file
87
src/Views/About.axaml
Normal file
|
@ -0,0 +1,87 @@
|
|||
<Window 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.About"
|
||||
x:DataType="v:About"
|
||||
Title="{DynamicResource Text.About}"
|
||||
Background="{DynamicResource Brush.Window}"
|
||||
SizeToContent="WidthAndHeight"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome">
|
||||
<Grid RowDefinitions="30,*">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto">
|
||||
<Path Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="10,0,0,0"
|
||||
Data="{StaticResource Icons.Info}"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle, Converter={x:Static BoolConverters.Not}}"/>
|
||||
|
||||
<Grid Grid.Column="0" Classes="caption_button_box" Margin="2,4,0,0" IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle}">
|
||||
<Button Classes="caption_button_macos" Click="CloseWindow">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Close}"/>
|
||||
<Path Height="7" Width="7" Stretch="Fill" Data="{StaticResource Icons.MacOS.Close}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.About}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Classes="caption_button"
|
||||
Click="CloseWindow"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle, Converter={x:Static BoolConverters.Not}}">
|
||||
<Path Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0"
|
||||
Width="200" Height="200"
|
||||
Margin="8,0"
|
||||
Source="/App.ico"
|
||||
HorizontalAlignment="Center"/>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="0,20,32,0">
|
||||
<StackPanel Height="48" Orientation="Horizontal">
|
||||
<TextBlock Classes="bold" Text="SourceGit" FontSize="32" />
|
||||
<Border Margin="12,0,0,0" Height="20" CornerRadius="10" Background="{DynamicResource Brush.Accent1}">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect OffsetX="0" OffsetY="0" BlurRadius="6" Color="Black" Opacity=".3"/>
|
||||
</Border.Effect>
|
||||
<TextBlock Classes="monospace" Margin="8,0" Text="{Binding Version}" FontSize="12" Foreground="White"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Margin="2,0,0,0" Text="{DynamicResource Text.About.Copyright}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
|
||||
<StackPanel Orientation="Vertical" Margin="0,28,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{DynamicResource Text.About.BuildWith}" />
|
||||
<TextBlock Text="Avalonia UI" Cursor="Hand" Foreground="{DynamicResource Brush.Accent1}" TextDecorations="Underline" PointerPressed="OnVisitAvaloniaUI"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
|
||||
<TextBlock Text="{DynamicResource Text.About.Editor}" />
|
||||
<TextBlock Text="OneWare.AvaloniaEdit" Cursor="Hand" Foreground="{DynamicResource Brush.Accent1}" TextDecorations="Underline" PointerPressed="OnVisitAvaloniaEdit"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
|
||||
<TextBlock Text="{DynamicResource Text.About.Fonts}" />
|
||||
<TextBlock Text="JetBrains Mono" Cursor="Hand" Foreground="{DynamicResource Brush.Accent1}" TextDecorations="Underline" PointerPressed="OnVisitJetBrainsMonoFont"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
39
src/Views/About.axaml.cs
Normal file
39
src/Views/About.axaml.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class About : Window {
|
||||
public string Version {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public About() {
|
||||
var ver = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
Version = $"{ver.Major}.{ver.Minor}";
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CloseWindow(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnVisitAvaloniaUI(object sender, PointerPressedEventArgs e) {
|
||||
Native.OS.OpenBrowser("https://www.avaloniaui.net/");
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnVisitAvaloniaEdit(object sender, PointerPressedEventArgs e) {
|
||||
Native.OS.OpenBrowser("https://www.nuget.org/packages/OneWare.AvaloniaEdit");
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnVisitJetBrainsMonoFont(object sender, PointerPressedEventArgs e) {
|
||||
Native.OS.OpenBrowser("https://www.jetbrains.com/lp/mono/");
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.About"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="{DynamicResource Text.About}"
|
||||
Width="420" SizeToContent="Height"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Help}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.About}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="Quit"
|
||||
Width="28"
|
||||
IconSize="10"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Content -->
|
||||
<StackPanel Grid.Row="2" Orientation="Vertical">
|
||||
|
||||
<!-- LOGO -->
|
||||
<Image
|
||||
Margin="0,16,0,0"
|
||||
Width="64" Height="64"
|
||||
Source="pack://application:,,,/App.ico"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock
|
||||
Margin="0,24,0,8"
|
||||
Text="{DynamicResource Text.About.Title}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="18"/>
|
||||
|
||||
<!-- Version -->
|
||||
<TextBlock
|
||||
x:Name="version"
|
||||
Margin="0,0,0,8"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="11"
|
||||
Text="VERSION: v1.0"/>
|
||||
|
||||
<DataGrid
|
||||
x:Name="hotkeys"
|
||||
Grid.Row="2"
|
||||
Margin="16,8"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
GridLinesVisibility="All"
|
||||
HorizontalGridLinesBrush="{DynamicResource Brush.Border0}"
|
||||
VerticalGridLinesBrush="{DynamicResource Brush.Border0}"
|
||||
HeadersVisibility="Column"
|
||||
RowHeight="24"
|
||||
ColumnHeaderHeight="24"
|
||||
IsHitTestVisible="False"
|
||||
BorderThickness="1,1,0,0"
|
||||
BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Border BorderThickness="0,0,1,1" BorderBrush="{DynamicResource Brush.Border0}" Background="{DynamicResource Brush.Window}">
|
||||
<TextBlock
|
||||
Text="{TemplateBinding Content}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="DemiBold"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Width="100" Header="{DynamicResource Text.Hotkeys.Col.Key}" Binding="{Binding .Key}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
|
||||
<DataGridTextColumn Width="*" Header="{DynamicResource Text.Hotkeys.Col.Desc}" Binding="{Binding .Desc}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Official site -->
|
||||
<TextBlock HorizontalAlignment="Center" Margin="0,4,0,16">
|
||||
<TextBlock Text="© 2023" Margin="0" Padding="0"/>
|
||||
<Hyperlink NavigateUri="https://github.com/sourcegit-scm/sourcegit.git" RequestNavigate="OnRequestNavigate" ToolTip="https://github.com/sourcegit-scm/sourcegit.git">
|
||||
<Run Text="SourceGit."/>
|
||||
</Hyperlink>
|
||||
<TextBlock Text="All rights reserved." Margin="0" Padding="0"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:Window>
|
|
@ -1,48 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
||||
/// <summary>
|
||||
/// 关于对话框
|
||||
/// </summary>
|
||||
public partial class About : Controls.Window {
|
||||
|
||||
public class Keymap {
|
||||
public string Key { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); }
|
||||
}
|
||||
|
||||
public About() {
|
||||
InitializeComponent();
|
||||
|
||||
var asm = Assembly.GetExecutingAssembly().GetName();
|
||||
version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor}";
|
||||
|
||||
hotkeys.ItemsSource = new List<Keymap>() {
|
||||
new Keymap("CTRL + T", "NewTab"),
|
||||
new Keymap("CTRL + W", "CloseTab"),
|
||||
new Keymap("CTRL + TAB", "NextTab"),
|
||||
new Keymap("CTRL + [1-9]", "SwitchTo"),
|
||||
new Keymap("CTRL + F", "Search"),
|
||||
new Keymap("F5", "Refresh"),
|
||||
new Keymap("SPACE", "ToggleStage"),
|
||||
new Keymap("ESC", "CancelPopup"),
|
||||
};
|
||||
}
|
||||
|
||||
private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
|
||||
var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}");
|
||||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
57
src/Views/AddRemote.axaml
Normal file
57
src/Views/AddRemote.axaml
Normal file
|
@ -0,0 +1,57 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.AddRemote"
|
||||
x:DataType="vm:AddRemote">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Remote.AddTitle}"/>
|
||||
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,Auto" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Remote.Name}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.Remote.Name.Placeholder}"
|
||||
Text="{Binding Name, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Remote.URL}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.Remote.URL.Placeholder}"
|
||||
Text="{Binding Url, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.SSHKey}"
|
||||
IsVisible="{Binding UseSSH}"/>
|
||||
<Grid Grid.Row="2" Grid.Column="2" Height="32" ColumnDefinitions="*,Auto" IsVisible="{Binding UseSSH}">
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtSSHKey"
|
||||
Height="26"
|
||||
CornerRadius="3"
|
||||
Watermark="{DynamicResource Text.SSHKey.Placeholder}"
|
||||
Text="{Binding SSHKey, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="32" Height="32" Margin="4,0,0,0" Click="SelectSSHKey">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
22
src/Views/AddRemote.axaml.cs
Normal file
22
src/Views/AddRemote.axaml.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class AddRemote : UserControl {
|
||||
public AddRemote() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SelectSSHKey(object sender, RoutedEventArgs e) {
|
||||
var options = new FilePickerOpenOptions() { AllowMultiple = false, FileTypeFilter = [new FilePickerFileType("SSHKey") { Patterns = ["*.*"] }] };
|
||||
var toplevel = TopLevel.GetTopLevel(this);
|
||||
var selected = await toplevel.StorageProvider.OpenFilePickerAsync(options);
|
||||
if (selected.Count == 1) {
|
||||
txtSSHKey.Text = selected[0].Path.LocalPath;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
43
src/Views/AddSubmodule.axaml
Normal file
43
src/Views/AddSubmodule.axaml
Normal file
|
@ -0,0 +1,43 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.AddSubmodule"
|
||||
x:DataType="vm:AddSubmodule">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Submodule.Add}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.URL}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.RepositoryURL}"
|
||||
Text="{Binding Url, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Submodule.RelativePath}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.Submodule.RelativePath.Placeholder}"
|
||||
Text="{Binding RelativePath, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="2" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Submodule.FetchNested}"
|
||||
IsChecked="{Binding Recursive, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/AddSubmodule.axaml.cs
Normal file
9
src/Views/AddSubmodule.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class AddSubmodule : UserControl {
|
||||
public AddSubmodule() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
58
src/Views/Apply.axaml
Normal file
58
src/Views/Apply.axaml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Apply"
|
||||
x:DataType="vm:Apply">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Apply.Title}"/>
|
||||
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Apply.File}"/>
|
||||
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtPatchFile"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Watermark="{DynamicResource Text.Apply.File.Placeholder}"
|
||||
Text="{Binding PatchFile, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="32" Height="32" Margin="4,0,0,0" Click="SelectPatchFile">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Apply.WS}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1"
|
||||
Height="28" Padding="8,0"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding WhiteSpaceModes}"
|
||||
SelectedItem="{Binding SelectedWhiteSpaceMode, Mode=TwoWay}"
|
||||
IsEnabled="{Binding !IgnoreWhiteSpace}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:ApplyWhiteSpaceMode}">
|
||||
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<TextBlock Text="{Binding Desc}" Margin="8,0,0,0" FontSize="11" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<CheckBox Grid.Row="2" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Apply.IgnoreWS}"
|
||||
IsChecked="{Binding IgnoreWhiteSpace, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
24
src/Views/Apply.axaml.cs
Normal file
24
src/Views/Apply.axaml.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class Apply : UserControl {
|
||||
public Apply() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SelectPatchFile(object sender, RoutedEventArgs e) {
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
if (topLevel == null) return;
|
||||
|
||||
var options = new FilePickerOpenOptions() { AllowMultiple = false, FileTypeFilter = [ new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }] };
|
||||
var selected = await topLevel.StorageProvider.OpenFilePickerAsync(options);
|
||||
if (selected.Count == 1) {
|
||||
txtPatchFile.Text = selected[0].Path.LocalPath;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
63
src/Views/Archive.axaml
Normal file
63
src/Views/Archive.axaml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Archive"
|
||||
x:DataType="vm:Archive">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Archive.Title}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Archive.Revision}"/>
|
||||
<ContentControl Grid.Column="1" Content="{Binding BasedOn}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding, Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" FontFamily="{StaticResource JetBrainsMono}" Foreground="DarkOrange" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Tag">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Tag}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Archive.File}"/>
|
||||
<Grid Grid.Row="1" Grid.Column="1" ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtSaveFile"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Watermark="{DynamicResource Text.Archive.File.Placeholder}"
|
||||
Text="{Binding SaveFile, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="32" Height="32" Margin="4,0,0,0" Click="SelectOutputFile">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
22
src/Views/Archive.axaml.cs
Normal file
22
src/Views/Archive.axaml.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class Archive : UserControl {
|
||||
public Archive() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SelectOutputFile(object sender, RoutedEventArgs e) {
|
||||
var options = new FilePickerSaveOptions() { DefaultExtension = ".zip", FileTypeChoices = [ new FilePickerFileType("ZIP") { Patterns = [ "*.zip" ]}] };
|
||||
var toplevel = TopLevel.GetTopLevel(this);
|
||||
var selected = await toplevel.StorageProvider.SaveFilePickerAsync(options);
|
||||
if (selected != null) {
|
||||
txtSaveFile.Text = selected.Path.LocalPath;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.AssumeUnchanged"
|
||||
x:Name="me"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="{DynamicResource Text.AssumeUnchanged}"
|
||||
Width="600" MinHeight="200" SizeToContent="Height"
|
||||
ResizeMode="NoResize">
|
||||
<Window.Resources>
|
||||
<Style x:Key="Style.DataGridRow.Change" TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||
<EventSetter Event="RequestBringIntoView" Handler="OnRequestBringIntoView"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Ignores}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.AssumeUnchanged}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="OnQuit"
|
||||
Width="28"
|
||||
IconSize="10"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<DataGrid
|
||||
Grid.Row="2"
|
||||
x:Name="list"
|
||||
Margin="8,4"
|
||||
RowHeight="24"
|
||||
SelectionMode="Extended"
|
||||
SelectionUnit="FullRow"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
ItemsSource="{Binding ElementName=me, Path=Files}"
|
||||
RowStyle="{StaticResource Style.DataGridRow.Change}"
|
||||
Visibility="Collapsed">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn IsReadOnly="True" Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="8,0" Text="{Binding .}" FontSize="14"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="32">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<controls:IconButton
|
||||
Grid.Column="6"
|
||||
Click="Remove"
|
||||
Width="12" Height="12"
|
||||
Margin="4,0"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
ToolTip="{DynamicResource Text.AssumeUnchanged.Remove}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Grid.Row="2" x:Name="mask" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed">
|
||||
<Path Width="48" Height="48" Margin="0,32,0,8" Data="{StaticResource Icon.Empty}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Margin="0,0,0,32" Text="{DynamicResource Text.AssumeUnchanged.Empty}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:Window>
|
|
@ -1,64 +0,0 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
/// <summary>
|
||||
/// 管理不跟踪变更的文件
|
||||
/// </summary>
|
||||
public partial class AssumeUnchanged : Controls.Window {
|
||||
private string repo = null;
|
||||
|
||||
public ObservableCollection<string> Files { get; set; }
|
||||
|
||||
public AssumeUnchanged(string repo) {
|
||||
this.repo = repo;
|
||||
this.Files = new ObservableCollection<string>();
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Task.Run(() => {
|
||||
var unchanged = new Commands.AssumeUnchanged(repo).View();
|
||||
Dispatcher.Invoke(() => {
|
||||
if (unchanged.Count > 0) {
|
||||
foreach (var file in unchanged) Files.Add(file);
|
||||
|
||||
mask.Visibility = Visibility.Collapsed;
|
||||
list.Visibility = Visibility.Visible;
|
||||
list.ItemsSource = Files;
|
||||
} else {
|
||||
list.Visibility = Visibility.Collapsed;
|
||||
mask.Visibility = Visibility.Visible;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void OnQuit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Remove(object sender, RoutedEventArgs e) {
|
||||
var btn = sender as Button;
|
||||
if (btn == null) return;
|
||||
|
||||
var file = btn.DataContext as string;
|
||||
if (file == null) return;
|
||||
|
||||
new Commands.AssumeUnchanged(repo).Remove(file);
|
||||
Files.Remove(file);
|
||||
|
||||
if (Files.Count == 0) {
|
||||
list.Visibility = Visibility.Collapsed;
|
||||
mask.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
105
src/Views/AssumeUnchangedManager.axaml
Normal file
105
src/Views/AssumeUnchangedManager.axaml
Normal file
|
@ -0,0 +1,105 @@
|
|||
<Window 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"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.AssumeUnchangedManager"
|
||||
x:DataType="vm:AssumeUnchangedManager"
|
||||
Title="{DynamicResource Text.AssumeUnchanged}"
|
||||
Background="{DynamicResource Brush.Window}"
|
||||
Width="600" Height="400"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome">
|
||||
<Grid RowDefinitions="30,1,*">
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Path Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="10,0,0,0"
|
||||
Data="{StaticResource Icons.File.Ignore}"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle, Converter={x:Static BoolConverters.Not}}"/>
|
||||
|
||||
<Grid Grid.Column="0" Classes="caption_button_box" Margin="2,4,0,0" IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle}">
|
||||
<Button Classes="caption_button_macos" Click="CloseWindow">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Close}"/>
|
||||
<Path Height="7" Width="7" Stretch="Fill" Data="{StaticResource Icons.MacOS.Close}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.AssumeUnchanged}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Classes="caption_button"
|
||||
Click="CloseWindow"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle, Converter={x:Static BoolConverters.Not}}">
|
||||
<Path Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Line -->
|
||||
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Unchanged Files -->
|
||||
<DataGrid Grid.Row="2"
|
||||
Margin="8"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
ItemsSource="{Binding Files}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Margin="8,0,4,0" Data="{StaticResource Icons.File}"/>
|
||||
<TextBlock Text="{Binding}" Margin="4,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Button Classes="icon_button"
|
||||
Command="{Binding $parent[v:AssumeUnchangedManager].DataContext.(vm:AssumeUnchangedManager).Remove}"
|
||||
CommandParameter="{Binding}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Clear}"/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Empty -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsVisible="{Binding Files.Count, Converter={x:Static c:IntConverters.IsZero}}">
|
||||
<Path Width="48" Height="48" Data="{StaticResource Icons.Empty}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Margin="0,16,0,0" Text="{DynamicResource Text.AssumeUnchanged.Empty}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
14
src/Views/AssumeUnchangedManager.axaml.cs
Normal file
14
src/Views/AssumeUnchangedManager.axaml.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class AssumeUnchangedManager : Window {
|
||||
public AssumeUnchangedManager() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CloseWindow(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
122
src/Views/Avatar.cs
Normal file
122
src/Views/Avatar.cs
Normal file
|
@ -0,0 +1,122 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
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<FontFamily> FallbackFontFamilyProperty =
|
||||
AvaloniaProperty.Register<Avatar, FontFamily>(nameof(FallbackFontFamily));
|
||||
|
||||
public FontFamily FallbackFontFamily {
|
||||
get => GetValue(FallbackFontFamilyProperty);
|
||||
set => SetValue(FallbackFontFamilyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Models.User> UserProperty =
|
||||
AvaloniaProperty.Register<Avatar, Models.User>(nameof(User));
|
||||
|
||||
public Models.User User {
|
||||
get => GetValue(UserProperty);
|
||||
set => SetValue(UserProperty, value);
|
||||
}
|
||||
|
||||
static Avatar() {
|
||||
AffectsRender<Avatar>(FallbackFontFamilyProperty);
|
||||
UserProperty.Changed.AddClassHandler<Avatar>(OnUserPropertyChanged);
|
||||
}
|
||||
|
||||
public Avatar() {
|
||||
var refetch = new MenuItem() { Header = App.Text("RefetchAvatar") };
|
||||
refetch.Click += (o, e) => {
|
||||
if (User != null) {
|
||||
_image = Models.AvatarManager.Request(_emailMD5, true);
|
||||
InvalidateVisual();
|
||||
}
|
||||
};
|
||||
|
||||
ContextMenu = new ContextMenu();
|
||||
ContextMenu.Items.Add(refetch);
|
||||
|
||||
Models.AvatarManager.Subscribe(this);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context) {
|
||||
if (User == null) return;
|
||||
|
||||
float corner = (float)Math.Max(2, Bounds.Width / 16);
|
||||
if (_image != null) {
|
||||
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
|
||||
context.PushClip(new RoundedRect(rect, corner));
|
||||
context.DrawImage(_image, 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);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAvatarResourceReady(string md5, Bitmap bitmap) {
|
||||
if (_emailMD5 == md5) {
|
||||
_image = bitmap;
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e) {
|
||||
if (avatar.User == null) {
|
||||
avatar._emailMD5 = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = string.IsNullOrWhiteSpace(avatar.User.Name) ? "?" : avatar.User.Name.Substring(0, 1);
|
||||
var chars = placeholder.ToCharArray();
|
||||
var sum = 0;
|
||||
foreach (var c in chars) sum += Math.Abs(c);
|
||||
|
||||
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(avatar.User.Email.ToLower().Trim()));
|
||||
var builder = new StringBuilder();
|
||||
foreach (var c in hash) builder.Append(c.ToString("x2"));
|
||||
var md5 = builder.ToString();
|
||||
if (avatar._emailMD5 != md5) {
|
||||
avatar._emailMD5 = md5;
|
||||
avatar._image = Models.AvatarManager.Request(md5, false);
|
||||
}
|
||||
|
||||
avatar._fallbackBrush = new LinearGradientBrush {
|
||||
GradientStops = FALLBACK_GRADIENTS[sum % FALLBACK_GRADIENTS.Length],
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
};
|
||||
|
||||
var typeface = avatar.FallbackFontFamily == null ? Typeface.Default : new Typeface(avatar.FallbackFontFamily);
|
||||
|
||||
avatar._fallbackLabel = new FormattedText(
|
||||
placeholder,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
avatar.Width * 0.65,
|
||||
Brushes.White);
|
||||
|
||||
avatar.InvalidateVisual();
|
||||
}
|
||||
|
||||
private FormattedText _fallbackLabel = null;
|
||||
private LinearGradientBrush _fallbackBrush = null;
|
||||
private string _emailMD5 = null;
|
||||
private Bitmap _image = null;
|
||||
}
|
||||
}
|
76
src/Views/Blame.axaml
Normal file
76
src/Views/Blame.axaml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:m="using:SourceGit.Models"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Blame"
|
||||
x:Name="me"
|
||||
x:DataType="vm:Blame"
|
||||
Title="{DynamicResource Text.Blame}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border0}"
|
||||
Background="{DynamicResource Brush.TitleBar}"
|
||||
MinWidth="1280" MinHeight="720"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome">
|
||||
<Grid Margin="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToContentMargin}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToTitleBarHeight}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
<!-- Bottom border -->
|
||||
<Rectangle Grid.Column="0" Grid.ColumnSpan="5" Fill="{DynamicResource Brush.Border0}" Height="1" VerticalAlignment="Bottom"/>
|
||||
|
||||
<!-- Caption Buttons (macOS) -->
|
||||
<Border Grid.Column="0" IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle}">
|
||||
<v:CaptionButtonsMacOS/>
|
||||
</Border>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="1" Margin="8,0,0,0" Width="12" Height="12" Data="{StaticResource Icons.Blame}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="2" Margin="8,0" Text="{DynamicResource Text.Blame}" FontWeight="Bold" IsHitTestVisible="False" VerticalAlignment="Center"/>
|
||||
|
||||
<!-- Blame information -->
|
||||
<TextBlock Grid.Column="3" Text="{Binding Title}" FontSize="11" FontStyle="Italic" IsHitTestVisible="False" VerticalAlignment="Center"/>
|
||||
|
||||
<!-- Caption Buttons (Windows) -->
|
||||
<Border Grid.Column="4" IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=UseMacOSStyle, Converter={x:Static BoolConverters.Not}}">
|
||||
<v:CaptionButtons/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Background="{DynamicResource Brush.Window}">
|
||||
<!-- Blame View -->
|
||||
<v:BlameTextEditor HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="0"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
FontFamily="{StaticResource JetBrainsMono}"
|
||||
FontSize="12"
|
||||
BlameData="{Binding Data}"/>
|
||||
|
||||
<!-- Not supported mask (for binary files) -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsBinary}">
|
||||
<Path Width="64" Height="64" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Error}"/>
|
||||
<TextBlock Margin="0,16,0,0" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Foreground="{DynamicResource Brush.FG2}" Text="{DynamicResource Text.BlameTypeNotSupported}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Loading -->
|
||||
<Grid Grid.Row="1" Background="{DynamicResource Brush.Window}" IsVisible="{Binding Data, Converter={x:Static ObjectConverters.IsNull}}">
|
||||
<Path Width="48" Height="48" Classes="rotating" Data="{StaticResource Icons.Loading}" IsVisible="{Binding Data, Converter={x:Static ObjectConverters.IsNull}}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
261
src/Views/Blame.axaml.cs
Normal file
261
src/Views/Blame.axaml.cs
Normal file
|
@ -0,0 +1,261 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Styling;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Document;
|
||||
using AvaloniaEdit.Editing;
|
||||
using AvaloniaEdit.Rendering;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using AvaloniaEdit.Utils;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public class BlameTextEditor : TextEditor {
|
||||
public class CommitInfoMargin : AbstractMargin {
|
||||
public CommitInfoMargin(BlameTextEditor editor) {
|
||||
_editor = editor;
|
||||
ClipToBounds = true;
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context) {
|
||||
if (_editor.BlameData == null) return;
|
||||
|
||||
var view = TextView;
|
||||
if (view != null && view.VisualLinesValid) {
|
||||
var typeface = view.CreateTypeface();
|
||||
var underlinePen = new Pen(Brushes.DarkOrange, 1);
|
||||
|
||||
foreach (var line in view.VisualLines) {
|
||||
var lineNumber = line.FirstDocumentLine.LineNumber;
|
||||
if (lineNumber > _editor.BlameData.LineInfos.Count) break;
|
||||
|
||||
var info = _editor.BlameData.LineInfos[lineNumber - 1];
|
||||
if (!info.IsFirstInGroup) continue;
|
||||
|
||||
var x = 0.0;
|
||||
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop) - view.VerticalOffset;
|
||||
|
||||
var shaLink = new FormattedText(
|
||||
info.CommitSHA,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
_editor.FontSize,
|
||||
Brushes.DarkOrange);
|
||||
context.DrawText(shaLink, new Point(x, y));
|
||||
context.DrawLine(underlinePen, new Point(x, y + shaLink.Baseline + 2), new Point(x + shaLink.Width, y + shaLink.Baseline + 2));
|
||||
x += shaLink.Width + 8;
|
||||
|
||||
var time = new FormattedText(
|
||||
info.Time,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
_editor.FontSize,
|
||||
_editor.Foreground);
|
||||
context.DrawText(time, new Point(x, y));
|
||||
x += time.Width + 8;
|
||||
|
||||
var author = new FormattedText(
|
||||
info.Author,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
_editor.FontSize,
|
||||
_editor.Foreground);
|
||||
context.DrawText(author, new Point(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize) {
|
||||
return new Size(250, 0);
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e) {
|
||||
base.OnPointerPressed(e);
|
||||
|
||||
var view = TextView;
|
||||
if (!e.Handled && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && view != null && view.VisualLinesValid) {
|
||||
var pos = e.GetPosition(this);
|
||||
var typeface = view.CreateTypeface();
|
||||
|
||||
foreach (var line in view.VisualLines) {
|
||||
var lineNumber = line.FirstDocumentLine.LineNumber;
|
||||
if (lineNumber >= _editor.BlameData.LineInfos.Count) break;
|
||||
|
||||
var info = _editor.BlameData.LineInfos[lineNumber - 1];
|
||||
if (!info.IsFirstInGroup) continue;
|
||||
|
||||
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop) - view.VerticalOffset;
|
||||
var shaLink = new FormattedText(
|
||||
info.CommitSHA,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
_editor.FontSize,
|
||||
Brushes.DarkOrange);
|
||||
|
||||
var rect = new Rect(0, y, shaLink.Width, shaLink.Height);
|
||||
if (rect.Contains(pos)) {
|
||||
_editor.OnCommitSHAClicked(info.CommitSHA);
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BlameTextEditor _editor = null;
|
||||
}
|
||||
|
||||
public class VerticalSeperatorMargin : AbstractMargin {
|
||||
public VerticalSeperatorMargin(BlameTextEditor editor) {
|
||||
_editor = editor;
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context) {
|
||||
var pen = new Pen(_editor.BorderBrush, 1);
|
||||
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize) {
|
||||
return new Size(1, 0);
|
||||
}
|
||||
|
||||
private BlameTextEditor _editor = null;
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Models.BlameData> BlameDataProperty =
|
||||
AvaloniaProperty.Register<BlameTextEditor, Models.BlameData>(nameof(BlameData));
|
||||
|
||||
public Models.BlameData BlameData {
|
||||
get => GetValue(BlameDataProperty);
|
||||
set => SetValue(BlameDataProperty, value);
|
||||
}
|
||||
|
||||
protected override Type StyleKeyOverride => typeof(TextEditor);
|
||||
|
||||
public BlameTextEditor() : base(new TextArea(), new TextDocument()) {
|
||||
IsReadOnly = true;
|
||||
ShowLineNumbers = false;
|
||||
WordWrap = false;
|
||||
}
|
||||
|
||||
public void OnCommitSHAClicked(string sha) {
|
||||
if (DataContext is ViewModels.Blame blame) {
|
||||
blame.NavigateToCommit(sha);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e) {
|
||||
base.OnLoaded(e);
|
||||
|
||||
TextArea.LeftMargins.Add(new LineNumberMargin() { Margin = new Thickness(8, 0) });
|
||||
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
|
||||
TextArea.LeftMargins.Add(new CommitInfoMargin(this) { Margin = new Thickness(8, 0) });
|
||||
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
|
||||
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
|
||||
TextArea.TextView.Margin = new Thickness(4, 0);
|
||||
|
||||
if (App.Current?.ActualThemeVariant == ThemeVariant.Dark) {
|
||||
_registryOptions = new RegistryOptions(ThemeName.DarkPlus);
|
||||
} else {
|
||||
_registryOptions = new RegistryOptions(ThemeName.LightPlus);
|
||||
}
|
||||
|
||||
_textMate = this.InstallTextMate(_registryOptions);
|
||||
|
||||
if (BlameData != null) {
|
||||
_textMate.SetGrammar(_registryOptions.GetScopeByExtension(Path.GetExtension(BlameData.File)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUnloaded(RoutedEventArgs e) {
|
||||
base.OnUnloaded(e);
|
||||
|
||||
TextArea.LeftMargins.Clear();
|
||||
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
|
||||
|
||||
_registryOptions = null;
|
||||
_textMate.Dispose();
|
||||
_textMate = null;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) {
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == BlameDataProperty) {
|
||||
if (BlameData != null) {
|
||||
Text = BlameData.Content;
|
||||
if (_textMate != null) _textMate.SetGrammar(_registryOptions.GetScopeByExtension(Path.GetExtension(BlameData.File)));
|
||||
} else {
|
||||
Text = string.Empty;
|
||||
}
|
||||
} else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null && _textMate != null) {
|
||||
if (App.Current?.ActualThemeVariant == ThemeVariant.Dark) {
|
||||
_textMate.SetTheme(_registryOptions.LoadTheme(ThemeName.DarkPlus));
|
||||
} else {
|
||||
_textMate.SetTheme(_registryOptions.LoadTheme(ThemeName.LightPlus));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) {
|
||||
var selected = SelectedText;
|
||||
if (string.IsNullOrEmpty(selected)) return;
|
||||
|
||||
var icon = new Avalonia.Controls.Shapes.Path();
|
||||
icon.Width = 10;
|
||||
icon.Height = 10;
|
||||
icon.Stretch = Stretch.Uniform;
|
||||
icon.Data = App.Current?.FindResource("Icons.Copy") as StreamGeometry;
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = App.Text("Copy");
|
||||
copy.Icon = icon;
|
||||
copy.Click += (o, ev) => {
|
||||
App.CopyText(selected);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(copy);
|
||||
menu.Open(TextArea.TextView);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private RegistryOptions _registryOptions = null;
|
||||
private TextMate.Installation _textMate = null;
|
||||
}
|
||||
|
||||
public partial class Blame : Window {
|
||||
public Blame() {
|
||||
if (App.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
Owner = desktop.MainWindow;
|
||||
}
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e) {
|
||||
base.OnClosed(e);
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void OnCommitSHAPointerPressed(object sender, PointerPressedEventArgs e) {
|
||||
if (DataContext is ViewModels.Blame blame) {
|
||||
var txt = sender as TextBlock;
|
||||
blame.NavigateToCommit(txt.Text);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.Blame"
|
||||
x:Name="me"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
xmlns:models="clr-namespace:SourceGit.Models"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Text.Blame}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Height="600" Width="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title Bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Commit}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.Blame}"/>
|
||||
|
||||
<!-- Description -->
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
x:Name="txtFile"
|
||||
Margin="8,0,0,0"
|
||||
FontFamily="{Binding Source={x:Static models:Preference.Instance}, Path=General.FontFamilyContent, Mode=OneWay}"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
FontSize="11"/>
|
||||
|
||||
<!-- Window Commands -->
|
||||
<StackPanel Grid.Column="4" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<controls:IconButton Click="Minimize" Width="48" IconSize="10" Icon="{StaticResource Icon.Minimize}" HoverBackground="#40000000" Opacity="1"/>
|
||||
<ToggleButton Style="{StaticResource Style.ToggleButton.MaxOrRestore}" Width="48" IsChecked="{Binding ElementName=me, Path=IsMaximized}"/>
|
||||
<controls:IconButton Click="Quit" Width="48" IconSize="10" Icon="{StaticResource Icon.Close}" HoverBackground="Red" Opacity="1"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Line -->
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Viewer -->
|
||||
<DataGrid
|
||||
Grid.Row="2"
|
||||
x:Name="blame"
|
||||
GridLinesVisibility="Vertical"
|
||||
VerticalGridLinesBrush="{DynamicResource Brush.Border2}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
FrozenColumnCount="2"
|
||||
RowHeight="16"
|
||||
SelectionUnit="FullRow"
|
||||
SelectionMode="Single"
|
||||
SizeChanged="OnViewerSizeChanged"
|
||||
SelectionChanged="OnSelectionChanged">
|
||||
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="{x:Type DataGridRow}">
|
||||
<EventSetter Event="RequestBringIntoView" Handler="OnViewerRequestBringIntoView"/>
|
||||
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsSelected" Value="False"/>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</MultiTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="RenderOptions.ClearTypeHint" Value="Enabled"/>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="Auto">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock x:Name="LineNumber" Text="{Binding Line.LineNumber}" HorizontalAlignment="Right" Padding="8,0"/>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="LineNumber" Property="Foreground" Value="DarkOrange"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="Auto" IsReadOnly="True">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid x:Name="BG">
|
||||
<Border x:Name="Content" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="4,0" MaxWidth="300">
|
||||
<TextBlock>
|
||||
<Hyperlink NavigateUri="{Binding Line.CommitSHA}" RequestNavigate="GotoCommit" Foreground="DarkOrange" ToolTip="GOTO COMMIT">
|
||||
<Run Text="{Binding Line.CommitSHA, Mode=OneWay}"/>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{Binding Line.Time}" Margin="8,0"/>
|
||||
<TextBlock Text="{Binding Line.Author}" Foreground="DarkOrange"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsFirstLine}" Value="True">
|
||||
<Setter TargetName="Content" Property="BorderThickness" Value="0"/>
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding IsFirstLineInGroup}" Value="False">
|
||||
<Setter TargetName="Content" Property="Visibility" Value="Hidden"/>
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="BG" Property="Background" Value="#44000000"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="SizeToCells" MinWidth="1" IsReadOnly="True">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border x:Name="BG" BorderThickness="0">
|
||||
<TextBlock Text="{Binding Line.Content}" Style="{DynamicResource Style.TextBlock.LineContent}"/>
|
||||
</Border>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="BG" Property="Background" Value="#44000000"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel
|
||||
x:Name="notSupport"
|
||||
Grid.Row="3"
|
||||
Orientation="Vertical"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Background="{DynamicResource Brush.Window}"
|
||||
Visibility="Collapsed">
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icon.Error}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Text="{DynamicResource Text.BlameTypeNotSupported}" Margin="0,16,0,0" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Loading -->
|
||||
<controls:Loading x:Name="loading" Grid.Row="3" Width="48" Height="48" IsAnimating="True"/>
|
||||
</Grid>
|
||||
</controls:Window>
|
|
@ -1,173 +0,0 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
/// <summary>
|
||||
/// 逐行追溯
|
||||
/// </summary>
|
||||
public partial class Blame : Controls.Window {
|
||||
/// <summary>
|
||||
/// DataGrid数据源结构
|
||||
/// </summary>
|
||||
public class Record : INotifyPropertyChanged {
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 原始Blame行数据
|
||||
/// </summary>
|
||||
public Models.BlameLine Line { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是第一行
|
||||
/// </summary>
|
||||
public bool IsFirstLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 前一行与本行的提交不同
|
||||
/// </summary>
|
||||
public bool IsFirstLineInGroup { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否当前选中,会影响背景色
|
||||
/// </summary>
|
||||
private bool isSelected = false;
|
||||
public bool IsSelected {
|
||||
get { return isSelected; }
|
||||
set {
|
||||
if (isSelected != value) {
|
||||
isSelected = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blame数据
|
||||
/// </summary>
|
||||
public ObservableCollection<Record> Records { get; set; }
|
||||
|
||||
public Blame(string repo, string file, string revision) {
|
||||
InitializeComponent();
|
||||
|
||||
this.repo = repo;
|
||||
Records = new ObservableCollection<Record>();
|
||||
txtFile.Text = $"{file}@{revision.Substring(0, 8)}";
|
||||
|
||||
Task.Run(() => {
|
||||
var lfs = new Commands.LFS(repo).IsFiltered(file);
|
||||
if (lfs) {
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.IsAnimating = false;
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
notSupport.Visibility = Visibility.Visible;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var rs = new Commands.Blame(repo, file, revision).Result();
|
||||
if (rs.IsBinary) {
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.IsAnimating = false;
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
notSupport.Visibility = Visibility.Visible;
|
||||
});
|
||||
} else {
|
||||
string lastSHA = null;
|
||||
foreach (var line in rs.Lines) {
|
||||
var r = new Record();
|
||||
r.Line = line;
|
||||
r.IsSelected = false;
|
||||
|
||||
if (line.CommitSHA != lastSHA) {
|
||||
lastSHA = line.CommitSHA;
|
||||
r.IsFirstLineInGroup = true;
|
||||
} else {
|
||||
r.IsFirstLineInGroup = false;
|
||||
}
|
||||
|
||||
Records.Add(r);
|
||||
}
|
||||
|
||||
if (Records.Count > 0) Records[0].IsFirstLine = true;
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.IsAnimating = false;
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
blame.ItemsSource = Records;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region WINDOW_COMMANDS
|
||||
private void Minimize(object sender, RoutedEventArgs e) {
|
||||
SystemCommands.MinimizeWindow(this);
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EVENTS
|
||||
private T GetVisualChild<T>(DependencyObject parent) where T : Visual {
|
||||
T child = null;
|
||||
|
||||
int count = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
|
||||
child = v as T;
|
||||
|
||||
if (child == null) {
|
||||
child = GetVisualChild<T>(v);
|
||||
}
|
||||
|
||||
if (child != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
private void OnViewerSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||
var total = blame.ActualWidth;
|
||||
var offset = blame.NonFrozenColumnsViewportHorizontalOffset;
|
||||
var minWidth = total - offset;
|
||||
|
||||
var scroller = GetVisualChild<ScrollViewer>(blame);
|
||||
if (scroller != null && scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
|
||||
|
||||
blame.Columns[2].MinWidth = minWidth;
|
||||
blame.Columns[2].Width = DataGridLength.SizeToCells;
|
||||
blame.UpdateLayout();
|
||||
}
|
||||
|
||||
private void OnViewerRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
var r = blame.SelectedItem as Record;
|
||||
if (r == null) return;
|
||||
|
||||
foreach (var one in Records) {
|
||||
one.IsSelected = one.Line.CommitSHA == r.Line.CommitSHA;
|
||||
}
|
||||
}
|
||||
|
||||
private void GotoCommit(object sender, RequestNavigateEventArgs e) {
|
||||
Models.Watcher.Get(repo).NavigateTo(e.Uri.OriginalString);
|
||||
e.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private string repo = null;
|
||||
}
|
||||
}
|
23
src/Views/CaptionButtons.axaml
Normal file
23
src/Views/CaptionButtons.axaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<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:v="using:SourceGit.Views"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CaptionButtons">
|
||||
<Grid Classes="caption_button_box">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="caption_button" Click="MinimizeWindow">
|
||||
<Path Data="{StaticResource Icons.Window.Minimize}"/>
|
||||
</Button>
|
||||
<Button Classes="caption_button" Click="MaximizeOrRestoreWindow">
|
||||
<Path Data="{Binding $parent[Window].WindowState, Converter={x:Static c:WindowStateConverters.ToMaxOrRestoreIcon}}"/>
|
||||
</Button>
|
||||
<Button Classes="caption_button" Click="CloseWindow">
|
||||
<Path Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
33
src/Views/CaptionButtons.axaml.cs
Normal file
33
src/Views/CaptionButtons.axaml.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CaptionButtons : UserControl {
|
||||
public CaptionButtons() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void MinimizeWindow(object sender, RoutedEventArgs e) {
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null) {
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximizeOrRestoreWindow(object sender, RoutedEventArgs e) {
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null) {
|
||||
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseWindow(object sender, RoutedEventArgs e) {
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null) {
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
src/Views/CaptionButtonsMacOS.axaml
Normal file
32
src/Views/CaptionButtonsMacOS.axaml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<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:v="using:SourceGit.Views"
|
||||
xmlns:m="using:SourceGit.Models"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CaptionButtonsMacOS">
|
||||
<Grid Classes="caption_button_box">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="caption_button_macos" Click="CloseWindow" Margin="10,0,0,0">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Close}"/>
|
||||
<Path Height="7" Width="7" Stretch="Fill" Data="{StaticResource Icons.MacOS.Close}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
<Button Classes="caption_button_macos" Click="MinimizeWindow">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Minimize}"/>
|
||||
<Path Height="2" Width="8" Stretch="Fill" Data="{StaticResource Icons.MacOS.Minimize}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
<Button Classes="caption_button_macos" Click="MaximizeOrRestoreWindow">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Maximize}"/>
|
||||
<Path Height="8" Width="8" Stretch="Fill" Data="{StaticResource Icons.MacOS.Maximize}}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
33
src/Views/CaptionButtonsMacOS.axaml.cs
Normal file
33
src/Views/CaptionButtonsMacOS.axaml.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CaptionButtonsMacOS : UserControl {
|
||||
public CaptionButtonsMacOS() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void MinimizeWindow(object sender, RoutedEventArgs e) {
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null) {
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximizeOrRestoreWindow(object sender, RoutedEventArgs e) {
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null) {
|
||||
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseWindow(object sender, RoutedEventArgs e) {
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null) {
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
112
src/Views/ChangeStatusIcon.cs
Normal file
112
src/Views/ChangeStatusIcon.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public class ChangeStatusIcon : Control {
|
||||
private static readonly IBrush[] BACKGROUNDS = [
|
||||
Brushes.Transparent,
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
new LinearGradientBrush {
|
||||
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
|
||||
},
|
||||
];
|
||||
|
||||
private static readonly string[] INDICATOR = ["?", "±", "+", "−", "➜", "❏", "U", "★"];
|
||||
|
||||
public static readonly StyledProperty<bool> IsWorkingCopyChangeProperty =
|
||||
AvaloniaProperty.Register<Avatar, bool>(nameof(IsWorkingCopyChange));
|
||||
|
||||
public bool IsWorkingCopyChange {
|
||||
get => GetValue(IsWorkingCopyChangeProperty);
|
||||
set => SetValue(IsWorkingCopyChangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Models.Change> ChangeProperty =
|
||||
AvaloniaProperty.Register<Avatar, Models.Change>(nameof(Change));
|
||||
|
||||
public Models.Change Change {
|
||||
get => GetValue(ChangeProperty);
|
||||
set => SetValue(ChangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<FontFamily> IconFontFamilyProperty =
|
||||
AvaloniaProperty.Register<Avatar, FontFamily>(nameof(IconFontFamily));
|
||||
|
||||
public FontFamily IconFontFamily {
|
||||
get => GetValue(IconFontFamilyProperty);
|
||||
set => SetValue(IconFontFamilyProperty, value);
|
||||
}
|
||||
|
||||
static ChangeStatusIcon() {
|
||||
AffectsRender<ChangeStatusIcon>(IsWorkingCopyChangeProperty, ChangeProperty, IconFontFamilyProperty);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context) {
|
||||
if (Change == null || Bounds.Width <= 0) return;
|
||||
|
||||
var typeface = IconFontFamily == null ? Typeface.Default : new Typeface(IconFontFamily);
|
||||
|
||||
IBrush background = null;
|
||||
string indicator;
|
||||
if (IsWorkingCopyChange) {
|
||||
if (Change.IsConflit) {
|
||||
background = Brushes.OrangeRed;
|
||||
indicator = "!";
|
||||
} else {
|
||||
background = BACKGROUNDS[(int)Change.WorkTree];
|
||||
indicator = INDICATOR[(int)Change.WorkTree];
|
||||
}
|
||||
} else {
|
||||
background = BACKGROUNDS[(int)Change.Index];
|
||||
indicator = INDICATOR[(int)Change.Index];
|
||||
}
|
||||
|
||||
var txt = new FormattedText(
|
||||
indicator,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
Bounds.Width * 0.8,
|
||||
Brushes.White);
|
||||
|
||||
float corner = (float)Math.Max(2, Bounds.Width / 16);
|
||||
Point textOrigin = new Point((Bounds.Width - txt.Width) * 0.5, (Bounds.Height - txt.Height) * 0.5);
|
||||
context.DrawRectangle(background, null, new Rect(0, 0, Bounds.Width, Bounds.Height), corner, corner);
|
||||
context.DrawText(txt, textOrigin);
|
||||
}
|
||||
}
|
||||
}
|
35
src/Views/ChangeViewModeSwitcher.axaml
Normal file
35
src/Views/ChangeViewModeSwitcher.axaml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<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.ChangeViewModeSwitcher"
|
||||
x:DataType="v:ChangeViewModeSwitcher">
|
||||
<Button Classes="icon_button" Padding="0" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center" ToolTip.Tip="{DynamicResource Text.ChangeDisplayMode}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedLeft">
|
||||
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.List}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.List}">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.List}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Grid}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.Grid}">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Grid}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Tree}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.Tree}">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Tree}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
|
||||
<Path Stretch="Uniform" Fill="{DynamicResource Brush.FG2}" Data="{Binding ViewMode, Converter={x:Static c:ChangeViewModeConverters.ToIcon}}"/>
|
||||
</Button>
|
||||
</UserControl>
|
23
src/Views/ChangeViewModeSwitcher.axaml.cs
Normal file
23
src/Views/ChangeViewModeSwitcher.axaml.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class ChangeViewModeSwitcher : UserControl {
|
||||
public static readonly StyledProperty<Models.ChangeViewMode> ViewModeProperty =
|
||||
AvaloniaProperty.Register<ChangeViewModeSwitcher, Models.ChangeViewMode>(nameof(ViewMode));
|
||||
|
||||
public Models.ChangeViewMode ViewMode {
|
||||
get => GetValue(ViewModeProperty);
|
||||
set => SetValue(ViewModeProperty, value);
|
||||
}
|
||||
|
||||
public ChangeViewModeSwitcher() {
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SwitchMode(object param) {
|
||||
ViewMode = (Models.ChangeViewMode)param;
|
||||
}
|
||||
}
|
||||
}
|
19
src/Views/Checkout.axaml
Normal file
19
src/Views/Checkout.axaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Checkout"
|
||||
x:DataType="vm:Checkout">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Checkout}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,16,0,0">
|
||||
<TextBlock Text="{DynamicResource Text.Checkout.Target}"/>
|
||||
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Text="{Binding Branch}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/Checkout.axaml.cs
Normal file
9
src/Views/Checkout.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class Checkout : UserControl {
|
||||
public Checkout() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
32
src/Views/CherryPick.axaml
Normal file
32
src/Views/CherryPick.axaml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CherryPick"
|
||||
x:DataType="vm:CherryPick">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.CherryPick}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.CherryPick.Commit}"/>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Target.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" FontFamily="{StaticResource JetBrainsMono}" Foreground="DarkOrange" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Target.Subject}}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="1" Grid.Column="1"
|
||||
Content="{DynamicResource Text.CherryPick.CommitChanges}"
|
||||
IsChecked="{Binding AutoCommit, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/CherryPick.axaml.cs
Normal file
9
src/Views/CherryPick.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CherryPick : UserControl {
|
||||
public CherryPick() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
19
src/Views/Cleanup.axaml
Normal file
19
src/Views/Cleanup.axaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Cleanup"
|
||||
x:DataType="vm:Cleanup">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Repository.Clean}"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.CleanTips}"
|
||||
Margin="0,16,0,0"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/Cleanup.axaml.cs
Normal file
9
src/Views/Cleanup.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class Cleanup : UserControl {
|
||||
public Cleanup() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
18
src/Views/ClearStashes.axaml
Normal file
18
src/Views/ClearStashes.axaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<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"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.ClearStashes"
|
||||
x:DataType="vm:ClearStashes">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.ClearStashes}"/>
|
||||
|
||||
<TextBlock Margin="0,16,0,8"
|
||||
Text="{DynamicResource Text.ClearStashes.Message}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/ClearStashes.axaml.cs
Normal file
9
src/Views/ClearStashes.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class ClearStashes : UserControl {
|
||||
public ClearStashes() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
54
src/Views/Clone.axaml
Normal file
54
src/Views/Clone.axaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Clone"
|
||||
x:DataType="vm:Clone">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock Classes="bold" FontSize="18" Text="{DynamicResource Text.Clone}"/>
|
||||
|
||||
<Grid Margin="8,16,0,0" Height="28" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.RemoteURL}"/>
|
||||
<TextBox Grid.Column="1" CornerRadius="3" Text="{Binding Remote, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="120,*" IsVisible="{Binding UseSSH}">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.SSHKey}"/>
|
||||
<Grid Grid.Column="1" ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtSSHKey"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Watermark="{DynamicResource Text.SSHKey.Placeholder}"
|
||||
Text="{Binding SSHKey, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="32" Height="32" Margin="4,0,0,0" Click="SelectSSHKey">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.ParentFolder}"/>
|
||||
<Grid Grid.Column="1" ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtParentFolder"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Text="{Binding ParentFolder, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="32" Height="32" Margin="4,0,0,0" Click="SelectParentFolder">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.LocalName}"/>
|
||||
<TextBox Grid.Column="1" CornerRadius="3" Watermark="{DynamicResource Text.Clone.LocalName.Placeholder}" Text="{Binding Local, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
<Grid Margin="8,4,0,0" Height="28" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.Clone.AdditionalParam}"/>
|
||||
<TextBox Grid.Column="1" CornerRadius="3" Watermark="{DynamicResource Text.Clone.AdditionalParam.Placeholder}" Text="{Binding ExtraArgs, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
33
src/Views/Clone.axaml.cs
Normal file
33
src/Views/Clone.axaml.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class Clone : UserControl {
|
||||
public Clone() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SelectParentFolder(object sender, RoutedEventArgs e) {
|
||||
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
|
||||
var toplevel = TopLevel.GetTopLevel(this);
|
||||
var selected = await toplevel.StorageProvider.OpenFolderPickerAsync(options);
|
||||
if (selected.Count == 1) {
|
||||
txtParentFolder.Text = selected[0].Path.LocalPath;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async void SelectSSHKey(object sender, RoutedEventArgs e) {
|
||||
var options = new FilePickerOpenOptions() { AllowMultiple = false, FileTypeFilter = [new FilePickerFileType("SSHKey") { Patterns = ["*.*"] }] };
|
||||
var toplevel = TopLevel.GetTopLevel(this);
|
||||
var selected = await toplevel.StorageProvider.OpenFilePickerAsync(options);
|
||||
if (selected.Count == 1) {
|
||||
txtSSHKey.Text = selected[0].Path.LocalPath;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.Clone"
|
||||
x:Name="me"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="{DynamicResource Text.Clone}"
|
||||
Width="500" SizeToContent="Height"
|
||||
ResizeMode="NoResize">
|
||||
|
||||
<Window.TaskbarItemInfo>
|
||||
<TaskbarItemInfo ProgressState="None"/>
|
||||
</Window.TaskbarItemInfo>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Pull}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.Clone}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="OnQuit"
|
||||
Width="28"
|
||||
IconSize="10"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<!-- Body -->
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition x:Name="rowSSHKey" Height="0"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="48"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Clone.RemoteURL}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"/>
|
||||
<controls:TextEdit
|
||||
Grid.Row="0" Grid.Column="1"
|
||||
x:Name="txtUrl"
|
||||
Height="24"
|
||||
Placeholder="{DynamicResource Text.Clone.RemoteURL.Placeholder}"
|
||||
TextChanged="OnUrlChanged">
|
||||
<controls:TextEdit.Text>
|
||||
<Binding ElementName="me" Path="Uri" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<validations:GitURL/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</controls:TextEdit.Text>
|
||||
</controls:TextEdit>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1" Grid.Column="0"
|
||||
Text="{DynamicResource Text.SSHKey}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"/>
|
||||
<Grid Grid.Row="1" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:TextEdit
|
||||
Grid.Column="0"
|
||||
x:Name="txtSSHKey"
|
||||
Height="24"
|
||||
Placeholder="{DynamicResource Text.SSHKey.Placeholder}"/>
|
||||
|
||||
<controls:IconButton
|
||||
Grid.Column="1"
|
||||
Click="OnSelectSSHKey"
|
||||
Width="24" Height="24"
|
||||
Margin="2,0,0,0" IconSize="16"
|
||||
BorderBrush="{DynamicResource Brush.Border1}"
|
||||
BorderThickness="1"
|
||||
Icon="{StaticResource Icon.Folder.Open}"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Clone.Folder}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"/>
|
||||
<Grid Grid.Row="2" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:TextEdit
|
||||
Grid.Column="0"
|
||||
x:Name="txtFolder"
|
||||
Height="24"
|
||||
Placeholder="{DynamicResource Text.Clone.Folder.Placeholder}">
|
||||
<controls:TextEdit.Text>
|
||||
<Binding ElementName="me" Path="Folder" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<validations:CloneDir/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</controls:TextEdit.Text>
|
||||
</controls:TextEdit>
|
||||
|
||||
<controls:IconButton
|
||||
Grid.Column="1"
|
||||
Click="OnFolderSelectorClick"
|
||||
Width="24" Height="24"
|
||||
Margin="2,0,0,0" IconSize="16"
|
||||
BorderBrush="{DynamicResource Brush.Border1}"
|
||||
BorderThickness="1"
|
||||
Icon="{StaticResource Icon.Folder.Open}"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Clone.LocalName}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"/>
|
||||
<controls:TextEdit
|
||||
Grid.Row="3" Grid.Column="1"
|
||||
Height="24"
|
||||
x:Name="txtLocal"
|
||||
Placeholder="{DynamicResource Text.Clone.LocalName.Placeholder}">
|
||||
<controls:TextEdit.Text>
|
||||
<Binding ElementName="me" Path="LocalName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<validations:LocalRepositoryName/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</controls:TextEdit.Text>
|
||||
</controls:TextEdit>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Clone.RemoteName}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"/>
|
||||
<controls:TextEdit
|
||||
Grid.Row="4" Grid.Column="1"
|
||||
x:Name="txtRemote"
|
||||
Height="24"
|
||||
Placeholder="{DynamicResource Text.Clone.RemoteName.Placeholder}">
|
||||
<controls:TextEdit.Text>
|
||||
<Binding ElementName="me" Path="RemoteName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<validations:RemoteName x:Name="ruleRemote"/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</controls:TextEdit.Text>
|
||||
</controls:TextEdit>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Clone.AdditionalParam}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"/>
|
||||
<controls:TextEdit
|
||||
Grid.Row="5" Grid.Column="1"
|
||||
Height="24"
|
||||
Placeholder="{DynamicResource Text.Clone.AdditionalParam.Placeholder}"
|
||||
Text="{Binding ElementName=me, Path=ExtraArgs, Mode=TwoWay}"/>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
Height="32"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Click="OnStart" Width="80" Content="{DynamicResource Text.Start}" BorderBrush="{DynamicResource Brush.FG1}" Background="{DynamicResource Brush.Accent1}" FontWeight="Bold"/>
|
||||
<Button Click="OnQuit" Width="80" Margin="8,0,0,0" Content="{DynamicResource Text.Cancel}" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Progress -->
|
||||
<Border x:Name="progress" Visibility="Collapsed" Background="{DynamicResource Brush.Popup}" Opacity=".9">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<controls:Loading x:Name="processing" Width="48" Height="48"/>
|
||||
<TextBlock x:Name="txtProgress" Margin="0,16,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Exception -->
|
||||
<Grid x:Name="exception" Margin="8" Visibility="Collapsed" Background="{DynamicResource Brush.Window}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="26"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="48"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Text="{DynamicResource Text.Launcher.Error}"
|
||||
FontWeight="Bold"/>
|
||||
<controls:TextEdit
|
||||
Grid.Row="1"
|
||||
x:Name="txtError"
|
||||
IsReadOnly="true"
|
||||
BorderThickness="0"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
MaxHeight="80"
|
||||
Margin="0,8"
|
||||
VerticalAlignment="Top"/>
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Height="26"
|
||||
Margin="4,0" Padding="8,0"
|
||||
Click="OnCloseException"
|
||||
Content="{DynamicResource Text.Sure}"
|
||||
Background="{DynamicResource Brush.Accent1}"
|
||||
BorderBrush="{DynamicResource Brush.FG1}"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</controls:Window>
|
|
@ -1,131 +0,0 @@
|
|||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Shell;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
||||
/// <summary>
|
||||
/// 克隆
|
||||
/// </summary>
|
||||
public partial class Clone : Controls.Window {
|
||||
public string Uri { get; set; }
|
||||
public string Folder { get; set; }
|
||||
public string LocalName { get; set; }
|
||||
public string RemoteName { get; set; }
|
||||
public string ExtraArgs { get; set; }
|
||||
|
||||
public Clone() {
|
||||
Folder = Models.Preference.Instance.Git.DefaultCloneDir;
|
||||
InitializeComponent();
|
||||
ruleRemote.IsOptional = true;
|
||||
}
|
||||
|
||||
#region EVENTS
|
||||
private void OnQuit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
private async void OnStart(object s, RoutedEventArgs e) {
|
||||
var checks = new Controls.TextEdit[] { txtUrl, txtFolder, txtLocal, txtRemote };
|
||||
foreach (var edit in checks) {
|
||||
edit.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(edit)) return;
|
||||
}
|
||||
|
||||
TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Indeterminate;
|
||||
progress.Visibility = Visibility.Visible;
|
||||
processing.IsAnimating = true;
|
||||
|
||||
var sshKey = txtSSHKey.Text;
|
||||
var succ = await Task.Run(() => {
|
||||
var extras = string.IsNullOrEmpty(ExtraArgs) ? "" : ExtraArgs;
|
||||
if (!string.IsNullOrEmpty(RemoteName)) extras += $" --origin {RemoteName}";
|
||||
|
||||
var cloneRs = new Commands.Clone(Folder, Uri, LocalName, sshKey, extras, msg => {
|
||||
Dispatcher.Invoke(() => txtProgress.Text = msg);
|
||||
}, err => {
|
||||
Dispatcher.Invoke(() => txtError.Text = err);
|
||||
}).Exec();
|
||||
|
||||
if (!cloneRs) return false;
|
||||
|
||||
var path = Folder;
|
||||
if (!string.IsNullOrEmpty(LocalName)) {
|
||||
path = Path.GetFullPath(Path.Combine(path, LocalName));
|
||||
} else {
|
||||
var name = Path.GetFileName(Uri);
|
||||
if (name.EndsWith(".git")) name = name.Substring(0, name.Length - 4);
|
||||
path = Path.GetFullPath(Path.Combine(path, name));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(path)) {
|
||||
Dispatcher.Invoke(() => txtError.Text = $"Folder {path} not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
var config = new Commands.Config(path);
|
||||
var remote = "origin";
|
||||
if (!string.IsNullOrEmpty(RemoteName)) remote = RemoteName;
|
||||
config.Set($"remote.{remote}.sshkey", sshKey);
|
||||
}
|
||||
|
||||
var gitDir = new Commands.QueryGitDir(path).Result();
|
||||
var repo = Models.Preference.Instance.AddRepository(path, gitDir);
|
||||
if (repo != null) Dispatcher.Invoke(() => Models.Watcher.Open(repo));
|
||||
return true;
|
||||
});
|
||||
|
||||
TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
|
||||
progress.Visibility = Visibility.Collapsed;
|
||||
processing.IsAnimating = false;
|
||||
if (succ) {
|
||||
Close();
|
||||
} else {
|
||||
exception.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFolderSelectorClick(object sender, RoutedEventArgs e) {
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.ShowNewFolderButton = true;
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
||||
Folder = dialog.SelectedPath;
|
||||
txtFolder.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectSSHKey(object sender, RoutedEventArgs e) {
|
||||
var initPath = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "..", ".ssh"));
|
||||
if (!Directory.Exists(initPath)) Directory.CreateDirectory(initPath);
|
||||
|
||||
var dialog = new OpenFileDialog();
|
||||
dialog.Filter = $"SSH Private Key|*";
|
||||
dialog.Title = App.Text("SSHKey");
|
||||
dialog.InitialDirectory = initPath;
|
||||
dialog.CheckFileExists = true;
|
||||
dialog.Multiselect = false;
|
||||
|
||||
if (dialog.ShowDialog() == true) txtSSHKey.Text = dialog.FileName;
|
||||
}
|
||||
|
||||
private void OnUrlChanged(object sender, TextChangedEventArgs e) {
|
||||
var isSSHProtocal = Validations.GitURL.IsSSH(txtUrl.Text);
|
||||
if (isSSHProtocal) {
|
||||
rowSSHKey.Height = new GridLength(32, GridUnitType.Pixel);
|
||||
} else {
|
||||
rowSSHKey.Height = new GridLength(0, GridUnitType.Pixel);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseException(object s, RoutedEventArgs e) {
|
||||
exception.Visibility = Visibility.Collapsed;
|
||||
e.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
115
src/Views/CommitBaseInfo.axaml
Normal file
115
src/Views/CommitBaseInfo.axaml
Normal file
|
@ -0,0 +1,115 @@
|
|||
<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: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.CommitBaseInfo">
|
||||
<UserControl.DataTemplates>
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!-- Author & Committer -->
|
||||
<Grid ColumnDefinitions="96,*,96,*" Margin="0,8">
|
||||
<!-- Author -->
|
||||
<v:Avatar Grid.Column="0"
|
||||
Width="64" Height="64"
|
||||
HorizontalAlignment="Right"
|
||||
FallbackFontFamily="{StaticResource JetBrainsMono}"
|
||||
User="{Binding Author}"/>
|
||||
<StackPanel Grid.Column="1" Margin="16,0,8,0" Orientation="Vertical">
|
||||
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.CommitDetail.Info.Author}"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,8">
|
||||
<SelectableTextBlock Text="{Binding Author.Name}" FontSize="12" Margin="2,0,8,0"/>
|
||||
<SelectableTextBlock Text="{Binding Author.Email}" FontSize="12" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
<SelectableTextBlock Text="{Binding AuthorTimeStr}" Margin="2,0,0,0" FontSize="11" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Committer -->
|
||||
<v:Avatar Grid.Column="2"
|
||||
Width="64" Height="64"
|
||||
HorizontalAlignment="Right"
|
||||
FallbackFontFamily="{StaticResource JetBrainsMono}"
|
||||
User="{Binding Committer}"
|
||||
IsVisible="{Binding IsCommitterVisible}"/>
|
||||
<StackPanel Grid.Column="3" Margin="16,0,8,0" Orientation="Vertical" IsVisible="{Binding IsCommitterVisible}">
|
||||
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.CommitDetail.Info.Committer}"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,8">
|
||||
<SelectableTextBlock Text="{Binding Committer.Name}" FontSize="12" Margin="2,0,8,0"/>
|
||||
<SelectableTextBlock Text="{Binding Committer.Email}" FontSize="12" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
<SelectableTextBlock Text="{Binding CommitterTimeStr}" Margin="2,0,0,0" FontSize="11" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Line -->
|
||||
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}" VerticalAlignment="Center"/>
|
||||
|
||||
<!-- Base Information -->
|
||||
<Grid RowDefinitions="24,Auto,Auto,Auto" ColumnDefinitions="96,*">
|
||||
<!-- SHA -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.SHA}" />
|
||||
<SelectableTextBlock Grid.Row="0" Grid.Column="1" Text="{Binding SHA}" Margin="12,0,0,0" FontSize="12" VerticalAlignment="Center"/>
|
||||
|
||||
<!-- PARENTS -->
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
|
||||
<ItemsControl Grid.Row="1" Grid.Column="1" Height="24" Margin="12,0,0,0" ItemsSource="{Binding Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
FontSize="12"
|
||||
Foreground="DarkOrange"
|
||||
TextDecorations="Underline"
|
||||
Margin="0,0,16,0"
|
||||
PointerPressed="OnParentSHAPressed"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- REFS -->
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Refs}" IsVisible="{Binding Decorators.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
|
||||
<ItemsControl Grid.Row="2" Grid.Column="1" Height="24" Margin="12,0,0,0" ItemsSource="{Binding Decorators}" IsVisible="{Binding Decorators.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type m:Decorator}">
|
||||
<Border Height="16" Margin="0,0,6,0" CornerRadius="2" ClipToBounds="True">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Background="{DynamicResource Brush.Decorator}" Width="16">
|
||||
<Path Width="8" Height="8" Stretch="Fill" Data="{Binding Type, Converter={x:Static c:DecoratorTypeConverters.ToIcon}}" Fill="{DynamicResource Brush.DecoratorIcon}"/>
|
||||
</Border>
|
||||
<Border Background="{Binding Type, Converter={x:Static c:DecoratorTypeConverters.ToBackground}}">
|
||||
<TextBlock Text="{Binding Name}" FontSize="10" Margin="4,0" Foreground="Black"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Messages -->
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
|
||||
<ScrollViewer Grid.Row="3" Grid.Column="1" Margin="12,4,0,0" MaxHeight="100" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<SelectableTextBlock Text="{Binding FullMessage}" FontSize="12" TextWrapping="Wrap"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!-- Line -->
|
||||
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</UserControl.DataTemplates>
|
||||
</UserControl>
|
17
src/Views/CommitBaseInfo.axaml.cs
Normal file
17
src/Views/CommitBaseInfo.axaml.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CommitBaseInfo : UserControl {
|
||||
public CommitBaseInfo() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnParentSHAPressed(object sender, PointerPressedEventArgs e) {
|
||||
if (DataContext is ViewModels.CommitDetail detail) {
|
||||
detail.NavigateTo((sender as Control).DataContext as string);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
183
src/Views/CommitChanges.axaml
Normal file
183
src/Views/CommitChanges.axaml
Normal file
|
@ -0,0 +1,183 @@
|
|||
<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"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CommitChanges"
|
||||
x:DataType="vm:CommitDetail">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="256" MinWidth="200" MaxWidth="480"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0" RowDefinitions="26,*">
|
||||
<!-- Search & Display Mode -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="24,*,24,24">
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Background="Transparent"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"/>
|
||||
<Path Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Fill="{DynamicResource Brush.FG2}"
|
||||
Data="{StaticResource Icons.Search}"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Classes="no_background no_border"
|
||||
Height="24"
|
||||
FontSize="12"
|
||||
CornerRadius="0"
|
||||
Watermark="{DynamicResource Text.CommitDetail.Changes.Search}"
|
||||
Text="{Binding SearchChangeFilter, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
IsVisible="{Binding SearchChangeFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding ClearSearchChangeFilter}">
|
||||
<Path Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Clear}"/>
|
||||
</Button>
|
||||
|
||||
<v:ChangeViewModeSwitcher Grid.Column="3"
|
||||
Width="18" Height="18"
|
||||
HorizontalAlignment="Right"
|
||||
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Changes -->
|
||||
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
|
||||
<Grid>
|
||||
<DataGrid Background="Transparent"
|
||||
ItemsSource="{Binding VisibleChanges}"
|
||||
SelectedItem="{Binding SelectedChange, Mode=TwoWay}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
SelectionChanged="OnDataGridSelectionChanged"
|
||||
ContextRequested="OnDataGridContextRequested"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}" IconFontFamily="{StaticResource JetBrainsMono}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="PATH">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path}" Margin="4,0,0,0" FontSize="12"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<DataGrid Background="Transparent"
|
||||
ItemsSource="{Binding VisibleChanges}"
|
||||
SelectedItem="{Binding SelectedChange, Mode=TwoWay}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
SelectionChanged="OnDataGridSelectionChanged"
|
||||
ContextRequested="OnDataGridContextRequested"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsGrid}}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}" IconFontFamily="{StaticResource JetBrainsMono}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="FILE_NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}" Margin="4,0,0,0" FontSize="12" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="FOLDER_PATH">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}" Margin="4,0,0,0" FontSize="12" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<TreeView ItemsSource="{Binding ChangeTree}"
|
||||
SelectedItem="{Binding SelectedChangeNode, Mode=TwoWay}"
|
||||
AutoScrollToSelectedItem="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnTreeViewContextRequested"
|
||||
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsTree}}">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:FileTreeNode">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:FileTreeNode}">
|
||||
<Grid Height="24" ColumnDefinitions="Auto,*">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
|
||||
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding Backend}" IconFontFamily="{StaticResource JetBrainsMono}" IsVisible="{Binding !IsFolder}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" FontSize="12" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Column="1"
|
||||
Width="1" MinWidth=".5"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Stretch"
|
||||
Background="Transparent"/>
|
||||
|
||||
<Grid Grid.Column="2" Margin="4,0,0,0">
|
||||
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Diff}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Margin="0,16,0,0"
|
||||
Text="{DynamicResource Text.Diff.Welcome}"
|
||||
FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ContentControl Content="{Binding DiffContext}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:DiffContext">
|
||||
<v:DiffView/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
39
src/Views/CommitChanges.axaml.cs
Normal file
39
src/Views/CommitChanges.axaml.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CommitChanges : UserControl {
|
||||
public CommitChanges() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
if (sender is DataGrid datagrid && datagrid.IsVisible && datagrid.SelectedItem != null) {
|
||||
datagrid.ScrollIntoView(datagrid.SelectedItem, null);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDataGridContextRequested(object sender, ContextRequestedEventArgs e) {
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null) {
|
||||
var detail = DataContext as ViewModels.CommitDetail;
|
||||
var menu = detail.CreateChangeContextMenu(datagrid.SelectedItem as Models.Change);
|
||||
menu.Open(datagrid);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnTreeViewContextRequested(object sender, ContextRequestedEventArgs e) {
|
||||
if (sender is TreeView view && view.SelectedItem != null) {
|
||||
var detail = DataContext as ViewModels.CommitDetail;
|
||||
var node = view.SelectedItem as ViewModels.FileTreeNode;
|
||||
if (node != null && !node.IsFolder) {
|
||||
var menu = detail.CreateChangeContextMenu(node.Backend as Models.Change);
|
||||
menu.Open(view);
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
76
src/Views/CommitDetail.axaml
Normal file
76
src/Views/CommitDetail.axaml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<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: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.CommitDetail"
|
||||
x:DataType="vm:CommitDetail"
|
||||
Background="{DynamicResource Brush.Window}">
|
||||
<TabControl SelectedIndex="{Binding ActivePageIndex, Mode=TwoWay}" Padding="4">
|
||||
<!-- Information Page -->
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.CommitDetail.Info}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<!-- Base Information -->
|
||||
<v:CommitBaseInfo Grid.Row="0" Content="{Binding Commit}"/>
|
||||
|
||||
<!-- Change List -->
|
||||
<DataGrid Grid.Row="1"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Changes}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
Margin="80,0,8,0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnChangeListContextRequested"
|
||||
DoubleTapped="OnChangeListDoubleTapped">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<v:ChangeStatusIcon Width="14" Height="14" IsWorkingCopyChange="False" Change="{Binding}" IconFontFamily="{StaticResource JetBrainsMono}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="PATH">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path}" Margin="8,0,0,0" FontSize="12"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.CommitDetail.Changes}"/>
|
||||
</TabItem.Header>
|
||||
<v:CommitChanges/>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.CommitDetail.Files}"/>
|
||||
</TabItem.Header>
|
||||
<v:RevisionFiles/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
28
src/Views/CommitDetail.axaml.cs
Normal file
28
src/Views/CommitDetail.axaml.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CommitDetail : UserControl {
|
||||
public CommitDetail() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnChangeListDoubleTapped(object sender, TappedEventArgs e) {
|
||||
if (DataContext is ViewModels.CommitDetail detail) {
|
||||
var datagrid = sender as DataGrid;
|
||||
detail.ActivePageIndex = 1;
|
||||
detail.SelectedChange = datagrid.SelectedItem as Models.Change;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnChangeListContextRequested(object sender, ContextRequestedEventArgs e) {
|
||||
if (DataContext is ViewModels.CommitDetail detail) {
|
||||
var datagrid = sender as DataGrid;
|
||||
var menu = detail.CreateChangeContextMenu(datagrid.SelectedItem as Models.Change);
|
||||
menu.Open(datagrid);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.ConfirmDialog"
|
||||
x:Name="me"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="500" SizeToContent="Height"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Help}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" x:Name="txtTitle" Text=""/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="OnQuit"
|
||||
Width="28"
|
||||
IconSize="10"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<!-- Body -->
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="48"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0" Grid.Column="0"
|
||||
x:Name="txtMessage"
|
||||
Margin="8, 16"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
Height="32"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Button Click="OnSure" Width="80" Content="{DynamicResource Text.Sure}" FontWeight="Bold"/>
|
||||
<Button x:Name="btnCancel" Click="OnQuit" Width="80" Margin="8,0,0,0" Content="{DynamicResource Text.Cancel}" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</controls:Window>
|
|
@ -1,49 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
||||
/// <summary>
|
||||
/// 通用的确认弹出框
|
||||
/// </summary>
|
||||
public partial class ConfirmDialog : Controls.Window {
|
||||
private Action cbOK;
|
||||
private Action cbCancel;
|
||||
|
||||
public ConfirmDialog(string title, string message) {
|
||||
Owner = App.Current.MainWindow;
|
||||
|
||||
cbOK = null;
|
||||
cbCancel = null;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
txtTitle.Text = title;
|
||||
txtMessage.Text = message;
|
||||
btnCancel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public ConfirmDialog(string title, string message, Action onOk, Action onCancel = null) {
|
||||
Owner = App.Current.MainWindow;
|
||||
|
||||
cbOK = onOk;
|
||||
cbCancel = onCancel;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
txtTitle.Text = title;
|
||||
txtMessage.Text = message;
|
||||
btnCancel.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void OnSure(object sender, RoutedEventArgs e) {
|
||||
cbOK?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnQuit(object sender, RoutedEventArgs e) {
|
||||
cbCancel?.Invoke();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 头像控件
|
||||
/// </summary>
|
||||
public class Avatar : Image {
|
||||
|
||||
/// <summary>
|
||||
/// 显示FallbackLabel时的背景色
|
||||
/// </summary>
|
||||
private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] {
|
||||
new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90),
|
||||
new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90),
|
||||
new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90),
|
||||
new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90),
|
||||
new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 头像资源本地缓存路径
|
||||
/// </summary>
|
||||
public static readonly string CACHE_PATH = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
"avatars");
|
||||
|
||||
/// <summary>
|
||||
/// 邮件属性定义
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
|
||||
"Email",
|
||||
typeof(string),
|
||||
typeof(Avatar),
|
||||
new PropertyMetadata(null, OnEmailChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 邮件属性
|
||||
/// </summary>
|
||||
public string Email {
|
||||
get { return (string)GetValue(EmailProperty); }
|
||||
set { SetValue(EmailProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载头像失败时显示的Label属性定义
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
|
||||
"FallbackLabel",
|
||||
typeof(string),
|
||||
typeof(Avatar),
|
||||
new PropertyMetadata("?", OnFallbackLabelChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 下载头像失败时显示的Label属性
|
||||
/// </summary>
|
||||
public string FallbackLabel {
|
||||
get { return (string)GetValue(FallbackLabelProperty); }
|
||||
set { SetValue(FallbackLabelProperty, value); }
|
||||
}
|
||||
|
||||
private static event Action<string> RefetchRequested;
|
||||
private static event Action<string> FetchCompleted;
|
||||
|
||||
private static Dictionary<string, BitmapImage> loaded = new Dictionary<string, BitmapImage>();
|
||||
private static Task loader = null;
|
||||
|
||||
private int colorIdx = 0;
|
||||
private FormattedText label = null;
|
||||
|
||||
public Avatar() {
|
||||
RefetchRequested += email => {
|
||||
if (email == Email) {
|
||||
Source = null;
|
||||
InvalidateVisual();
|
||||
}
|
||||
};
|
||||
|
||||
FetchCompleted += email => {
|
||||
if (email == Email) {
|
||||
Source = loaded[Email];
|
||||
InvalidateVisual();
|
||||
}
|
||||
};
|
||||
|
||||
SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality);
|
||||
SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto);
|
||||
|
||||
var refetch = new MenuItem();
|
||||
refetch.Header = App.Text("Dashboard.Refresh");
|
||||
refetch.Click += (o, e) => Refetch();
|
||||
|
||||
ContextMenu = new ContextMenu();
|
||||
ContextMenu.Items.Add(refetch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动刷新
|
||||
/// </summary>
|
||||
private void Refetch() {
|
||||
byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(Email.ToLower().Trim()));
|
||||
string md5 = "";
|
||||
for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2");
|
||||
md5 = md5.ToLower();
|
||||
string filePath = Path.Combine(CACHE_PATH, md5);
|
||||
if (File.Exists(filePath)) File.Delete(filePath);
|
||||
|
||||
RefetchRequested?.Invoke(Email);
|
||||
if (loaded.ContainsKey(Email)) loaded.Remove(Email);
|
||||
OnEmailChanged(this, new DependencyPropertyChangedEventArgs(EmailProperty, null, Email));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染实现
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
var corner = Math.Max(2, Width / 16);
|
||||
|
||||
if (Source == null && label != null) {
|
||||
var offsetX = (double)0;
|
||||
if (HorizontalAlignment == HorizontalAlignment.Right) {
|
||||
offsetX = -Width * 0.5;
|
||||
} else if (HorizontalAlignment == HorizontalAlignment.Left) {
|
||||
offsetX = Width * 0.5;
|
||||
}
|
||||
|
||||
Brush brush = BACKGROUND_BRUSHES[colorIdx];
|
||||
dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner);
|
||||
dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5));
|
||||
} else {
|
||||
dc.PushClip(new RectangleGeometry(new Rect(0, 0, Width, Height), corner, corner));
|
||||
base.OnRender(dc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示文本变化时触发
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Avatar a = d as Avatar;
|
||||
if (a == null) return;
|
||||
|
||||
var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?";
|
||||
|
||||
a.colorIdx = 0;
|
||||
a.label = new FormattedText(
|
||||
placeholder,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
a.Width * 0.65,
|
||||
Brushes.White,
|
||||
VisualTreeHelper.GetDpi(a).PixelsPerDip);
|
||||
|
||||
var chars = placeholder.ToCharArray();
|
||||
foreach (var ch in chars) a.colorIdx += Math.Abs(ch);
|
||||
a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length;
|
||||
if (a.Source == null) a.InvalidateVisual();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 邮件变化时触发
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Avatar a = d as Avatar;
|
||||
if (a == null) return;
|
||||
|
||||
if (a.Source != null) {
|
||||
a.Source = null;
|
||||
a.InvalidateVisual();
|
||||
}
|
||||
|
||||
var email = e.NewValue as string;
|
||||
if (string.IsNullOrEmpty(email)) return;
|
||||
|
||||
if (loaded.ContainsKey(email)) {
|
||||
a.Source = loaded[email];
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim()));
|
||||
string md5 = "";
|
||||
for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2");
|
||||
md5 = md5.ToLower();
|
||||
|
||||
string filePath = Path.Combine(CACHE_PATH, md5);
|
||||
if (File.Exists(filePath)) {
|
||||
var img = LoadFromFile(filePath);
|
||||
loaded.Add(email, img);
|
||||
a.Source = img;
|
||||
return;
|
||||
}
|
||||
|
||||
loaded.Add(email, null);
|
||||
|
||||
Action job = () => {
|
||||
try {
|
||||
var req = WebRequest.CreateHttp($"https://cravatar.cn/avatar/{md5}?d=404");
|
||||
req.Timeout = 2000;
|
||||
req.Method = "GET";
|
||||
|
||||
var rsp = req.GetResponse() as HttpWebResponse;
|
||||
if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) {
|
||||
using (var reader = rsp.GetResponseStream()) {
|
||||
using (var writer = File.OpenWrite(filePath)) {
|
||||
reader.CopyTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
a.Dispatcher.Invoke(() => {
|
||||
loaded[email] = LoadFromFile(filePath);
|
||||
FetchCompleted?.Invoke(email);
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
if (loader != null && !loader.IsCompleted) {
|
||||
loader = loader.ContinueWith(t => { job(); });
|
||||
} else {
|
||||
loader = Task.Run(job);
|
||||
}
|
||||
}
|
||||
|
||||
private static BitmapImage LoadFromFile(string file) {
|
||||
var img = new BitmapImage();
|
||||
img.BeginInit();
|
||||
img.StreamSource = new MemoryStream(File.ReadAllBytes(file));
|
||||
img.EndInit();
|
||||
return img;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 徽章
|
||||
/// </summary>
|
||||
public class Badge : Border {
|
||||
private TextBlock label = null;
|
||||
|
||||
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
|
||||
"Label",
|
||||
typeof(string),
|
||||
typeof(Border),
|
||||
new PropertyMetadata("", OnLabelChanged));
|
||||
|
||||
public string Label {
|
||||
get { return (string)GetValue(LabelProperty); }
|
||||
set { SetValue(LabelProperty, value); }
|
||||
}
|
||||
|
||||
public Badge() {
|
||||
Width = double.NaN;
|
||||
Height = 18;
|
||||
CornerRadius = new CornerRadius(9);
|
||||
VerticalAlignment = VerticalAlignment.Center;
|
||||
Visibility = Visibility.Collapsed;
|
||||
|
||||
SetResourceReference(BackgroundProperty, "Brush.Badge");
|
||||
|
||||
label = new TextBlock();
|
||||
label.FontSize = 10;
|
||||
label.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
label.Margin = new Thickness(9, 0, 9, 0);
|
||||
Child = label;
|
||||
}
|
||||
|
||||
private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Badge badge = d as Badge;
|
||||
if (badge != null) {
|
||||
var text = e.NewValue as string;
|
||||
if (string.IsNullOrEmpty(text) || text == "0") {
|
||||
badge.Visibility = Visibility.Collapsed;
|
||||
} else {
|
||||
badge.label.Text = text;
|
||||
badge.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
/// <summary>
|
||||
/// 用于切换变更显示模式的按钮
|
||||
/// </summary>
|
||||
public class ChangeDisplaySwitcher : Button {
|
||||
|
||||
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
|
||||
"Mode",
|
||||
typeof(Models.Change.DisplayMode),
|
||||
typeof(ChangeDisplaySwitcher),
|
||||
new PropertyMetadata(Models.Change.DisplayMode.Tree, OnModeChanged));
|
||||
|
||||
public Models.Change.DisplayMode Mode {
|
||||
get { return (Models.Change.DisplayMode)GetValue(ModeProperty); }
|
||||
set { SetValue(ModeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent ModeChangedEvent = EventManager.RegisterRoutedEvent(
|
||||
"ModeChanged",
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler),
|
||||
typeof(ChangeDisplaySwitcher));
|
||||
|
||||
public event RoutedEventHandler ModeChanged {
|
||||
add { AddHandler(ModeChangedEvent, value); }
|
||||
remove { RemoveHandler(ModeChangedEvent, value); }
|
||||
}
|
||||
|
||||
private Path icon = null;
|
||||
|
||||
public ChangeDisplaySwitcher() {
|
||||
icon = new Path();
|
||||
icon.Data = FindResource("Icon.Tree") as Geometry;
|
||||
icon.SetResourceReference(Path.FillProperty, "Brush.FG2");
|
||||
|
||||
Content = icon;
|
||||
Style = FindResource("Style.Button") as Style;
|
||||
BorderThickness = new Thickness(0);
|
||||
ToolTip = App.Text("ChangeDisplayMode");
|
||||
|
||||
Click += OnClicked;
|
||||
}
|
||||
|
||||
private void OnClicked(object sender, RoutedEventArgs e) {
|
||||
if (ContextMenu != null) {
|
||||
ContextMenu.IsOpen = true;
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Placement = PlacementMode.Bottom;
|
||||
menu.PlacementTarget = this;
|
||||
menu.StaysOpen = false;
|
||||
menu.Focusable = true;
|
||||
|
||||
FillMenu(menu, "ChangeDisplayMode.Tree", "Icon.Tree", Models.Change.DisplayMode.Tree);
|
||||
FillMenu(menu, "ChangeDisplayMode.List", "Icon.List", Models.Change.DisplayMode.List);
|
||||
FillMenu(menu, "ChangeDisplayMode.Grid", "Icon.Grid", Models.Change.DisplayMode.Grid);
|
||||
|
||||
ContextMenu = menu;
|
||||
ContextMenu.IsOpen = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void FillMenu(ContextMenu menu, string header, string icon, Models.Change.DisplayMode useMode) {
|
||||
var iconMode = new Path();
|
||||
iconMode.Width = 12;
|
||||
iconMode.Height = 12;
|
||||
iconMode.Data = FindResource(icon) as Geometry;
|
||||
iconMode.SetResourceReference(Path.FillProperty, "Brush.FG2");
|
||||
|
||||
var item = new MenuItem();
|
||||
item.Icon = iconMode;
|
||||
item.Header = App.Text(header);
|
||||
item.Click += (o, e) => Mode = useMode;
|
||||
|
||||
menu.Items.Add(item);
|
||||
}
|
||||
|
||||
private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var elem = d as ChangeDisplaySwitcher;
|
||||
if (elem != null) {
|
||||
switch (elem.Mode) {
|
||||
case Models.Change.DisplayMode.Tree:
|
||||
elem.icon.Data = elem.FindResource("Icon.Tree") as Geometry;
|
||||
break;
|
||||
case Models.Change.DisplayMode.List:
|
||||
elem.icon.Data = elem.FindResource("Icon.List") as Geometry;
|
||||
break;
|
||||
case Models.Change.DisplayMode.Grid:
|
||||
elem.icon.Data = elem.FindResource("Icon.Grid") as Geometry;
|
||||
break;
|
||||
}
|
||||
elem.RaiseEvent(new RoutedEventArgs(ModeChangedEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
/// <summary>
|
||||
/// 变更状态图标
|
||||
/// </summary>
|
||||
class ChangeStatusIcon : FrameworkElement {
|
||||
public static readonly Brush[] Backgrounds = new Brush[] {
|
||||
Brushes.Transparent,
|
||||
new LinearGradientBrush(Color.FromRgb(238, 160, 14), Color.FromRgb(228, 172, 67), 90),
|
||||
new LinearGradientBrush(Color.FromRgb(47, 185, 47), Color.FromRgb(75, 189, 75), 90),
|
||||
new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90),
|
||||
new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90),
|
||||
new LinearGradientBrush(Color.FromRgb(238, 160, 14), Color.FromRgb(228, 172, 67), 90),
|
||||
new LinearGradientBrush(Color.FromRgb(238, 160, 14), Color.FromRgb(228, 172, 67), 90),
|
||||
new LinearGradientBrush(Color.FromRgb(47, 185, 47), Color.FromRgb(75, 189, 75), 90),
|
||||
};
|
||||
|
||||
public static readonly string[] Labels = new string[] {
|
||||
"?",
|
||||
"±",
|
||||
"+",
|
||||
"−",
|
||||
"➜",
|
||||
"❏",
|
||||
"U",
|
||||
"★",
|
||||
};
|
||||
|
||||
public static readonly DependencyProperty ChangeProperty = DependencyProperty.Register(
|
||||
"Change",
|
||||
typeof(Models.Change),
|
||||
typeof(ChangeStatusIcon),
|
||||
new PropertyMetadata(null, ForceDirty));
|
||||
|
||||
public Models.Change Change {
|
||||
get { return (Models.Change)GetValue(ChangeProperty); }
|
||||
set { SetValue(ChangeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsLocalChangeProperty = DependencyProperty.Register(
|
||||
"IsLocalChange",
|
||||
typeof(bool),
|
||||
typeof(ChangeStatusIcon),
|
||||
new PropertyMetadata(false, ForceDirty));
|
||||
|
||||
public bool IsLocalChange {
|
||||
get { return (bool)GetValue(IsLocalChangeProperty); }
|
||||
set { SetValue(IsLocalChangeProperty, value); }
|
||||
}
|
||||
|
||||
private Brush background;
|
||||
private FormattedText label;
|
||||
|
||||
public ChangeStatusIcon() {
|
||||
HorizontalAlignment = HorizontalAlignment.Center;
|
||||
VerticalAlignment = VerticalAlignment.Center;
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
if (background == null || label == null) return;
|
||||
var corner = Math.Max(2, Width / 16);
|
||||
dc.DrawRoundedRectangle(background, null, new Rect(0, 0, Width, Height), corner, corner);
|
||||
dc.DrawText(label, new Point((Width - label.Width) * 0.5, 0));
|
||||
}
|
||||
|
||||
private static void ForceDirty(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var icon = d as ChangeStatusIcon;
|
||||
if (icon == null) return;
|
||||
|
||||
if (icon.Change == null) {
|
||||
icon.background = null;
|
||||
icon.label = null;
|
||||
return;
|
||||
}
|
||||
|
||||
string txt;
|
||||
if (icon.IsLocalChange) {
|
||||
if (icon.Change.IsConflit) {
|
||||
icon.background = Brushes.OrangeRed;
|
||||
txt = "!";
|
||||
} else {
|
||||
icon.background = Backgrounds[(int)icon.Change.WorkTree];
|
||||
txt = Labels[(int)icon.Change.WorkTree];
|
||||
}
|
||||
} else {
|
||||
icon.background = Backgrounds[(int)icon.Change.Index];
|
||||
txt = Labels[(int)icon.Change.Index];
|
||||
}
|
||||
|
||||
icon.label = new FormattedText(
|
||||
txt,
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
icon.Width * 0.8,
|
||||
new SolidColorBrush(Color.FromRgb(241, 241, 241)),
|
||||
VisualTreeHelper.GetDpi(icon).PixelsPerDip);
|
||||
|
||||
icon.InvalidateVisual();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
/// <summary>
|
||||
/// 绘制提交频率柱状图
|
||||
/// </summary>
|
||||
public class Chart : FrameworkElement {
|
||||
public static readonly double MAX_SHAPE_WIDTH = 24;
|
||||
|
||||
public static readonly DependencyProperty LineBrushProperty = DependencyProperty.Register(
|
||||
"LineBrush",
|
||||
typeof(Brush),
|
||||
typeof(Chart),
|
||||
new PropertyMetadata(Brushes.White));
|
||||
|
||||
public Brush LineBrush {
|
||||
get { return (Brush)GetValue(LineBrushProperty); }
|
||||
set { SetValue(LineBrushProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ChartBrushProperty = DependencyProperty.Register(
|
||||
"ChartBrush",
|
||||
typeof(Brush),
|
||||
typeof(Chart),
|
||||
new PropertyMetadata(Brushes.White));
|
||||
|
||||
public Brush ChartBrush {
|
||||
get { return (Brush)GetValue(ChartBrushProperty); }
|
||||
set { SetValue(ChartBrushProperty, value); }
|
||||
}
|
||||
|
||||
private List<Models.StatisticSample> samples = new List<Models.StatisticSample>();
|
||||
private List<Rect> hitboxes = new List<Rect>();
|
||||
private int maxV = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制数据
|
||||
/// </summary>
|
||||
/// <param name="samples">数据源</param>
|
||||
public void SetData(List<Models.StatisticSample> samples) {
|
||||
this.samples = samples;
|
||||
this.hitboxes.Clear();
|
||||
|
||||
maxV = 0;
|
||||
foreach (var s in samples) {
|
||||
if (maxV < s.Count) maxV = s.Count;
|
||||
}
|
||||
|
||||
if (maxV <= 5) {
|
||||
maxV = 5;
|
||||
} else if (maxV <= 10) {
|
||||
maxV = 10;
|
||||
} else if (maxV <= 50) {
|
||||
maxV = 50;
|
||||
} else if (maxV <= 100) {
|
||||
maxV = 100;
|
||||
} else if (maxV <= 200) {
|
||||
maxV = 200;
|
||||
} else if (maxV <= 500) {
|
||||
maxV = 500;
|
||||
} else {
|
||||
maxV = (int)Math.Ceiling(maxV / 500.0) * 500;
|
||||
}
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e) {
|
||||
base.OnMouseMove(e);
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
base.OnRender(dc);
|
||||
|
||||
var font = new FontFamily("Consolas");
|
||||
var culture = CultureInfo.CurrentCulture;
|
||||
var typeface = new Typeface(font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
||||
var ppi = VisualTreeHelper.GetDpi(this).PixelsPerDip;
|
||||
var pen = new Pen(LineBrush, 1);
|
||||
|
||||
// 坐标系绘制
|
||||
var maxLabel = new FormattedText($"{maxV}", culture, FlowDirection.LeftToRight, typeface, 12.0, LineBrush, ppi);
|
||||
var horizonStart = maxLabel.Width + 8;
|
||||
var labelHeight = 32;
|
||||
dc.DrawText(maxLabel, new Point(0, - maxLabel.Height * 0.5));
|
||||
dc.DrawLine(pen, new Point(horizonStart, 0), new Point(horizonStart, ActualHeight - labelHeight));
|
||||
dc.DrawLine(pen, new Point(horizonStart, ActualHeight - labelHeight), new Point(ActualWidth, ActualHeight - labelHeight));
|
||||
|
||||
if (samples.Count == 0) return;
|
||||
|
||||
// 绘制纵坐标数值参考线
|
||||
var stepX = (ActualWidth - horizonStart) / samples.Count;
|
||||
var stepV = (ActualHeight - labelHeight) / 5;
|
||||
var labelStepV = maxV / 5;
|
||||
var gridPen = new Pen(LineBrush, 1) { DashStyle = DashStyles.Dash };
|
||||
for (int i = 1; i < 5; i++) {
|
||||
var vLabel = new FormattedText(
|
||||
$"{maxV - i * labelStepV}",
|
||||
culture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
12.0,
|
||||
LineBrush,
|
||||
ppi);
|
||||
|
||||
var dashHeight = i * stepV;
|
||||
var vy = Math.Max(0, dashHeight - vLabel.Height * 0.5);
|
||||
dc.PushOpacity(.1);
|
||||
dc.DrawLine(gridPen, new Point(horizonStart + 1, dashHeight), new Point(ActualWidth, dashHeight));
|
||||
dc.Pop();
|
||||
dc.DrawText(vLabel, new Point(horizonStart - vLabel.Width - 8, vy));
|
||||
}
|
||||
|
||||
// 先计算一下当前每个样本的碰撞区域,用于当鼠标移动上去时显示数值
|
||||
if (hitboxes.Count == 0) {
|
||||
var shapeWidth = Math.Min(32, stepX - 4);
|
||||
for (int i = 0; i < samples.Count; i++) {
|
||||
var h = samples[i].Count * (ActualHeight - labelHeight) / maxV;
|
||||
var x = horizonStart + 1 + stepX * i + (stepX - shapeWidth) * 0.5;
|
||||
var y = ActualHeight - labelHeight - h;
|
||||
hitboxes.Add(new Rect(x, y, shapeWidth, h));
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制样本
|
||||
for (int i = 0; i < samples.Count; i++) {
|
||||
var hLabel = new FormattedText(
|
||||
samples[i].Name,
|
||||
culture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
10.0,
|
||||
LineBrush,
|
||||
ppi);
|
||||
var rect = hitboxes[i];
|
||||
var xLabel = rect.X - (hLabel.Width - rect.Width) * 0.5;
|
||||
var yLabel = ActualHeight - labelHeight + 4;
|
||||
|
||||
dc.DrawRectangle(ChartBrush, null, rect);
|
||||
|
||||
if (stepX < 32) {
|
||||
dc.PushTransform(new TranslateTransform(xLabel, yLabel));
|
||||
dc.PushTransform(new RotateTransform(45, hLabel.Width * 0.5, hLabel.Height * 0.5));
|
||||
dc.DrawText(hLabel, new Point(0, 0));
|
||||
dc.Pop();
|
||||
dc.Pop();
|
||||
} else {
|
||||
dc.DrawText(hLabel, new Point(xLabel, yLabel));
|
||||
}
|
||||
}
|
||||
|
||||
// 当鼠标移动上去时显示数值
|
||||
var mouse = Mouse.GetPosition(this);
|
||||
for (int i = 0; i < samples.Count; i++) {
|
||||
var rect = hitboxes[i];
|
||||
if (rect.Contains(mouse)) {
|
||||
var tooltip = new FormattedText(
|
||||
$"{samples[i].Count}",
|
||||
culture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
12.0,
|
||||
FindResource("Brush.FG1") as Brush,
|
||||
ppi);
|
||||
|
||||
var tx = rect.X - (tooltip.Width - rect.Width) * 0.5;
|
||||
var ty = rect.Y - tooltip.Height - 4;
|
||||
dc.DrawText(tooltip, new Point(tx, ty));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 提交线路图
|
||||
/// </summary>
|
||||
public class CommitGraph : FrameworkElement {
|
||||
public static readonly Pen[] PENS = new Pen[] {
|
||||
new Pen(Brushes.Orange, 2),
|
||||
new Pen(Brushes.ForestGreen, 2),
|
||||
new Pen(Brushes.Gold, 2),
|
||||
new Pen(Brushes.Magenta, 2),
|
||||
new Pen(Brushes.Red, 2),
|
||||
new Pen(Brushes.Gray, 2),
|
||||
new Pen(Brushes.Turquoise, 2),
|
||||
new Pen(Brushes.Olive, 2),
|
||||
};
|
||||
|
||||
public static readonly double UNIT_WIDTH = 12;
|
||||
public static readonly double HALF_WIDTH = 6;
|
||||
public static readonly double UNIT_HEIGHT = 24;
|
||||
public static readonly double HALF_HEIGHT = 12;
|
||||
|
||||
public class Path {
|
||||
public List<Point> Points = new List<Point>();
|
||||
public int Color = 0;
|
||||
}
|
||||
|
||||
public class PathHelper {
|
||||
public string Next;
|
||||
public bool IsMerged;
|
||||
public double LastX;
|
||||
public double LastY;
|
||||
public double EndY;
|
||||
public Path Path;
|
||||
|
||||
public PathHelper(string next, bool isMerged, int color, Point start) {
|
||||
Next = next;
|
||||
IsMerged = isMerged;
|
||||
LastX = start.X;
|
||||
LastY = start.Y;
|
||||
EndY = LastY;
|
||||
|
||||
Path = new Path();
|
||||
Path.Color = color % PENS.Length;
|
||||
Path.Points.Add(start);
|
||||
}
|
||||
|
||||
public PathHelper(string next, bool isMerged, int color, Point start, Point to) {
|
||||
Next = next;
|
||||
IsMerged = isMerged;
|
||||
LastX = to.X;
|
||||
LastY = to.Y;
|
||||
EndY = LastY;
|
||||
|
||||
Path = new Path();
|
||||
Path.Color = color % PENS.Length;
|
||||
Path.Points.Add(start);
|
||||
Path.Points.Add(to);
|
||||
}
|
||||
|
||||
public void Add(double x, double y, bool isEnd = false) {
|
||||
if (x > LastX) {
|
||||
Add(new Point(LastX, LastY));
|
||||
Add(new Point(x, y - HALF_HEIGHT));
|
||||
if (isEnd) Add(new Point(x, y));
|
||||
} else if (x < LastX) {
|
||||
if (y > LastY + HALF_HEIGHT) Add(new Point(LastX, LastY + HALF_HEIGHT));
|
||||
Add(new Point(x, y));
|
||||
} else if (isEnd) {
|
||||
Add(new Point(x, y));
|
||||
}
|
||||
|
||||
LastX = x;
|
||||
LastY = y;
|
||||
}
|
||||
|
||||
private void Add(Point p) {
|
||||
if (EndY < p.Y) {
|
||||
Path.Points.Add(p);
|
||||
EndY = p.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Link {
|
||||
public Point Start;
|
||||
public Point Control;
|
||||
public Point End;
|
||||
public int Color;
|
||||
}
|
||||
|
||||
public class Dot {
|
||||
public Point Center;
|
||||
public int Color;
|
||||
}
|
||||
|
||||
public class Data {
|
||||
public List<Path> Paths = new List<Path>();
|
||||
public List<Link> Links = new List<Link>();
|
||||
public List<Dot> Dots = new List<Dot>();
|
||||
}
|
||||
|
||||
private Data data = null;
|
||||
private double startY = 0;
|
||||
|
||||
public CommitGraph() {
|
||||
Models.Theme.AddListener(this, InvalidateVisual);
|
||||
IsHitTestVisible = false;
|
||||
ClipToBounds = true;
|
||||
}
|
||||
|
||||
public void SetOffset(double offset) {
|
||||
startY = offset;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
public void SetData(List<Models.Commit> commits, bool isSearchResult = false) {
|
||||
if (isSearchResult) {
|
||||
foreach (var c in commits) c.Margin = new Thickness(0);
|
||||
data = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = new Data();
|
||||
var unsolved = new List<PathHelper>();
|
||||
var mapUnsolved = new Dictionary<string, PathHelper>();
|
||||
var ended = new List<PathHelper>();
|
||||
var offsetY = -HALF_HEIGHT;
|
||||
var colorIdx = 0;
|
||||
|
||||
foreach (var commit in commits) {
|
||||
var major = null as PathHelper;
|
||||
var isMerged = commit.IsMerged;
|
||||
var oldCount = unsolved.Count;
|
||||
|
||||
// 更新Y坐标
|
||||
offsetY += UNIT_HEIGHT;
|
||||
|
||||
// 找到第一个依赖于本提交的树,将其他依赖于本提交的树标记为终止,并对已存在的线路调整(防止线重合)
|
||||
double offsetX = -HALF_WIDTH;
|
||||
foreach (var l in unsolved) {
|
||||
if (l.Next == commit.SHA) {
|
||||
if (major == null) {
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = l;
|
||||
|
||||
if (commit.Parents.Count > 0) {
|
||||
major.Next = commit.Parents[0];
|
||||
if (!mapUnsolved.ContainsKey(major.Next)) mapUnsolved.Add(major.Next, major);
|
||||
} else {
|
||||
major.Next = "ENDED";
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
major.Add(offsetX, offsetY);
|
||||
} else {
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
isMerged = isMerged || l.IsMerged;
|
||||
} else {
|
||||
if (!mapUnsolved.ContainsKey(l.Next)) mapUnsolved.Add(l.Next, l);
|
||||
offsetX += UNIT_WIDTH;
|
||||
l.Add(offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理本提交为非当前分支HEAD的情况(创建新依赖线路)
|
||||
if (major == null && commit.Parents.Count > 0) {
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = new PathHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
|
||||
unsolved.Add(major);
|
||||
temp.Paths.Add(major.Path);
|
||||
colorIdx++;
|
||||
}
|
||||
|
||||
// 确定本提交的点的位置
|
||||
Point position = new Point(offsetX, offsetY);
|
||||
if (major != null) {
|
||||
major.IsMerged = isMerged;
|
||||
position.X = major.LastX;
|
||||
position.Y = offsetY;
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = major.Path.Color });
|
||||
} else {
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = 0 });
|
||||
}
|
||||
|
||||
// 处理本提交的其他依赖
|
||||
for (int j = 1; j < commit.Parents.Count; j++) {
|
||||
var parent = commit.Parents[j];
|
||||
if (mapUnsolved.ContainsKey(parent)) {
|
||||
var l = mapUnsolved[parent];
|
||||
var link = new Link();
|
||||
|
||||
link.Start = position;
|
||||
link.End = new Point(l.LastX, offsetY + HALF_HEIGHT);
|
||||
link.Control = new Point(link.End.X, link.Start.Y);
|
||||
link.Color = l.Path.Color;
|
||||
temp.Links.Add(link);
|
||||
} else {
|
||||
offsetX += UNIT_WIDTH;
|
||||
|
||||
// 防止有下一个提交有ended线时,新的分支线与旧线重合
|
||||
var l = new PathHelper(commit.Parents[j], isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT));
|
||||
unsolved.Add(l);
|
||||
temp.Paths.Add(l.Path);
|
||||
colorIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理已终止的线
|
||||
foreach (var l in ended) {
|
||||
l.Add(position.X, position.Y, true);
|
||||
unsolved.Remove(l);
|
||||
}
|
||||
|
||||
// 加入本次提交
|
||||
commit.IsMerged = isMerged;
|
||||
commit.Margin = new Thickness(Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH), 0, 0, 0);
|
||||
|
||||
// 清理
|
||||
ended.Clear();
|
||||
mapUnsolved.Clear();
|
||||
}
|
||||
|
||||
// 处理尚未终结的线
|
||||
for (int i = 0; i < unsolved.Count; i++) {
|
||||
var path = unsolved[i];
|
||||
var endY = (commits.Count - 0.5) * UNIT_HEIGHT;
|
||||
|
||||
if (path.Path.Points.Count == 1 && path.Path.Points[0].Y == endY) continue;
|
||||
path.Add((i + 0.5) * UNIT_WIDTH, endY, true);
|
||||
}
|
||||
unsolved.Clear();
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
data = temp;
|
||||
InvalidateVisual();
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
if (data == null) return;
|
||||
|
||||
dc.PushTransform(new TranslateTransform(0, -startY));
|
||||
|
||||
// 计算边界
|
||||
var top = startY;
|
||||
var bottom = startY + ActualHeight;
|
||||
|
||||
// 绘制线
|
||||
DrawCurves(dc, top, bottom);
|
||||
|
||||
// 绘制点
|
||||
var dotFill = FindResource("Brush.Contents") as Brush;
|
||||
foreach (var dot in data.Dots) {
|
||||
if (dot.Center.Y < top) continue;
|
||||
if (dot.Center.Y > bottom) break;
|
||||
|
||||
dc.DrawEllipse(dotFill, PENS[dot.Color], dot.Center, 3, 3);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCurves(DrawingContext dc, double top, double bottom) {
|
||||
foreach (var line in data.Paths) {
|
||||
var last = line.Points[0];
|
||||
var size = line.Points.Count;
|
||||
|
||||
if (line.Points[size - 1].Y < top) continue;
|
||||
if (last.Y > bottom) continue;
|
||||
|
||||
var geo = new StreamGeometry();
|
||||
var pen = PENS[line.Color];
|
||||
using (var ctx = geo.Open()) {
|
||||
var started = false;
|
||||
var ended = false;
|
||||
for (int i = 1; i < size; i++) {
|
||||
var cur = line.Points[i];
|
||||
if (cur.Y < top) {
|
||||
last = cur;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
ctx.BeginFigure(last, false, false);
|
||||
started = true;
|
||||
}
|
||||
|
||||
if (cur.Y > bottom) {
|
||||
cur.Y = bottom;
|
||||
ended = true;
|
||||
}
|
||||
|
||||
if (cur.X > last.X) {
|
||||
ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur, true, false);
|
||||
} else if (cur.X < last.X) {
|
||||
if (i < size - 1) {
|
||||
var midY = (last.Y + cur.Y) / 2;
|
||||
var midX = (last.X + cur.X) / 2;
|
||||
ctx.PolyQuadraticBezierTo(new Point[] {
|
||||
new Point(last.X, midY),
|
||||
new Point(midX, midY),
|
||||
new Point(cur.X, midY),
|
||||
cur}, true, false);
|
||||
} else {
|
||||
ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur, true, false);
|
||||
}
|
||||
} else {
|
||||
ctx.LineTo(cur, true, false);
|
||||
}
|
||||
|
||||
if (ended) break;
|
||||
last = cur;
|
||||
}
|
||||
}
|
||||
|
||||
geo.Freeze();
|
||||
dc.DrawGeometry(null, pen, geo);
|
||||
}
|
||||
|
||||
foreach (var link in data.Links) {
|
||||
if (link.End.Y < top) continue;
|
||||
if (link.Start.Y > bottom) break;
|
||||
|
||||
var geo = new StreamGeometry();
|
||||
using (var ctx = geo.Open()) {
|
||||
ctx.BeginFigure(link.Start, false, false);
|
||||
ctx.QuadraticBezierTo(link.Control, link.End, true, false);
|
||||
}
|
||||
|
||||
geo.Freeze();
|
||||
dc.DrawGeometry(null, PENS[link.Color], geo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
/// <summary>
|
||||
/// 支持部分高亮的文本组件
|
||||
/// </summary>
|
||||
public class HighlightableTextBlock : TextBlock {
|
||||
private static readonly Brush BG_EMPTY = new SolidColorBrush(Color.FromArgb(60, 0, 0, 0));
|
||||
private static readonly Brush BG_ADDED = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
||||
private static readonly Brush BG_DELETED = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
|
||||
private static readonly Brush HL_ADDED = new SolidColorBrush(Color.FromArgb(128, 0, 255, 0));
|
||||
private static readonly Brush HL_DELETED = new SolidColorBrush(Color.FromArgb(128, 255, 0, 0));
|
||||
|
||||
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
|
||||
"Data",
|
||||
typeof(Models.TextChanges.Line),
|
||||
typeof(HighlightableTextBlock),
|
||||
new PropertyMetadata(null, OnContentChanged));
|
||||
|
||||
public Models.TextChanges.Line Data {
|
||||
get { return (Models.TextChanges.Line)GetValue(DataProperty); }
|
||||
set { SetValue(DataProperty, value); }
|
||||
}
|
||||
|
||||
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var txt = d as HighlightableTextBlock;
|
||||
if (txt == null) return;
|
||||
|
||||
txt.Inlines.Clear();
|
||||
txt.Text = null;
|
||||
txt.Background = Brushes.Transparent;
|
||||
txt.FontStyle = FontStyles.Normal;
|
||||
|
||||
if (txt.Data == null) return;
|
||||
|
||||
Brush highlightBrush = Brushes.Transparent;
|
||||
switch (txt.Data.Mode) {
|
||||
case Models.TextChanges.LineMode.None:
|
||||
txt.Background = BG_EMPTY;
|
||||
break;
|
||||
case Models.TextChanges.LineMode.Indicator:
|
||||
txt.FontStyle = FontStyles.Italic;
|
||||
break;
|
||||
case Models.TextChanges.LineMode.Added:
|
||||
txt.Background = BG_ADDED;
|
||||
highlightBrush = HL_ADDED;
|
||||
break;
|
||||
case Models.TextChanges.LineMode.Deleted:
|
||||
txt.Background = BG_DELETED;
|
||||
highlightBrush = HL_DELETED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
txt.SetResourceReference(ForegroundProperty, txt.Data.Mode == Models.TextChanges.LineMode.Indicator ? "Brush.FG2" : "Brush.FG1");
|
||||
|
||||
if (txt.Data.Highlights == null || txt.Data.Highlights.Count == 0) {
|
||||
txt.Text = txt.Data.Content;
|
||||
return;
|
||||
}
|
||||
|
||||
var started = 0;
|
||||
foreach (var highlight in txt.Data.Highlights) {
|
||||
if (started < highlight.Start) {
|
||||
txt.Inlines.Add(new Run(txt.Data.Content.Substring(started, highlight.Start - started)));
|
||||
}
|
||||
|
||||
txt.Inlines.Add(new Run() {
|
||||
Background = highlightBrush,
|
||||
Text = txt.Data.Content.Substring(highlight.Start, highlight.Count),
|
||||
});
|
||||
|
||||
started = highlight.Start + highlight.Count;
|
||||
}
|
||||
|
||||
if (started < txt.Data.Content.Length) {
|
||||
txt.Inlines.Add(new Run(txt.Data.Content.Substring(started)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 简化只有一个Icon的Button
|
||||
/// </summary>
|
||||
public class IconButton : Button {
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
"Icon",
|
||||
typeof(Geometry),
|
||||
typeof(IconButton),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public Geometry Icon {
|
||||
get { return (Geometry)GetValue(IconProperty); }
|
||||
set { SetValue(IconProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconSizeProperty = DependencyProperty.Register(
|
||||
"IconSize",
|
||||
typeof(double),
|
||||
typeof(IconButton),
|
||||
new PropertyMetadata(14.0));
|
||||
|
||||
public double IconSize {
|
||||
get { return (double)GetValue(IconSizeProperty); }
|
||||
set { SetValue(IconSizeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HoverBackgroundProperty = DependencyProperty.Register(
|
||||
"HoverBackground",
|
||||
typeof(Brush),
|
||||
typeof(IconButton),
|
||||
new PropertyMetadata(Brushes.Transparent));
|
||||
|
||||
public Brush HoverBackground {
|
||||
get { return (Brush)GetValue(HoverBackgroundProperty); }
|
||||
set { SetValue(HoverBackgroundProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 加载中图标
|
||||
/// </summary>
|
||||
public class Loading : UserControl {
|
||||
private Path icon = null;
|
||||
|
||||
public static readonly DependencyProperty IsAnimatingProperty = DependencyProperty.Register(
|
||||
"IsAnimating",
|
||||
typeof(bool),
|
||||
typeof(Loading),
|
||||
new PropertyMetadata(false, OnIsAnimatingChanged));
|
||||
|
||||
public bool IsAnimating {
|
||||
get { return (bool)GetValue(IsAnimatingProperty); }
|
||||
set { SetValue(IsAnimatingProperty, value); }
|
||||
}
|
||||
|
||||
public Loading() {
|
||||
icon = new Path();
|
||||
icon.Data = FindResource("Icon.Loading") as Geometry;
|
||||
icon.RenderTransformOrigin = new Point(.5, .5);
|
||||
icon.RenderTransform = new RotateTransform(0);
|
||||
icon.Width = double.NaN;
|
||||
icon.Height = double.NaN;
|
||||
|
||||
AddChild(icon);
|
||||
}
|
||||
|
||||
private static void OnIsAnimatingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var loading = d as Loading;
|
||||
if (loading == null) return;
|
||||
|
||||
if (loading.IsAnimating) {
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
loading.icon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
} else {
|
||||
loading.icon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 用于方便切换子页面的组件
|
||||
/// </summary>
|
||||
public class PageContainer : Grid {
|
||||
private Dictionary<string, UIElement> pages;
|
||||
private string front;
|
||||
|
||||
public PageContainer() {
|
||||
pages = new Dictionary<string, UIElement>();
|
||||
front = null;
|
||||
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
public void Add(string id, UIElement view) {
|
||||
view.Visibility = Visibility.Collapsed;
|
||||
pages.Add(id, view);
|
||||
Children.Add(view);
|
||||
}
|
||||
|
||||
public UIElement Get(string id) {
|
||||
if (pages.ContainsKey(id)) return pages[id];
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Goto(string id) {
|
||||
if (!pages.ContainsKey(id)) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(front)) {
|
||||
if (front == id) return;
|
||||
pages[front].Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
front = id;
|
||||
pages[front].Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public void Remove(string id) {
|
||||
if (!pages.ContainsKey(id)) return;
|
||||
if (front == id) front = null;
|
||||
Children.Remove(pages[id]);
|
||||
pages.Remove(id);
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e) {
|
||||
foreach (var child in Children) {
|
||||
var elem = child as UIElement;
|
||||
var id = elem.Uid;
|
||||
if (string.IsNullOrEmpty(id)) continue;
|
||||
|
||||
pages.Add(id, elem);
|
||||
front = id;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(front)) {
|
||||
pages[front].Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 可显示弹出面板的容器接口
|
||||
/// </summary>
|
||||
public interface IPopupContainer {
|
||||
void Show(PopupWidget widget);
|
||||
void ShowAndStart(PopupWidget widget);
|
||||
void UpdateProgress(string message);
|
||||
void ClosePopups(bool unlock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可弹出面板
|
||||
/// </summary>
|
||||
public class PopupWidget : UserControl {
|
||||
private static Dictionary<string, IPopupContainer> containers = new Dictionary<string, IPopupContainer>();
|
||||
private static string currentContainer = null;
|
||||
private IPopupContainer mine = null;
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个弹出容器
|
||||
/// </summary>
|
||||
/// <param name="id">页面ID</param>
|
||||
/// <param name="container">容器实例</param>
|
||||
public static void RegisterContainer(string id, IPopupContainer container) {
|
||||
if (containers.ContainsKey(id)) containers[id] = container;
|
||||
else containers.Add(id, container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除一个弹出容器
|
||||
/// </summary>
|
||||
/// <param name="id">容器ID</param>
|
||||
public static void UnregisterContainer(string id) {
|
||||
if (containers.ContainsKey(id)) containers.Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前的弹出容器
|
||||
/// </summary>
|
||||
/// <param name="id">容器ID</param>
|
||||
public static void SetCurrentContainer(string id) {
|
||||
if (containers.ContainsKey(id)) currentContainer = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public PopupWidget() {
|
||||
Height = double.NaN;
|
||||
Padding = new Thickness(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示
|
||||
/// </summary>
|
||||
public void Show() {
|
||||
if (string.IsNullOrEmpty(currentContainer) || !containers.ContainsKey(currentContainer)) return;
|
||||
mine = containers[currentContainer];
|
||||
mine.Show(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示并直接点击开始
|
||||
/// </summary>
|
||||
public void ShowAndStart() {
|
||||
if (string.IsNullOrEmpty(currentContainer) || !containers.ContainsKey(currentContainer)) return;
|
||||
mine = containers[currentContainer];
|
||||
mine.ShowAndStart(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗体标题
|
||||
/// </summary>
|
||||
/// <returns>返回具体的标题</returns>
|
||||
public virtual string GetTitle() {
|
||||
return "TITLE";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击确定时的回调,由程序自己
|
||||
/// </summary>
|
||||
/// <returns>返回一个任务,任务预期返回类型为bool,表示是否关闭Popup</returns>
|
||||
public virtual Task<bool> Start() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新进度显示
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
protected void UpdateProgress(string message) {
|
||||
mine?.UpdateProgress(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 扩展默认TextBox
|
||||
/// </summary>
|
||||
public class TextEdit : TextBox {
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(
|
||||
"Placeholder",
|
||||
typeof(string),
|
||||
typeof(TextEdit),
|
||||
new PropertyMetadata(""));
|
||||
|
||||
public string Placeholder {
|
||||
get { return (string)GetValue(PlaceholderProperty); }
|
||||
set { SetValue(PlaceholderProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlaceholderVisibilityProperty = DependencyProperty.Register(
|
||||
"PlaceholderVisibility",
|
||||
typeof(Visibility),
|
||||
typeof(TextEdit),
|
||||
new PropertyMetadata(Visibility.Visible));
|
||||
|
||||
public Visibility PlaceholderVisibility {
|
||||
get { return (Visibility)GetValue(PlaceholderVisibilityProperty); }
|
||||
set { SetValue(PlaceholderVisibilityProperty, value); }
|
||||
}
|
||||
|
||||
public TextEdit() {
|
||||
TextChanged += OnTextChanged;
|
||||
SelectionChanged += OnSelectionChanged;
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e) {
|
||||
base.OnMouseWheel(e);
|
||||
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) {
|
||||
if (e.Delta > 0) {
|
||||
LineLeft();
|
||||
} else {
|
||||
LineRight();
|
||||
}
|
||||
} else {
|
||||
if (e.Delta > 0) {
|
||||
LineUp();
|
||||
} else {
|
||||
LineDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextChanged(object sender, TextChangedEventArgs e) {
|
||||
PlaceholderVisibility = string.IsNullOrEmpty(Text) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object sender, RoutedEventArgs e) {
|
||||
if (!IsFocused) return;
|
||||
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed && SelectionLength > 0) {
|
||||
var p = Mouse.GetPosition(this);
|
||||
if (p.X <= 8) {
|
||||
LineLeft();
|
||||
} else if (p.X >= ActualWidth - 8) {
|
||||
LineRight();
|
||||
}
|
||||
|
||||
if (p.Y <= 8) {
|
||||
LineUp();
|
||||
} else if (p.Y >= ActualHeight - 8) {
|
||||
LineDown();
|
||||
}
|
||||
} else {
|
||||
var rect = GetRectFromCharacterIndex(CaretIndex);
|
||||
if (rect.Left <= 0) {
|
||||
ScrollToHorizontalOffset(HorizontalOffset + rect.Left);
|
||||
} else if (rect.Right >= ActualWidth) {
|
||||
ScrollToHorizontalOffset(HorizontalOffset + rect.Right);
|
||||
}
|
||||
|
||||
if (rect.Top <= 0) {
|
||||
ScrollToVerticalOffset(VerticalOffset + rect.Top);
|
||||
} else if (rect.Bottom >= ActualHeight) {
|
||||
ScrollToVerticalOffset(VerticalOffset + rect.Bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 树
|
||||
/// </summary>
|
||||
public class Tree : TreeView {
|
||||
public static readonly DependencyProperty MultiSelectionProperty = DependencyProperty.Register(
|
||||
"MultiSelection",
|
||||
typeof(bool),
|
||||
typeof(Tree),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
public bool MultiSelection {
|
||||
get { return (bool)GetValue(MultiSelectionProperty); }
|
||||
set { SetValue(MultiSelectionProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IndentProperty = DependencyProperty.Register(
|
||||
"Indent",
|
||||
typeof(double),
|
||||
typeof(TreeItem),
|
||||
new PropertyMetadata(16.0));
|
||||
|
||||
public double Indent {
|
||||
get { return (double)GetValue(IndentProperty); }
|
||||
set { SetValue(IndentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent(
|
||||
"SelectionChanged",
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler),
|
||||
typeof(Tree));
|
||||
|
||||
public event RoutedEventHandler SelectionChanged {
|
||||
add { AddHandler(SelectionChangedEvent, value); }
|
||||
remove { RemoveHandler(SelectionChangedEvent, value); }
|
||||
}
|
||||
|
||||
public List<object> Selected {
|
||||
get;
|
||||
set;
|
||||
} = new List<object>();
|
||||
|
||||
public TreeItem FindItem(DependencyObject elem) {
|
||||
if (elem == null) return null;
|
||||
if (elem is TreeItem) return elem as TreeItem;
|
||||
if (elem is Tree) return null;
|
||||
return FindItem(VisualTreeHelper.GetParent(elem));
|
||||
}
|
||||
|
||||
public void SelectAll() {
|
||||
SelectAllChildren(this);
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
|
||||
public void UnselectAll() {
|
||||
if (Selected.Count == 0) return;
|
||||
|
||||
UnselectAllChildren(this);
|
||||
Selected.Clear();
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
|
||||
public void Select(object dataContext) {
|
||||
if (Selected.Count == 1 && Selected[0] == dataContext) return;
|
||||
|
||||
var item = FindItemByDataContext(this, dataContext);
|
||||
if (item != null) {
|
||||
AddSelected(item, true);
|
||||
item.BringIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() {
|
||||
return new TreeItem(0, Indent);
|
||||
}
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) {
|
||||
return item is TreeItem;
|
||||
}
|
||||
|
||||
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) {
|
||||
base.OnItemsSourceChanged(oldValue, newValue);
|
||||
|
||||
if (Selected.Count > 0) {
|
||||
Selected.Clear();
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e) {
|
||||
base.OnPreviewKeyDown(e);
|
||||
|
||||
if (MultiSelection && e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control) {
|
||||
SelectAll();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseDown(MouseButtonEventArgs e) {
|
||||
base.OnPreviewMouseDown(e);
|
||||
|
||||
var hit = VisualTreeHelper.HitTest(this, e.GetPosition(this));
|
||||
if (hit == null || hit.VisualHit == null) return;
|
||||
|
||||
var item = FindItem(hit.VisualHit);
|
||||
if (item == null) return;
|
||||
|
||||
if (!MultiSelection) {
|
||||
if (item.IsChecked) return;
|
||||
AddSelected(item, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
|
||||
if (item.IsChecked) {
|
||||
RemoveSelected(item);
|
||||
} else {
|
||||
AddSelected(item, false);
|
||||
}
|
||||
} else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && Selected.Count > 0) {
|
||||
var last = FindItemByDataContext(this, Selected.Last());
|
||||
if (last == item) return;
|
||||
|
||||
var lastPos = last.PointToScreen(new Point(0, 0));
|
||||
var curPos = item.PointToScreen(new Point(0, 0));
|
||||
if (lastPos.Y > curPos.Y) {
|
||||
SelectRange(this, item, last);
|
||||
} else {
|
||||
SelectRange(this, last, item);
|
||||
}
|
||||
|
||||
AddSelected(item, false);
|
||||
} else if (e.RightButton == MouseButtonState.Pressed) {
|
||||
if (item.IsChecked) return;
|
||||
AddSelected(item, true);
|
||||
} else {
|
||||
if (item.IsChecked && Selected.Count == 1) return;
|
||||
AddSelected(item, true);
|
||||
}
|
||||
}
|
||||
|
||||
private TreeItem FindItemByDataContext(ItemsControl control, object data) {
|
||||
if (control == null) return null;
|
||||
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
|
||||
if (control.Items[i] == data) return child;
|
||||
|
||||
var found = FindItemByDataContext(child, data);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AddSelected(TreeItem item, bool removeOthers) {
|
||||
if (!item.IsVisible) return;
|
||||
|
||||
if (removeOthers && Selected.Count > 0) {
|
||||
UnselectAllChildren(this);
|
||||
Selected.Clear();
|
||||
}
|
||||
|
||||
item.IsChecked = true;
|
||||
Selected.Add(item.DataContext);
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
|
||||
private void RemoveSelected(TreeItem item) {
|
||||
item.IsChecked = false;
|
||||
Selected.Remove(item.DataContext);
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
|
||||
private void SelectAllChildren(ItemsControl control) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
|
||||
if (child == null) continue;
|
||||
|
||||
child.IsChecked = true;
|
||||
Selected.Add(control.Items[i]);
|
||||
SelectAllChildren(child);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnselectAllChildren(ItemsControl control) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
|
||||
if (child == null) continue;
|
||||
if (child.IsChecked) child.IsChecked = false;
|
||||
UnselectAllChildren(child);
|
||||
}
|
||||
}
|
||||
|
||||
private int SelectRange(ItemsControl control, TreeItem from, TreeItem to, int matches = 0) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
|
||||
if (child == null) continue;
|
||||
|
||||
if (matches == 1) {
|
||||
if (child == to) return 2;
|
||||
Selected.Add(control.Items[i]);
|
||||
child.IsChecked = true;
|
||||
if (TryEndRangeSelection(child, to)) return 2;
|
||||
} else if (child == from) {
|
||||
matches = 1;
|
||||
if (TryEndRangeSelection(child, to)) return 2;
|
||||
} else {
|
||||
matches = SelectRange(child, from, to, matches);
|
||||
if (matches == 2) return 2;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
private bool TryEndRangeSelection(ItemsControl control, TreeItem end) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeItem;
|
||||
if (child == null) continue;
|
||||
|
||||
if (child == end) {
|
||||
return true;
|
||||
} else {
|
||||
Selected.Add(control.Items[i]);
|
||||
child.IsChecked = true;
|
||||
|
||||
var ended = TryEndRangeSelection(child, end);
|
||||
if (ended) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
/// <summary>
|
||||
/// 树节点
|
||||
/// </summary>
|
||||
public class TreeItem : TreeViewItem {
|
||||
|
||||
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register(
|
||||
"IsChecked",
|
||||
typeof(bool),
|
||||
typeof(TreeItem),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
public bool IsChecked {
|
||||
get { return (bool)GetValue(IsCheckedProperty); }
|
||||
set { SetValue(IsCheckedProperty, value); }
|
||||
}
|
||||
|
||||
private int depth = 0;
|
||||
private double indent = 16;
|
||||
|
||||
public TreeItem(int depth, double indent) {
|
||||
this.depth = depth;
|
||||
this.indent = indent;
|
||||
|
||||
Padding = new Thickness(indent * depth, 0, 0, 0);
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() {
|
||||
return new TreeItem(depth + 1, indent);
|
||||
}
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) {
|
||||
return item is TreeItem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
/// <summary>
|
||||
/// 项目使用的窗体基类
|
||||
/// </summary>
|
||||
public class Window : System.Windows.Window {
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct OSVERSIONINFOEX {
|
||||
public int Size;
|
||||
public int Major;
|
||||
public int Minor;
|
||||
public int Build;
|
||||
public int Platform;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string CSDVersion;
|
||||
public ushort ServicePackMajor;
|
||||
public ushort ServicePackMinor;
|
||||
public short SuiteMask;
|
||||
public byte ProductType;
|
||||
public byte Reserved;
|
||||
}
|
||||
|
||||
[DllImport("ntdll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int RtlGetVersion(ref OSVERSIONINFOEX version);
|
||||
|
||||
[DllImport("dwmapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern long DwmSetWindowAttribute(IntPtr hwnd,
|
||||
uint attribute,
|
||||
ref uint pvAttribute,
|
||||
uint cbAttribute);
|
||||
|
||||
public static readonly DependencyProperty IsMaximizedProperty = DependencyProperty.Register(
|
||||
"IsMaximized",
|
||||
typeof(bool),
|
||||
typeof(Window),
|
||||
new PropertyMetadata(false, OnIsMaximizedChanged));
|
||||
|
||||
public bool IsMaximized {
|
||||
get { return (bool)GetValue(IsMaximizedProperty); }
|
||||
set { SetValue(IsMaximizedProperty, value); }
|
||||
}
|
||||
|
||||
public Window() {
|
||||
Style = FindResource("Style.Window") as Style;
|
||||
Loaded += OnWindowLoaded;
|
||||
}
|
||||
|
||||
private void OnWindowLoaded(object sender, RoutedEventArgs e) {
|
||||
OnStateChanged(null);
|
||||
|
||||
// Windows 11 需要特殊处理一下边框,使得其与Window 10下表现一致
|
||||
OSVERSIONINFOEX version = new OSVERSIONINFOEX() { Size = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) };
|
||||
if (RtlGetVersion(ref version) == 0 && version.Major >= 10 && version.Build >= 22000) {
|
||||
Models.Theme.Changed += UpdateBorderColor;
|
||||
Unloaded += (_, __) => Models.Theme.Changed -= UpdateBorderColor;
|
||||
|
||||
UpdateBorderColor();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBorderColor() {
|
||||
IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
|
||||
Color color = (BorderBrush as SolidColorBrush).Color;
|
||||
uint preference = ((uint)color.B << 16) | ((uint)color.G << 8) | (uint)color.R;
|
||||
DwmSetWindowAttribute(hWnd, 34, ref preference, sizeof(uint));
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(EventArgs e) {
|
||||
if (WindowState == WindowState.Maximized) {
|
||||
if (!IsMaximized) IsMaximized = true;
|
||||
BorderThickness = new Thickness(0);
|
||||
Padding = new Thickness((SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width) / 2);
|
||||
} else {
|
||||
if (IsMaximized) IsMaximized = false;
|
||||
BorderThickness = new Thickness(1);
|
||||
Padding = new Thickness(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIsMaximizedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
Window w = d as Window;
|
||||
if (w != null) {
|
||||
if (w.IsMaximized) {
|
||||
SystemCommands.MaximizeWindow(w);
|
||||
} else if (w.WindowState != WindowState.Minimized) {
|
||||
SystemCommands.RestoreWindow(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
public class BoolToCollapsed : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
public class BranchToName : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
var b = value as Models.Branch;
|
||||
if (b == null) return "";
|
||||
return string.IsNullOrEmpty(b.Remote) ? b.Name : $"{b.Remote}/{b.Name}";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
public class FontFamiliesToName : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is string s)
|
||||
return s.Split(',').ElementAt(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
public class IntToBookmarkBrush : IValueConverter {
|
||||
public static readonly Brush[] COLORS = new Brush[] {
|
||||
Brushes.Transparent,
|
||||
Brushes.Red,
|
||||
Brushes.Orange,
|
||||
Brushes.Gold,
|
||||
Brushes.ForestGreen,
|
||||
Brushes.DarkCyan,
|
||||
Brushes.DeepSkyBlue,
|
||||
Brushes.Purple,
|
||||
};
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
var index = (int)value;
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class IntToBookmarkStrokeBrush : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
var index = (int)value;
|
||||
return index == 0 ? App.Current.FindResource("Brush.FG1") : Brushes.Transparent;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
public class InverseBool : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return !(bool)value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return !(bool)value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// 将路径转换为纯文件名
|
||||
/// </summary>
|
||||
public class PureFileName : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return Path.GetFileName(value as string);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// 将路径转换为纯目录
|
||||
/// </summary>
|
||||
public class PureFolderName : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return Path.GetDirectoryName(value as string);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Views.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// 将当前窗口的状态转换为标题栏高度
|
||||
/// </summary>
|
||||
public class WindowStateToTitleBarHeight : IValueConverter {
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
WindowState state = (WindowState)value;
|
||||
return state == WindowState.Normal ? 36 : 30;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
74
src/Views/CreateBranch.axaml
Normal file
74
src/Views/CreateBranch.axaml
Normal file
|
@ -0,0 +1,74 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CreateBranch"
|
||||
x:DataType="vm:CreateBranch">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.CreateBranch}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.CreateBranch.BasedOn}"/>
|
||||
<ContentControl Grid.Column="1" Content="{Binding BasedOn}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding, Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" FontFamily="{StaticResource JetBrainsMono}" Foreground="DarkOrange" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Tag">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Tag}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.CreateBranch.Name}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Text="{Binding Name, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.CreateBranch.LocalChanges}"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
|
||||
GroupName="LocalChanges"
|
||||
IsChecked="{Binding AutoStash, Mode=TwoWay}"/>
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.Discard}"
|
||||
GroupName="LocalChanges"
|
||||
Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="3" Grid.Column="1"
|
||||
Content="{DynamicResource Text.CreateBranch.Checkout}"
|
||||
IsChecked="{Binding CheckoutAfterCreated, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/CreateBranch.axaml.cs
Normal file
9
src/Views/CreateBranch.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CreateBranch : UserControl {
|
||||
public CreateBranch() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
17
src/Views/CreateGroup.axaml
Normal file
17
src/Views/CreateGroup.axaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CreateGroup"
|
||||
x:DataType="vm:CreateGroup">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock Classes="bold" FontSize="18" Text="{DynamicResource Text.Welcome.AddRootFolder}"/>
|
||||
|
||||
<Grid Margin="8,16,0,0" Height="28" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="8,0" Text="{DynamicResource Text.Name}"/>
|
||||
<TextBox Grid.Column="1" CornerRadius="3" Text="{Binding Name, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/CreateGroup.axaml.cs
Normal file
9
src/Views/CreateGroup.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CreateGroup : UserControl {
|
||||
public CreateGroup() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
62
src/Views/CreateTag.axaml
Normal file
62
src/Views/CreateTag.axaml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.CreateTag"
|
||||
x:DataType="vm:CreateTag">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.CreateTag}"/>
|
||||
<Grid Margin="0,16,8,0" RowDefinitions="32,32,64" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.CreateTag.BasedOn}"/>
|
||||
<ContentControl Grid.Column="1" Content="{Binding BasedOn}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding, Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" FontFamily="{StaticResource JetBrainsMono}" Foreground="DarkOrange" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Subject}}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.CreateTag.Name}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Text="{Binding TagName, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="0,6,8,0"
|
||||
Text="{DynamicResource Text.CreateTag.Message}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1"
|
||||
Height="56"
|
||||
AcceptsReturn="True" AcceptsTab="False"
|
||||
VerticalAlignment="Center" VerticalContentAlignment="Top"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.CreateTag.Message.Placeholder}"
|
||||
Text="{Binding Message, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/CreateTag.axaml.cs
Normal file
9
src/Views/CreateTag.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class CreateTag : UserControl {
|
||||
public CreateTag() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
20
src/Views/DeleteBranch.axaml
Normal file
20
src/Views/DeleteBranch.axaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<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:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DeleteBranch"
|
||||
x:DataType="vm:DeleteBranch">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteBranch}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,16,0,0">
|
||||
<TextBlock Text="{DynamicResource Text.DeleteBranch.Branch}"/>
|
||||
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Text="{Binding Target, Converter={x:Static c:BranchConverters.ToName}}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/DeleteBranch.axaml.cs
Normal file
9
src/Views/DeleteBranch.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DeleteBranch : UserControl {
|
||||
public DeleteBranch() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
19
src/Views/DeleteRemote.axaml
Normal file
19
src/Views/DeleteRemote.axaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DeleteRemote"
|
||||
x:DataType="vm:DeleteRemote">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteRemote}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,16,0,0">
|
||||
<TextBlock Text="{DynamicResource Text.DeleteRemote.Remote}"/>
|
||||
<Path Width="14" Height="14" Margin="8,6,8,0" Data="{StaticResource Icons.Remote}"/>
|
||||
<TextBlock Text="{Binding Remote.Name}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/DeleteRemote.axaml.cs
Normal file
9
src/Views/DeleteRemote.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DeleteRemote : UserControl {
|
||||
public DeleteRemote() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
44
src/Views/DeleteRepositoryNode.axaml
Normal file
44
src/Views/DeleteRepositoryNode.axaml
Normal file
|
@ -0,0 +1,44 @@
|
|||
<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:c="using:SourceGit.Converters"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DeleteRepositoryNode"
|
||||
x:DataType="vm:DeleteRepositoryNode">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteRepositoryNode.TitleForGroup}"
|
||||
IsVisible="{Binding !Node.IsRepository}"/>
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteRepositoryNode.TitleForRepository}"
|
||||
IsVisible="{Binding Node.IsRepository}"/>
|
||||
|
||||
<Grid Margin="0,16,8,0" Height="28" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.DeleteRepositoryNode.Target}"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Margin="0,0,8,0"
|
||||
Fill="{Binding Node.Bookmark, Converter={x:Static c:BookmarkConverters.ToBrush}}"
|
||||
StrokeThickness="{Binding Node.Bookmark, Converter={x:Static c:BookmarkConverters.ToStrokeThickness}}"
|
||||
Stroke="{DynamicResource Brush.FG1}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Data="{StaticResource Icons.Bookmark}"
|
||||
IsVisible="{Binding Node.IsRepository}"/>
|
||||
<Path Width="12" Height="12" Margin="0,0,8,0"
|
||||
Fill="{DynamicResource Brush.FG1}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Data="{StaticResource Icons.Folder}"
|
||||
IsVisible="{Binding !Node.IsRepository}"/>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Node.Name}"/>
|
||||
<TextBlock Margin="8,0" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Node.Id}" IsVisible="{Binding Node.IsRepository}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/DeleteRepositoryNode.axaml.cs
Normal file
9
src/Views/DeleteRepositoryNode.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DeleteRepositoryNode : UserControl {
|
||||
public DeleteRepositoryNode() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
26
src/Views/DeleteSubmodule.axaml
Normal file
26
src/Views/DeleteSubmodule.axaml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<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:c="using:SourceGit.Converters"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DeleteSubmodule"
|
||||
x:DataType="vm:DeleteSubmodule">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteSubmodule}"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,16,0,0" Height="28" HorizontalAlignment="Center">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.DeleteSubmodule.Path}"/>
|
||||
<Path Width="12" Height="12"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Data="{StaticResource Icons.Submodule}"/>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Submodule}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/DeleteSubmodule.axaml.cs
Normal file
9
src/Views/DeleteSubmodule.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DeleteSubmodule : UserControl {
|
||||
public DeleteSubmodule() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
29
src/Views/DeleteTag.axaml
Normal file
29
src/Views/DeleteTag.axaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DeleteTag"
|
||||
x:DataType="vm:DeleteTag">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.DeleteTag}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.DeleteTag.Tag}"/>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Tag}"/>
|
||||
<TextBlock Text="{Binding Target.Name}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="1" Grid.Column="1"
|
||||
Content="{DynamicResource Text.DeleteTag.WithRemote}"
|
||||
IsChecked="{Binding ShouldPushToRemote, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/DeleteTag.axaml.cs
Normal file
9
src/Views/DeleteTag.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DeleteTag : UserControl {
|
||||
public DeleteTag() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
120
src/Views/DiffView.axaml
Normal file
120
src/Views/DiffView.axaml
Normal file
|
@ -0,0 +1,120 @@
|
|||
<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: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.DiffView"
|
||||
x:DataType="vm:DiffContext">
|
||||
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" Background="{DynamicResource Brush.Window}">
|
||||
<Grid RowDefinitions="26,*">
|
||||
<!-- Toolbar -->
|
||||
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" IsVisible="{Binding IsOrgFilePathVisible}" VerticalAlignment="Center">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.File}" Margin="8,0,0,0"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding OrgFilePath, Converter={x:Static c:PathConverters.TruncateIfTooLong}}" FontSize="11"/>
|
||||
<TextBlock Margin="8,0,0,0" Text="→"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.File}" Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding FilePath, Converter={x:Static c:PathConverters.TruncateIfTooLong}}" FontSize="11"/>
|
||||
<Path Grid.Column="2" Classes="rotating" Width="10" Height="10" Margin="8,0" Data="{DynamicResource Icons.Loading}" IsVisible="{Binding IsLoading}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Margin="32,0,0,0" Orientation="Horizontal" IsVisible="{Binding IsTextDiff}" VerticalAlignment="Center">
|
||||
<ToggleButton x:Name="toggleDisplayMode"
|
||||
Classes="layout_direction"
|
||||
Width="32" Height="26"
|
||||
Padding="9,6"
|
||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseCombinedTextDiff, Mode=TwoWay}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.Mode}"/>
|
||||
<Button Classes="icon_button" Width="32" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
|
||||
<Path Width="14" Height="14" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Same Page -->
|
||||
<Border Grid.Row="1" Background="{DynamicResource Brush.Window}" IsVisible="{Binding IsNoChange}">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Check}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Margin="0,16,0,0"
|
||||
Text="{DynamicResource Text.Diff.NoChange}"
|
||||
FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Diff Contents -->
|
||||
<ContentControl Grid.Row="1" Content="{Binding Content}">
|
||||
<ContentControl.DataTemplates>
|
||||
<!-- Binary Diff -->
|
||||
<DataTemplate DataType="m:BinaryDiff">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.Diff.Binary}"
|
||||
Margin="0,0,0,32"
|
||||
FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Binary}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<Grid Margin="0,16,0,0" HorizontalAlignment="Center" RowDefinitions="32,32" ColumnDefinitions="Auto,Auto,Auto" TextElement.FontFamily="{StaticResource JetBrainsMono}">
|
||||
<Border Grid.Row="0" Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.Diff.Binary.Old}" Margin="8,0" FontSize="10"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding OldSize}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right" FontSize="16" Margin="8,0"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" FontSize="16"/>
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="0" Height="16" Background="{DynamicResource Brush.Accent1}" CornerRadius="8" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.Diff.Binary.New}" Margin="8,0" FontSize="10"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding NewSize}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right" FontSize="16" Margin="8,0"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" FontSize="16"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- LFS Diff -->
|
||||
<DataTemplate DataType="m:LFSDiff">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.Diff.LFS}"
|
||||
Margin="0,0,0,32"
|
||||
FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.LFS}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<Grid Margin="0,16,0,0" HorizontalAlignment="Center" RowDefinitions="32,32" ColumnDefinitions="Auto,Auto,Auto" TextElement.FontFamily="{StaticResource JetBrainsMono}">
|
||||
<Border Grid.Row="0" Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.Diff.Binary.Old}" Margin="8,0" FontSize="10"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Old.Size}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right" FontSize="16" Margin="8,0"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" FontSize="16"/>
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="0" Height="16" Background="{DynamicResource Brush.Accent1}" CornerRadius="8" VerticalAlignment="Center">
|
||||
<TextBlock Text="{DynamicResource Text.Diff.Binary.New}" Margin="8,0" FontSize="10"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding New.Size}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right" FontSize="16" Margin="8,0"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" FontSize="16"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Text Diff -->
|
||||
<DataTemplate DataType="m:TextDiff">
|
||||
<v:TextDiffView TextDiff="{Binding}" UseCombined="{Binding #toggleDisplayMode.IsChecked}"/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
9
src/Views/DiffView.axaml.cs
Normal file
9
src/Views/DiffView.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DiffView : UserControl {
|
||||
public DiffView() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
45
src/Views/Discard.axaml
Normal file
45
src/Views/Discard.axaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<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:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Discard"
|
||||
x:DataType="vm:Discard">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Discard}"/>
|
||||
|
||||
<Grid Margin="0,16,0,8" RowDefinitions="32,32" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,8,0" HorizontalAlignment="Right" Text="{DynamicResource Text.Discard.Changes}"/>
|
||||
<ContentControl Grid.Row="0" Grid.Column="1" Content="{Binding Mode}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:DiscardModeAll">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Margin="0,2,0,0" Data="{StaticResource Icons.Folder.Open}"/>
|
||||
<TextBlock Text="{DynamicResource Text.Discard.All}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="vm:DiscardModeSingle">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.File}"/>
|
||||
<TextBlock Text="{Binding File}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="vm:DiscardModeMulti">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.File}"/>
|
||||
<TextBlock Text="{Binding Count, Converter={x:Static c:StringConverters.FormatByResourceKey}, ConverterParameter='Discard.Total'}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Text="{DynamicResource Text.Discard.Warning}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/Discard.axaml.cs
Normal file
9
src/Views/Discard.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class Discard : UserControl {
|
||||
public Discard() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
29
src/Views/DropStash.axaml
Normal file
29
src/Views/DropStash.axaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<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:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DropStash"
|
||||
x:DataType="vm:DropStash">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.StashDropConfirm}"/>
|
||||
<Grid Margin="0,16,8,0" Height="28" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.StashDropConfirm.Label}"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Margin="0,0,8,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
Data="{StaticResource Icons.Stashes}"/>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Stash.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Stash.Message}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
9
src/Views/DropStash.axaml.cs
Normal file
9
src/Views/DropStash.axaml.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class DropStash : UserControl {
|
||||
public DropStash() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
57
src/Views/EditRemote.axaml
Normal file
57
src/Views/EditRemote.axaml
Normal file
|
@ -0,0 +1,57 @@
|
|||
<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:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.EditRemote"
|
||||
x:DataType="vm:EditRemote">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Remote.AddTitle}"/>
|
||||
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,Auto" ColumnDefinitions="150,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Remote.Name}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.Remote.Name.Placeholder}"
|
||||
Text="{Binding Name, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Remote.URL}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Height="26"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2"
|
||||
Watermark="{DynamicResource Text.Remote.URL.Placeholder}"
|
||||
Text="{Binding Url, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.SSHKey}"
|
||||
IsVisible="{Binding UseSSH}"/>
|
||||
<Grid Grid.Row="2" Grid.Column="2" Height="32" ColumnDefinitions="*,Auto" IsVisible="{Binding UseSSH}">
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtSSHKey"
|
||||
Height="26"
|
||||
CornerRadius="3"
|
||||
Watermark="{DynamicResource Text.SSHKey.Placeholder}"
|
||||
Text="{Binding SSHKey, Mode=TwoWay}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="32" Height="32" Margin="4,0,0,0" Click="SelectSSHKey">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
22
src/Views/EditRemote.axaml.cs
Normal file
22
src/Views/EditRemote.axaml.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class EditRemote : UserControl {
|
||||
public EditRemote() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SelectSSHKey(object sender, RoutedEventArgs e) {
|
||||
var options = new FilePickerOpenOptions() { AllowMultiple = false, FileTypeFilter = [new FilePickerFileType("SSHKey") { Patterns = ["*.*"] }] };
|
||||
var toplevel = TopLevel.GetTopLevel(this);
|
||||
var selected = await toplevel.StorageProvider.OpenFilePickerAsync(options);
|
||||
if (selected.Count == 1) {
|
||||
txtSSHKey.Text = selected[0].Path.LocalPath;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<controls:Window
|
||||
x:Class="SourceGit.Views.EditRepositoryDisplayName"
|
||||
x:Name="me"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
xmlns:validations="clr-namespace:SourceGit.Views.Validations"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Text.Welcome.Rename}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
Width="500" SizeToContent="Height">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="48"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<Path Grid.Column="0" Margin="6,0" Width="12" Height="12" Data="{StaticResource Icon.Preference}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.Welcome.Rename}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="OnCancel"
|
||||
Width="28"
|
||||
IconSize="10"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<Grid Grid.Row="2" Margin="16,24,16,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Margin="0,0,8,0" Text="{DynamicResource Text.Welcome.NewName}"/>
|
||||
<controls:TextEdit Grid.Column="1" x:Name="txtName" Height="24">
|
||||
<controls:TextEdit.Text>
|
||||
<Binding ElementName="me" Path="NewName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<validations:Required/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</controls:TextEdit.Text>
|
||||
</controls:TextEdit>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="3" Height="32" Margin="16,0" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Click="OnSure" Width="80" Content="{DynamicResource Text.Sure}" BorderBrush="{DynamicResource Brush.FG1}" Background="{DynamicResource Brush.Accent1}" FontWeight="Bold"/>
|
||||
<Button Click="OnCancel" Width="80" Margin="8,0,0,0" Content="{DynamicResource Text.Cancel}" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:Window>
|
|
@ -1,35 +0,0 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
/// <summary>
|
||||
/// 修改仓库显示名称
|
||||
/// </summary>
|
||||
public partial class EditRepositoryDisplayName : Controls.Window {
|
||||
private Models.Repository repository = null;
|
||||
|
||||
public string NewName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public EditRepositoryDisplayName(Models.Repository repository) {
|
||||
this.repository = repository;
|
||||
NewName = repository.Name;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnSure(object s, RoutedEventArgs e) {
|
||||
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtName)) return;
|
||||
repository.Name = NewName;
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnCancel(object s, RoutedEventArgs e) {
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
54
src/Views/EditRepositoryNode.axaml
Normal file
54
src/Views/EditRepositoryNode.axaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<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:c="using:SourceGit.Converters"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.EditRepositoryNode"
|
||||
x:DataType="vm:EditRepositoryNode">
|
||||
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.EditRepositoryNode.TitleForGroup}"
|
||||
IsVisible="{Binding !IsRepository}"/>
|
||||
|
||||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.EditRepositoryNode.TitleForRepository}"
|
||||
IsVisible="{Binding IsRepository}"/>
|
||||
|
||||
<Grid Height="28" Margin="8,16,0,0" ColumnDefinitions="120,*" >
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,8,0" Text="{DynamicResource Text.EditRepositoryNode.Target}"/>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Name, Mode=OneTime}" IsVisible="{Binding !IsRepository}"/>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Id, Mode=TwoWay}" IsVisible="{Binding IsRepository}"/>
|
||||
</Grid>
|
||||
<Grid Height="28" Margin="8,4,0,0" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.EditRepositoryNode.Name}"/>
|
||||
<TextBox Grid.Column="1" CornerRadius="3" Text="{Binding Name, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
<Grid Height="28" Margin="8,4,0,0" ColumnDefinitions="120,*" IsVisible="{Binding IsRepository}">
|
||||
<TextBlock Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,8,0" Text="{DynamicResource Text.EditRepositoryNode.Bookmark}"/>
|
||||
<ComboBox Grid.Column="1"
|
||||
BorderThickness="0"
|
||||
Height="28"
|
||||
ItemsSource="{x:Static m:Bookmarks.Supported}"
|
||||
SelectedIndex="{Binding Bookmark, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Height="20" VerticalAlignment="Center">
|
||||
<Path Width="12" Height="12"
|
||||
Fill="{Binding, Converter={x:Static c:BookmarkConverters.ToBrush}}"
|
||||
StrokeThickness="{Binding, Converter={x:Static c:BookmarkConverters.ToStrokeThickness}}"
|
||||
Stroke="{DynamicResource Brush.FG1}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Data="{StaticResource Icons.Bookmark}"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
10
src/Views/EditRepositoryNode.axaml.cs
Normal file
10
src/Views/EditRepositoryNode.axaml.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public partial class EditRepositoryNode : UserControl {
|
||||
public EditRepositoryNode() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/Views/Extensions.cs
Normal file
12
src/Views/Extensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Avalonia.Markup.Xaml;
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
public class IntExtension : MarkupExtension {
|
||||
public int Value { get; set; }
|
||||
public IntExtension(int value) { this.Value = value; }
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) {
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue