mirror of
https://github.com/2dust/v2rayN.git
synced 2026-11-06 04:24:59 +03:00
Fix UI (#10527)
* Revert "Cache selected views in SimpleViewLocator"
This reverts commit 8ebbd7f4f8.
* Fix UI
* Optimize Styles
* Code clean
This commit is contained in:
Generated
+28
-28
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
@@ -222,6 +222,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enter a valid IPv6 CIDR range with at least two addresses, for example fc00::/18. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string FillCorrectFakeIPv6Range {
|
||||
get {
|
||||
return ResourceManager.GetString("FillCorrectFakeIPv6Range", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter the correct port format. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3240,6 +3249,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 IPv6 FakeIP 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFakeIPv6 {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFakeIPv6", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Return fake IPv6 addresses as well. Requires FakeIP to be enabled and AAAA queries to be allowed. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFakeIPv6Tips {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFakeIPv6Tips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -5444,32 +5471,5 @@ namespace ServiceLib.Resx {
|
||||
return ResourceManager.GetString("UpgradeAppNotExistTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// IPv6 FakeIP
|
||||
/// </summary>
|
||||
public static string TbFakeIPv6 {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFakeIPv6", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return fake IPv6 addresses as well. Requires FakeIP to be enabled and AAAA queries to be allowed.
|
||||
/// </summary>
|
||||
public static string TbFakeIPv6Tips {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFakeIPv6Tips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enter a valid IPv6 CIDR range with at least two addresses, for example fc00::/18.
|
||||
/// </summary>
|
||||
public static string FillCorrectFakeIPv6Range {
|
||||
get {
|
||||
return ResourceManager.GetString("FillCorrectFakeIPv6Range", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,7 +1153,7 @@
|
||||
<value>ACK aşkarlaması mümkün olmadıqda ehtiyat gecikmə (məsələn, 500ms). Yalnız sing-box. Boş = standart.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Aralıq formatı yanlışdır. 'başlanğıc-son' formatından istifadə edin (məsələn, 50-100).</value>
|
||||
<value>Aralıq formatı yanlışdır. 'başlanğıc-son' formatından istifadə edin (məsələn, 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>sing-box üçün keş faylını aktiv et (qayda dəsti faylları)</value>
|
||||
@@ -1453,7 +1453,7 @@
|
||||
<value>SVCB və HTTPS sorğularını blokla</value>
|
||||
</data>
|
||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS hostları: (hər sətirdə "domain1 ip1 ip2")</value>
|
||||
<value>DNS hostları: (hər sətirdə "domain1 ip1 ip2")</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>Əsas DNS parametrləri</value>
|
||||
@@ -1558,25 +1558,25 @@
|
||||
<value>Ehtiyat seçim</value>
|
||||
</data>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>'{0}' nüvəsi '{1}' şəbəkə növünü dəstəkləmir</value>
|
||||
<value>'{0}' nüvəsi '{1}' şəbəkə növünü dəstəkləmir</value>
|
||||
</data>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>'{0}' nüvəsi '{1}' protokolunu '{2}' ötürmə protokolu ilə dəstəkləmir</value>
|
||||
</data>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>'{0}' nüvəsi '{1}' protokolunu dəstəkləmir</value>
|
||||
<value>'{0}' nüvəsi '{1}' protokolunu dəstəkləmir</value>
|
||||
</data>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>{0} xassəsi yanlışdır, yoxlayın</value>
|
||||
</data>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>'{0}' protokolu dəstəklənmir</value>
|
||||
<value>'{0}' protokolu dəstəklənmir</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>Sistemdə sistem paneli funksiyası yoxdursa, bunu aktiv etməyin</value>
|
||||
</data>
|
||||
<data name="TbRuleTypeTips" xml:space="preserve">
|
||||
<value>Marşrutlaşdırma və DNS üçün ayrı qaydalar təyin edə və ya hər ikisinə tətbiq etmək üçün "ALL" seçə bilərsiniz</value>
|
||||
<value>Marşrutlaşdırma və DNS üçün ayrı qaydalar təyin edə və ya hər ikisinə tətbiq etmək üçün "ALL" seçə bilərsiniz</value>
|
||||
</data>
|
||||
<data name="TbRuleType" xml:space="preserve">
|
||||
<value>Qayda növü</value>
|
||||
@@ -1654,10 +1654,10 @@ Təyin edildikdə sertifikat sabitlənəcək və "Təhlükəsiz olmayan əlaqəy
|
||||
<value>Standart olaraq yalnız marşrutlaşdırma zamanı həll üçün çağırılır; uzaq serverin bu DNS-ə çata bildiyinə əmin olun</value>
|
||||
</data>
|
||||
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||
<value>Təyin edilməyibsə və ya "AsIs" seçilibsə, DNS həlli sistem DNS-indən istifadə edir; əks halda daxili DNS modulu istifadə olunur.</value>
|
||||
<value>Təyin edilməyibsə və ya "AsIs" seçilibsə, DNS həlli sistem DNS-indən istifadə edir; əks halda daxili DNS modulu istifadə olunur.</value>
|
||||
</data>
|
||||
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||
<value>Təyin edilməyibsə və ya "AsIs" seçilibsə, DNS həlli uzaq serverin DNS-i tərəfindən aparılır; əks halda daxili DNS modulu istifadə olunur.</value>
|
||||
<value>Təyin edilməyibsə və ya "AsIs" seçilibsə, DNS həlli uzaq serverin DNS-i tərəfindən aparılır; əks halda daxili DNS modulu istifadə olunur.</value>
|
||||
</data>
|
||||
<data name="TbHopInt7" xml:space="preserve">
|
||||
<value>Port dəyişmə intervalı</value>
|
||||
@@ -1891,7 +1891,7 @@ Təyin edildikdə sertifikat sabitlənəcək və "Təhlükəsiz olmayan əlaqəy
|
||||
<value>HTTP başlıqları (JSON)</value>
|
||||
</data>
|
||||
<data name="SubRequestHeadersTips" xml:space="preserve">
|
||||
<value>İstəyə bağlıdır. Nümunə: {"X-hwid": "my_device"}. Dəyərlər abunəlik çevirmə xidməti də daxil olmaqla, bu qrupdakı bütün URL-lər üçün standart başlıqları əvəz edir.</value>
|
||||
<value>İstəyə bağlıdır. Nümunə: {"X-hwid": "my_device"}. Dəyərlər abunəlik çevirmə xidməti də daxil olmaqla, bu qrupdakı bütün URL-lər üçün standart başlıqları əvəz edir.</value>
|
||||
</data>
|
||||
<data name="SubRequestHeadersInvalid" xml:space="preserve">
|
||||
<value>HTTP başlıqları yanlışdır. Unikal başlıq adları və sətir dəyərləri olan JSON obyektindən istifadə edin. Başlıq adlarında və dəyərlərində sətirsonu simvolları olmamalıdır.</value>
|
||||
@@ -1899,4 +1899,4 @@ Təyin edildikdə sertifikat sabitlənəcək və "Təhlükəsiz olmayan əlaqəy
|
||||
<data name="TbSortingProcess" xml:space="preserve">
|
||||
<value>Proses</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -20,17 +20,9 @@
|
||||
<Application.Styles>
|
||||
<semi:SemiTheme />
|
||||
<semi:AvaloniaEditSemiTheme />
|
||||
<StyleInclude Source="Assets/GlobalStyles.axaml" />
|
||||
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
|
||||
<dialogHost:DialogHostStyles />
|
||||
|
||||
<Style Selector="dialogHost|DialogHost">
|
||||
<Setter Property="OverlayBackground" Value="{DynamicResource SemiColorOverlayBackground}" />
|
||||
<Setter Property="Background" Value="{DynamicResource SemiColorFill0}" />
|
||||
|
||||
<Setter Property="dialogHost:DialogHostStyle.BoxShadow" Value="{DynamicResource SemiShadowElevated}" />
|
||||
<Setter Property="dialogHost:DialogHostStyle.CornerRadius" Value="6" />
|
||||
</Style>
|
||||
<StyleInclude Source="Assets/GlobalStyles.axaml" />
|
||||
</Application.Styles>
|
||||
|
||||
<TrayIcon.Icons>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<!-- Add Controls for Previewer Here -->
|
||||
@@ -6,7 +10,7 @@
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Style Selector="DataGrid">
|
||||
<Setter Property="RowHeight" Value="24" />
|
||||
<Setter Property="RowHeight" Value="{DynamicResource DataGridRowHeight}" />
|
||||
<Setter Property="ScrollViewer.AllowAutoHide" Value="False" />
|
||||
</Style>
|
||||
|
||||
@@ -28,6 +32,10 @@
|
||||
<Setter Property="Theme" Value="{StaticResource LineTabControl}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TabStrip">
|
||||
<Setter Property="Theme" Value="{StaticResource LineTabStrip}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.IconButton">
|
||||
<Setter Property="Width" Value="{StaticResource IconButtonWidth}" />
|
||||
<Setter Property="Height" Value="{StaticResource IconButtonHeight}" />
|
||||
@@ -35,4 +43,16 @@
|
||||
<Setter Property="Theme" Value="{DynamicResource BorderlessButton}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="dialogHost|DialogHost">
|
||||
<Setter Property="OverlayBackground" Value="{DynamicResource SemiColorOverlayBackground}" />
|
||||
<Setter Property="Background" Value="{DynamicResource SemiColorFill0}" />
|
||||
|
||||
<Setter Property="dialogHost:DialogHostStyle.BoxShadow" Value="{DynamicResource SemiShadowElevated}" />
|
||||
<Setter Property="dialogHost:DialogHostStyle.CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock, TextBox, SelectableTextBlock, Button, ToggleButton, ListBoxItem, MenuItem, ComboBoxItem, TabItem, TabStripItem, DataGridCell, HeaderedContentControl, avaloniaEdit|TextEditor">
|
||||
<Setter Property="TextElement.FontFamily" Value="{DynamicResource AppFontFamily}" />
|
||||
<Setter Property="TextElement.FontSize" Value="{DynamicResource AppFontSize}" />
|
||||
</Style>
|
||||
</Styles>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Avalonia.Controls.Templates;
|
||||
using v2rayN.Desktop.ViewModels;
|
||||
using v2rayN.Desktop.Views;
|
||||
@@ -10,7 +9,6 @@ public class SimpleViewLocator : IDataTemplate
|
||||
private static readonly Lazy<SimpleViewLocator> _instance = new(() => new SimpleViewLocator());
|
||||
|
||||
private readonly Dictionary<Type, Func<Control?>> _locator = new();
|
||||
private readonly ConditionalWeakTable<object, Control> _cachedViews = new();
|
||||
|
||||
private SimpleViewLocator()
|
||||
{
|
||||
@@ -47,18 +45,9 @@ public class SimpleViewLocator : IDataTemplate
|
||||
return new TextBlock { Text = "No VM provided" };
|
||||
}
|
||||
|
||||
var vmType = data.GetType();
|
||||
if (!_locator.TryGetValue(vmType, out var factory))
|
||||
{
|
||||
return new TextBlock { Text = $"VM Not Registered: {vmType}" };
|
||||
}
|
||||
_locator.TryGetValue(data.GetType(), out var factory);
|
||||
|
||||
if (ShouldCache(vmType))
|
||||
{
|
||||
return _cachedViews.GetValue(data, _ => CreateView(factory, vmType));
|
||||
}
|
||||
|
||||
return CreateView(factory, vmType);
|
||||
return factory?.Invoke() ?? new TextBlock { Text = $"VM Not Registered: {data.GetType()}" };
|
||||
}
|
||||
|
||||
public bool Match(object? data)
|
||||
@@ -66,19 +55,6 @@ public class SimpleViewLocator : IDataTemplate
|
||||
return data is MyReactiveObject;
|
||||
}
|
||||
|
||||
private static bool ShouldCache(Type vmType)
|
||||
{
|
||||
return vmType == typeof(MsgViewModel)
|
||||
|| vmType == typeof(ClashProxiesViewModel)
|
||||
|| vmType == typeof(ClashConnectionsViewModel)
|
||||
|| vmType == typeof(ProfilesViewModel);
|
||||
}
|
||||
|
||||
private static Control CreateView(Func<Control?> factory, Type vmType)
|
||||
{
|
||||
return factory.Invoke() ?? new TextBlock { Text = $"View Factory Returned Null: {vmType}" };
|
||||
}
|
||||
|
||||
public void RegisterViewFactory<TViewModel>(Func<Control> factory) where TViewModel : class
|
||||
{
|
||||
_locator.Add(typeof(TViewModel), factory);
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using AvaloniaEdit;
|
||||
using Semi.Avalonia;
|
||||
|
||||
namespace v2rayN.Desktop.ViewModels;
|
||||
|
||||
public partial class ThemeSettingViewModel : MyReactiveObject
|
||||
{
|
||||
private Style? _fontSizeStyle;
|
||||
private Style? _fontSizeExStyle;
|
||||
private Style? _fontFamilyStyle;
|
||||
|
||||
[Reactive] public partial string CurrentTheme { get; set; }
|
||||
|
||||
[Reactive] public partial int CurrentFontSize { get; set; }
|
||||
@@ -102,26 +95,7 @@ public partial class ThemeSettingViewModel : MyReactiveObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var style = new Style(x => Selectors.Or(
|
||||
x.OfType<Button>(),
|
||||
x.OfType<TextBox>(),
|
||||
x.OfType<TextBlock>(),
|
||||
x.OfType<SelectableTextBlock>(),
|
||||
x.OfType<Menu>(),
|
||||
x.OfType<ContextMenu>(),
|
||||
x.OfType<DataGridRow>(),
|
||||
x.OfType<ListBoxItem>(),
|
||||
x.OfType<HeaderedContentControl>(),
|
||||
x.OfType<TextEditor>()
|
||||
));
|
||||
style.Add(new Setter
|
||||
{
|
||||
Property = TemplatedControl.FontSizeProperty,
|
||||
Value = size,
|
||||
});
|
||||
ReplaceStyle(ref _fontSizeStyle, style);
|
||||
|
||||
Application.Current!.Resources["AppFontSize"] = size;
|
||||
ModifyFontSizeEx(size);
|
||||
}
|
||||
|
||||
@@ -129,10 +103,7 @@ public partial class ThemeSettingViewModel : MyReactiveObject
|
||||
{
|
||||
//DataGrid
|
||||
var rowHeight = 20 + (size / 2);
|
||||
|
||||
var style = new Style(x => x.OfType<DataGrid>());
|
||||
style.Add(new Setter(DataGrid.RowHeightProperty, rowHeight));
|
||||
ReplaceStyle(ref _fontSizeExStyle, style);
|
||||
Application.Current!.Resources["DataGridRowHeight"] = rowHeight;
|
||||
}
|
||||
|
||||
private void ModifyFontFamily()
|
||||
@@ -145,46 +116,11 @@ public partial class ThemeSettingViewModel : MyReactiveObject
|
||||
|
||||
try
|
||||
{
|
||||
var style = new Style(x => Selectors.Or(
|
||||
x.OfType<Button>(),
|
||||
x.OfType<TextBox>(),
|
||||
x.OfType<TextBlock>(),
|
||||
x.OfType<SelectableTextBlock>(),
|
||||
x.OfType<Menu>(),
|
||||
x.OfType<ContextMenu>(),
|
||||
x.OfType<DataGridRow>(),
|
||||
x.OfType<ListBoxItem>(),
|
||||
x.OfType<HeaderedContentControl>(),
|
||||
x.OfType<WindowNotificationManager>(),
|
||||
x.OfType<TextEditor>()
|
||||
));
|
||||
style.Add(new Setter
|
||||
{
|
||||
Property = TemplatedControl.FontFamilyProperty,
|
||||
Value = new FontFamily(currentFontFamily),
|
||||
});
|
||||
ReplaceStyle(ref _fontFamilyStyle, style);
|
||||
Application.Current!.Resources["AppFontFamily"] = currentFontFamily;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ModifyFontFamily", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReplaceStyle(ref Style? oldStyle, Style newStyle)
|
||||
{
|
||||
var app = Application.Current;
|
||||
if (app is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldStyle is not null)
|
||||
{
|
||||
app.Styles.Remove(oldStyle);
|
||||
}
|
||||
|
||||
oldStyle = newStyle;
|
||||
app.Styles.Add(newStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<ae:TextEditor
|
||||
Name="Editor"
|
||||
FontFamily="Cascadia Code,Consolas,Monospace"
|
||||
FontSize="14"
|
||||
ShowLineNumbers="True">
|
||||
<ae:TextEditor.ContextMenu>
|
||||
<ContextMenu>
|
||||
|
||||
@@ -18,15 +18,6 @@
|
||||
ShowInTaskbar="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Window.Styles>
|
||||
<!-- Keep the log selected; hide only the strip when it has no other tabs. -->
|
||||
<Style Selector="TabControl.logPane /template/ ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="IsVisible" Value="{Binding ShowClashUI}" />
|
||||
</Style>
|
||||
<Style Selector="TabControl.logPane /template/ Border#PART_BorderSeparator">
|
||||
<Setter Property="IsVisible" Value="{Binding ShowClashUI}" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
<dialogHost:DialogHost CloseOnClickAway="True" DisableOpeningAnimation="True">
|
||||
<DockPanel>
|
||||
<DockPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Top">
|
||||
@@ -123,47 +114,26 @@
|
||||
|
||||
<ContentControl x:Name="contentStatusBarView" DockPanel.Dock="Bottom" />
|
||||
|
||||
<Grid>
|
||||
<Grid
|
||||
x:Name="gridMain"
|
||||
ColumnDefinitions="1*,10,1*"
|
||||
IsVisible="False">
|
||||
<ContentControl x:Name="tabProfiles" Grid.Column="0" />
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
|
||||
<TabControl
|
||||
x:Name="tabMain"
|
||||
Grid.Column="2"
|
||||
Classes="logPane"
|
||||
HorizontalContentAlignment="Left">
|
||||
<TabItem x:Name="tabMsgView" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
|
||||
<TabItem x:Name="tabClashProxies" Header="{x:Static resx:ResUI.TbProxies}" />
|
||||
<TabItem x:Name="tabClashConnections" Header="{x:Static resx:ResUI.TbConnections}" />
|
||||
</TabControl>
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridMain1"
|
||||
IsVisible="False"
|
||||
RowDefinitions="1*,10,1*">
|
||||
<ContentControl x:Name="tabProfiles1" Grid.Row="0" />
|
||||
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" />
|
||||
<TabControl
|
||||
x:Name="tabMain1"
|
||||
Grid.Row="2"
|
||||
Classes="logPane"
|
||||
TabStripPlacement="Left">
|
||||
<TabItem x:Name="tabMsgView1" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
|
||||
<TabItem x:Name="tabClashProxies1" Header="{x:Static resx:ResUI.TbProxies}" />
|
||||
<TabItem x:Name="tabClashConnections1" Header="{x:Static resx:ResUI.TbConnections}" />
|
||||
</TabControl>
|
||||
</Grid>
|
||||
<Grid x:Name="gridMain2" IsVisible="False">
|
||||
<TabControl x:Name="tabMain2" TabStripPlacement="Left">
|
||||
<TabItem x:Name="tabProfiles2" Header="{x:Static resx:ResUI.menuServers}" />
|
||||
<TabItem x:Name="tabMsgView2" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
|
||||
<TabItem x:Name="tabClashProxies2" Header="{x:Static resx:ResUI.TbProxies}" />
|
||||
<TabItem x:Name="tabClashConnections2" Header="{x:Static resx:ResUI.TbConnections}" />
|
||||
</TabControl>
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridMain"
|
||||
ColumnDefinitions="1*,10,1*"
|
||||
IsVisible="False">
|
||||
<ContentControl x:Name="tabProfiles" Grid.Column="0" />
|
||||
<GridSplitter
|
||||
x:Name="gridSplitter"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<DockPanel x:Name="tabDockPanel" Grid.Column="2">
|
||||
<TabStrip
|
||||
x:Name="tabStrip"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
DockPanel.Dock="Top">
|
||||
<TabStripItem Content="{x:Static resx:ResUI.MsgInformationTitle}" />
|
||||
<TabStripItem Content="{x:Static resx:ResUI.TbProxies}" />
|
||||
<TabStripItem Content="{x:Static resx:ResUI.TbConnections}" />
|
||||
</TabStrip>
|
||||
<ContentControl x:Name="tabContent" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</dialogHost:DialogHost>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Layout;
|
||||
using DialogHostAvalonia;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
@@ -9,12 +12,16 @@ namespace v2rayN.Desktop.Views;
|
||||
public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
private readonly SingleReplaceableDisposable _layoutBindingsDisposable = new();
|
||||
private readonly WindowNotificationManager? _manager;
|
||||
private CheckUpdateView? _checkUpdateView;
|
||||
private BackupAndRestoreView? _backupAndRestoreView;
|
||||
private bool _blCloseByUser = false;
|
||||
|
||||
private MsgView? _msgView;
|
||||
private ProfilesView? _profilesView;
|
||||
private ClashConnectionsView? _clashConnectionsView;
|
||||
private ClashProxiesView? _clashProxiesView;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -81,11 +88,12 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.StatusBarViewModel, v => v.contentStatusBarView.Content).DisposeWith(disposables);
|
||||
|
||||
_layoutBindingsDisposable.DisposeWith(disposables);
|
||||
|
||||
this.WhenAnyValue(v => v.ViewModel.MainGirdOrientation)
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(UpdateLayout)
|
||||
.Subscribe(orientation =>
|
||||
{
|
||||
UpdateLayout(orientation, disposables);
|
||||
})
|
||||
.DisposeWith(disposables);
|
||||
|
||||
ViewModel.ReadTextFromClipboardInteraction.RegisterHandler(async interaction =>
|
||||
@@ -367,7 +375,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
{
|
||||
ShowHideWindow(false);
|
||||
}
|
||||
RestoreUI();
|
||||
}
|
||||
|
||||
private void RestoreUI()
|
||||
@@ -381,8 +388,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
}
|
||||
else if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Vertical)
|
||||
{
|
||||
gridMain1.RowDefinitions[0].Height = new GridLength(_config.UiItem.MainGirdHeight1, GridUnitType.Star);
|
||||
gridMain1.RowDefinitions[2].Height = new GridLength(_config.UiItem.MainGirdHeight2, GridUnitType.Star);
|
||||
gridMain.RowDefinitions[0].Height = new GridLength(_config.UiItem.MainGirdHeight1, GridUnitType.Star);
|
||||
gridMain.RowDefinitions[2].Height = new GridLength(_config.UiItem.MainGirdHeight2, GridUnitType.Star);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,76 +404,178 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
}
|
||||
else if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Vertical)
|
||||
{
|
||||
ConfigHandler.SaveMainGirdHeight(_config, gridMain1.RowDefinitions[0].ActualHeight, gridMain1.RowDefinitions[2].ActualHeight);
|
||||
ConfigHandler.SaveMainGirdHeight(_config, gridMain.RowDefinitions[0].ActualHeight, gridMain.RowDefinitions[2].ActualHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLayout(EGirdOrientation orientation)
|
||||
private void UpdateLayout(EGirdOrientation orientation, MultipleDisposable disposable)
|
||||
{
|
||||
var currentLayoutDisposables = new MultipleDisposable();
|
||||
_layoutBindingsDisposable.Create(currentLayoutDisposables);
|
||||
|
||||
ClearLayoutContent();
|
||||
|
||||
gridMain.IsVisible = orientation == EGirdOrientation.Horizontal;
|
||||
gridMain1.IsVisible = orientation == EGirdOrientation.Vertical;
|
||||
gridMain2.IsVisible = orientation == EGirdOrientation.Tab;
|
||||
gridMain.IsVisible = true;
|
||||
_msgView ??= new MsgView
|
||||
{
|
||||
DataContext = ViewModel?.MsgViewModel,
|
||||
};
|
||||
_profilesView ??= new ProfilesView
|
||||
{
|
||||
DataContext = ViewModel?.ProfilesViewModel,
|
||||
};
|
||||
_clashProxiesView ??= new ClashProxiesView
|
||||
{
|
||||
DataContext = ViewModel?.ClashProxiesViewModel,
|
||||
};
|
||||
_clashConnectionsView ??= new ClashConnectionsView
|
||||
{
|
||||
DataContext = ViewModel?.ClashConnectionsViewModel,
|
||||
};
|
||||
|
||||
switch (orientation)
|
||||
{
|
||||
case EGirdOrientation.Horizontal:
|
||||
this.OneWayBind(ViewModel, vm => vm.ProfilesViewModel, v => v.tabProfiles.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
||||
default:
|
||||
gridMain.ColumnDefinitions = new ColumnDefinitions("1*,10,1*");
|
||||
gridMain.RowDefinitions = new RowDefinitions("1*");
|
||||
Grid.SetColumn(tabProfiles, 0);
|
||||
Grid.SetColumn(gridSplitter, 1);
|
||||
Grid.SetColumn(tabDockPanel, 2);
|
||||
Grid.SetRow(tabProfiles, 0);
|
||||
Grid.SetRow(gridSplitter, 0);
|
||||
Grid.SetRow(tabDockPanel, 0);
|
||||
DockPanel.SetDock(tabStrip, Dock.Top);
|
||||
tabProfiles.Content = _profilesView;
|
||||
gridSplitter.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
tabStrip.ItemsPanel = new FuncTemplate<Panel?>(() => new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
});
|
||||
this.WhenAnyValue(x => x.ViewModel!.ShowClashUI)
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(show =>
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
tabStrip.Items.Clear();
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.MsgInformationTitle });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.TbProxies });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.TbConnections });
|
||||
tabStrip.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tabStrip.IsVisible = false;
|
||||
tabStrip.Items.Clear();
|
||||
}
|
||||
}).DisposeWith(disposable);
|
||||
tabStrip.GetObservable(SelectingItemsControl.SelectedIndexProperty)
|
||||
.Subscribe(index =>
|
||||
{
|
||||
tabContent.Content = index switch
|
||||
{
|
||||
1 => _clashProxiesView,
|
||||
2 => _clashConnectionsView,
|
||||
_ => _msgView,
|
||||
};
|
||||
}).DisposeWith(disposable);
|
||||
break;
|
||||
|
||||
case EGirdOrientation.Vertical:
|
||||
this.OneWayBind(ViewModel, vm => vm.ProfilesViewModel, v => v.tabProfiles1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies1.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections1.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain1.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
||||
gridMain.RowDefinitions = new RowDefinitions("1*,10,1*");
|
||||
gridMain.ColumnDefinitions = new ColumnDefinitions("1*");
|
||||
Grid.SetRow(tabProfiles, 0);
|
||||
Grid.SetRow(gridSplitter, 1);
|
||||
Grid.SetRow(tabDockPanel, 2);
|
||||
Grid.SetColumn(tabProfiles, 0);
|
||||
Grid.SetColumn(gridSplitter, 0);
|
||||
Grid.SetColumn(tabDockPanel, 0);
|
||||
DockPanel.SetDock(tabStrip, Dock.Left);
|
||||
tabProfiles.Content = _profilesView;
|
||||
gridSplitter.VerticalAlignment = VerticalAlignment.Stretch;
|
||||
tabStrip.ItemsPanel = new FuncTemplate<Panel?>(() => new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
});
|
||||
this.WhenAnyValue(x => x.ViewModel!.ShowClashUI)
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(show =>
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
tabStrip.Items.Clear();
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.MsgInformationTitle });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.TbProxies });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.TbConnections });
|
||||
tabStrip.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tabStrip.IsVisible = false;
|
||||
tabStrip.Items.Clear();
|
||||
}
|
||||
}).DisposeWith(disposable);
|
||||
tabStrip.GetObservable(SelectingItemsControl.SelectedIndexProperty)
|
||||
.Subscribe(index =>
|
||||
{
|
||||
tabContent.Content = index switch
|
||||
{
|
||||
1 => _clashProxiesView,
|
||||
2 => _clashConnectionsView,
|
||||
_ => _msgView,
|
||||
};
|
||||
}).DisposeWith(disposable);
|
||||
break;
|
||||
|
||||
case EGirdOrientation.Tab:
|
||||
default:
|
||||
this.OneWayBind(ViewModel, vm => vm.ProfilesViewModel, v => v.tabProfiles2.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView2.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies2.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections2.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies2.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections2.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
||||
gridMain.RowDefinitions = new RowDefinitions("1*");
|
||||
gridMain.ColumnDefinitions = new ColumnDefinitions("1*");
|
||||
Grid.SetColumn(tabProfiles, 0);
|
||||
Grid.SetColumn(gridSplitter, 0);
|
||||
Grid.SetColumn(tabDockPanel, 0);
|
||||
Grid.SetRow(tabProfiles, 0);
|
||||
Grid.SetRow(gridSplitter, 0);
|
||||
Grid.SetRow(tabDockPanel, 0);
|
||||
DockPanel.SetDock(tabStrip, Dock.Left);
|
||||
tabProfiles.Content = null;
|
||||
gridSplitter.VerticalAlignment = VerticalAlignment.Stretch;
|
||||
tabStrip.ItemsPanel = new FuncTemplate<Panel?>(() => new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
});
|
||||
this.WhenAnyValue(x => x.ViewModel!.ShowClashUI)
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(show =>
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
tabStrip.Items.Clear();
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.menuServers });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.MsgInformationTitle });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.TbProxies });
|
||||
tabStrip.Items.Add(new TabStripItem { Content = ResUI.TbConnections });
|
||||
tabStrip.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tabStrip.IsVisible = false;
|
||||
tabStrip.Items.Clear();
|
||||
}
|
||||
}).DisposeWith(disposable);
|
||||
tabStrip.GetObservable(SelectingItemsControl.SelectedIndexProperty)
|
||||
.Subscribe(index =>
|
||||
{
|
||||
tabContent.Content = index switch
|
||||
{
|
||||
1 => _msgView,
|
||||
2 => _clashProxiesView,
|
||||
3 => _clashConnectionsView,
|
||||
_ => _profilesView,
|
||||
};
|
||||
}).DisposeWith(disposable);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
RestoreUI();
|
||||
}
|
||||
|
||||
private void ClearLayoutContent()
|
||||
{
|
||||
tabProfiles.Content = null;
|
||||
tabMsgView.Content = null;
|
||||
tabClashProxies.Content = null;
|
||||
tabClashConnections.Content = null;
|
||||
|
||||
tabProfiles1.Content = null;
|
||||
tabMsgView1.Content = null;
|
||||
tabClashProxies1.Content = null;
|
||||
tabClashConnections1.Content = null;
|
||||
|
||||
tabProfiles2.Content = null;
|
||||
tabMsgView2.Content = null;
|
||||
tabClashProxies2.Content = null;
|
||||
tabClashConnections2.Content = null;
|
||||
}
|
||||
|
||||
private void AddHelpMenuItem()
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<TextBlock
|
||||
x:Name="txtMessage"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
LineHeight="22"
|
||||
TextWrapping="Wrap" />
|
||||
</ScrollViewer>
|
||||
|
||||
Reference in New Issue
Block a user