* Revert "Cache selected views in SimpleViewLocator"

This reverts commit 8ebbd7f4f8.

* Fix UI

* Optimize Styles

* Code clean
This commit is contained in:
DHR60
2026-11-05 02:08:04 +00:00
committed by GitHub
parent 9a029a3f7e
commit 66546700b0
10 changed files with 252 additions and 251 deletions
+28 -28
View File
@@ -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);
}
}
}
}
+10 -10
View File
@@ -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. &apos;başlanğıc-son&apos; 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ə &quot;domain1 ip1 ip2&quot;)</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>&apos;{0}&apos; nüvəsi &apos;{1}&apos; şə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>&apos;{0}&apos; nüvəsi &apos;{1}&apos; 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>&apos;{0}&apos; 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 &quot;ALL&quot; 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 &quot;AsIs&quot; 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 &quot;AsIs&quot; 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ə: {&quot;X-hwid&quot;: &quot;my_device&quot;}. 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>
+1 -9
View File
@@ -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>
+20 -50
View File
@@ -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>
+166 -57
View File
@@ -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>