1 Commits
Author SHA1 Message Date
nikit3s b2ae91b5b3 Release v0.1.1 2026-09-27 15:25:46 +03:00
11 changed files with 315 additions and 91 deletions
+3
View File
@@ -3,6 +3,9 @@
local.json
profiles.json
# локальные ярлыки Windows
*.lnk
# ── рантайм ──────────────────────────────────────────────────────────
# сюда лаунчер генерирует config.json перед запуском
run/
+133 -6
View File
@@ -33,10 +33,12 @@ using System.Threading.Tasks;
using System.Web.Script.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
@@ -147,7 +149,7 @@ namespace SbLauncher
_ni = new System.Windows.Forms.NotifyIcon();
_ni.Icon = LoadIcon();
_ni.Text = "sb launcher";
_ni.Text = "SbLauncher";
_ni.Visible = true;
_ni.DoubleClick += delegate { Restore(); };
@@ -497,13 +499,12 @@ namespace SbLauncher
vm.SetTheme(themeArg != null ? themeArg : Theme.Dark);
vm.Load(paths);
vm.ShowSettings = settingsTab;
vm.AppendLog("демо-рендер интерфейса — sing-box не запускался");
vm.ClearLog();
vm.AppendLog("демонстрация интерфейса — sing-box не запускался");
ImageSource logo = LoadLogo();
Image li = w.FindName("LogoImg") as Image;
if (li != null && logo != null) li.Source = logo;
Image statusLogo = w.FindName("StatusLogoImg") as Image;
if (statusLogo != null && logo != null) statusLogo.Source = logo;
// немного «живых» данных, чтобы картинка была честной
int i = 0;
@@ -526,6 +527,14 @@ namespace SbLauncher
root2.UpdateLayout();
Pump(); // дать ItemsControl создать карточки
root2.UpdateLayout();
ToggleButton modeSwitch = w.FindName("WireguardSwitch") as ToggleButton;
if (modeSwitch != null) UpdateSwitch(modeSwitch, false);
ListBox serverList = w.FindName("ServerList") as ListBox;
FrameworkElement serverHost = w.FindName("ServerListHost") as FrameworkElement;
Border selection = w.FindName("ServerSelectionWindow") as Border;
if (serverList != null && serverHost != null && selection != null)
MoveServerSelection(serverList, serverHost, selection, false);
root2.UpdateLayout();
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)(ww * 1.5), (int)(hh * 1.5), 144, 144, PixelFormats.Pbgra32);
@@ -608,8 +617,6 @@ namespace SbLauncher
{
Image li = w.FindName("LogoImg") as Image;
if (li != null) li.Source = logo;
Image statusLogo = w.FindName("StatusLogoImg") as Image;
if (statusLogo != null) statusLogo.Source = logo;
try { w.Icon = logo; } catch { }
}
@@ -656,7 +663,127 @@ namespace SbLauncher
if (log != null)
log.TextChanged += delegate { log.ScrollToEnd(); };
WireSwitch(w);
WireServerSelection(w);
w.Loaded += delegate { vm.PingAll(); };
}
static void WireSwitch(Window w)
{
ToggleButton toggle = w.FindName("WireguardSwitch") as ToggleButton;
if (toggle == null) return;
// При загрузке положение выставляется сразу; последующие изменения
// идут от текущего положения, поэтому быстрые клики не дают скачка.
toggle.Loaded += delegate { UpdateSwitch(toggle, false); };
toggle.Checked += delegate { UpdateSwitch(toggle, true); };
toggle.Unchecked += delegate { UpdateSwitch(toggle, true); };
}
static void UpdateSwitch(ToggleButton toggle, bool animate)
{
if (!toggle.IsLoaded && animate) return;
toggle.ApplyTemplate();
Border active = toggle.Template.FindName("ActiveTrack", toggle) as Border;
System.Windows.Shapes.Ellipse thumb =
toggle.Template.FindName("Thumb", toggle) as System.Windows.Shapes.Ellipse;
TranslateTransform shift = thumb != null ? thumb.RenderTransform as TranslateTransform : null;
if (active == null || shift == null) return;
if (shift.IsFrozen)
{
shift = shift.Clone();
thumb.RenderTransform = shift;
}
double targetX = toggle.IsChecked == true ? 22 : 0;
double targetOpacity = toggle.IsChecked == true ? 1 : 0;
double fromX = shift.X, fromOpacity = active.Opacity;
shift.BeginAnimation(TranslateTransform.XProperty, null);
active.BeginAnimation(UIElement.OpacityProperty, null);
shift.X = targetX;
active.Opacity = targetOpacity;
if (!animate) return;
DoubleAnimation slide = new DoubleAnimation(fromX, targetX,
TimeSpan.FromMilliseconds(430));
slide.EasingFunction = new BackEase { Amplitude = 0.28, EasingMode = EasingMode.EaseOut };
slide.FillBehavior = FillBehavior.Stop;
shift.BeginAnimation(TranslateTransform.XProperty, slide);
DoubleAnimation color = new DoubleAnimation(fromOpacity, targetOpacity,
TimeSpan.FromMilliseconds(300));
color.EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut };
color.FillBehavior = FillBehavior.Stop;
active.BeginAnimation(UIElement.OpacityProperty, color);
}
static void WireServerSelection(Window w)
{
ListBox list = w.FindName("ServerList") as ListBox;
FrameworkElement host = w.FindName("ServerListHost") as FrameworkElement;
Border highlight = w.FindName("ServerSelectionWindow") as Border;
if (list == null || host == null || highlight == null) return;
bool selectionPending = false;
int selectionRevision = 0;
list.Loaded += delegate { MoveServerSelection(list, host, highlight, false); };
list.SelectionChanged += delegate
{
selectionPending = true;
int revision = ++selectionRevision;
if (list.SelectedItem != null) list.ScrollIntoView(list.SelectedItem);
list.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(delegate
{
if (revision != selectionRevision) return;
try
{
list.UpdateLayout();
MoveServerSelection(list, host, highlight, true);
}
finally { selectionPending = false; }
}));
};
list.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(
delegate { if (!selectionPending) MoveServerSelection(list, host, highlight, false); }));
list.SizeChanged += delegate { if (!selectionPending) MoveServerSelection(list, host, highlight, false); };
}
static void MoveServerSelection(ListBox list, FrameworkElement host,
Border highlight, bool animate)
{
ListBoxItem item = list.ItemContainerGenerator.ContainerFromItem(list.SelectedItem) as ListBoxItem;
if (item == null || item.ActualHeight <= 10)
{
highlight.Visibility = Visibility.Collapsed;
return;
}
Point p;
try { p = item.TransformToAncestor(host).Transform(new Point(0, 0)); }
catch (InvalidOperationException) { return; }
double height = item.ActualHeight - 10; // промежуток между карточками
if (p.Y + height < 0 || p.Y > host.ActualHeight)
{
highlight.Visibility = Visibility.Collapsed;
return;
}
bool wasVisible = highlight.Visibility == Visibility.Visible;
double fromY = Canvas.GetTop(highlight);
if (double.IsNaN(fromY)) fromY = p.Y;
highlight.BeginAnimation(Canvas.TopProperty, null);
Canvas.SetLeft(highlight, p.X);
Canvas.SetTop(highlight, p.Y);
highlight.Width = item.ActualWidth;
highlight.Height = height;
highlight.Visibility = Visibility.Visible;
if (!animate || !wasVisible || Math.Abs(fromY - p.Y) < 0.5) return;
DoubleAnimation slide = new DoubleAnimation(fromY, p.Y,
TimeSpan.FromMilliseconds(520));
slide.EasingFunction = new BackEase { Amplitude = 0.32, EasingMode = EasingMode.EaseOut };
slide.FillBehavior = FillBehavior.Stop;
highlight.BeginAnimation(Canvas.TopProperty, slide);
}
}
}
+8
View File
@@ -7,6 +7,14 @@
Пока нет записей.
## 0.1.1 — 2026-09-27
- Название окна и значка в трее приведено к `SbLauncher`; в шапке показывается версия сборки.
- Значок в блоке подключения заменён кнопкой питания.
- Кнопка очистки лога убрана из интерфейса.
- Переключатель WireGuard получил плавное движение с лёгкой отдачей; подсветка выбранного сервера скользит между карточками.
- Анимация подключения переделана: мягкий пульс во время ожидания и спокойное свечение при успешном подключении.
## 0.1.0 — 2026-09-26
- Первый рабочий прототип WPF-лаунчера с интерфейсом glass.
+1 -1
View File
@@ -1,4 +1,4 @@
# sb launcher · glass
# SbLauncher
Лаунчер для sing-box с интерфейсом WPF. Работает в Windows 10/11 x64 с
.NET Framework 4.x. Поддерживает импорт и выбор серверов, три режима
+142 -77
View File
@@ -1,7 +1,7 @@
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="sb launcher · glass preview" Width="1120" Height="790"
Title="SbLauncher" Width="1120" Height="790"
WindowStyle="None" AllowsTransparency="False" Background="{DynamicResource Bg}"
ResizeMode="NoResize" WindowStartupLocation="CenterScreen"
TextOptions.TextFormattingMode="Display"
@@ -338,19 +338,38 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="46" Height="26">
<Border x:Name="Track" CornerRadius="13" Background="{DynamicResource SwitchTrack}"
<Grid Width="52" Height="30">
<Border CornerRadius="15" Background="{DynamicResource SwitchTrack}"
BorderBrush="{DynamicResource Stroke}" BorderThickness="1"/>
<Ellipse x:Name="Thumb" Width="18" Height="18" Fill="{DynamicResource SwitchThumb}"
HorizontalAlignment="Left" Margin="4,0,0,0"/>
<Border x:Name="ActiveTrack" CornerRadius="15" Background="{DynamicResource Accent}"
BorderBrush="{DynamicResource Accent}" BorderThickness="1" Opacity="0"/>
<Ellipse x:Name="Thumb" Width="22" Height="22" Fill="{DynamicResource SwitchThumb}"
HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4,0,0,0">
<Ellipse.RenderTransform><TranslateTransform/></Ellipse.RenderTransform>
<Ellipse.Effect>
<DropShadowEffect Color="#50000000" BlurRadius="4" ShadowDepth="1" Opacity="0.35"/>
</Ellipse.Effect>
</Ellipse>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Track" Property="Background" Value="{DynamicResource Accent}"/>
<Setter TargetName="Track" Property="BorderBrush" Value="{DynamicResource Accent}"/>
<Setter TargetName="Thumb" Property="Fill" Value="{DynamicResource OnAccent}"/>
<Setter TargetName="Thumb" Property="HorizontalAlignment" Value="Right"/>
<Setter TargetName="Thumb" Property="Margin" Value="0,0,4,0"/>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Thumb" Property="Width" Value="26"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard FillBehavior="Stop">
<DoubleAnimation Storyboard.TargetName="Thumb" Storyboard.TargetProperty="Width"
From="22" To="26" Duration="0:0:0.12"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard FillBehavior="Stop">
<DoubleAnimation Storyboard.TargetName="Thumb" Storyboard.TargetProperty="Width"
From="26" To="22" Duration="0:0:0.18"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -419,10 +438,6 @@
<Trigger SourceName="Card" Property="IsMouseOver" Value="True">
<Setter TargetName="Card" Property="Background" Value="{DynamicResource CardHover}"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="Card" Property="Background" Value="{DynamicResource CardSel}"/>
<Setter TargetName="Card" Property="BorderBrush" Value="{DynamicResource Accent}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
@@ -618,12 +633,12 @@
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="23,0,0,0">
<Image x:Name="LogoImg" Width="31" Height="31" VerticalAlignment="Center"
RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="sb launcher" Foreground="{DynamicResource Text}"
<TextBlock Text="SbLauncher" Foreground="{DynamicResource Text}"
FontSize="15" FontWeight="SemiBold" Margin="11,0,0,0"
VerticalAlignment="Center"/>
<Border Background="{DynamicResource SoftBg}" BorderBrush="{DynamicResource Stroke}"
BorderThickness="1" CornerRadius="10" Margin="12,0,0,0" Padding="8,3">
<TextBlock Text="glass preview" Foreground="{DynamicResource Muted}"
<TextBlock Text="{Binding VersionText}" Foreground="{DynamicResource Muted}"
FontSize="10" VerticalAlignment="Center"/>
</Border>
</StackPanel>
@@ -662,31 +677,40 @@
Padding="10,4"/>
</Grid>
<ListBox Grid.Row="1" ItemsSource="{Binding Servers}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
ItemTemplate="{StaticResource ServerCard}"
Background="Transparent" BorderThickness="0"
Margin="12,0,12,14" Padding="0,0,4,0"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Grid x:Name="ServerListHost" Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="ServerList" ItemsSource="{Binding Servers}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
ItemTemplate="{StaticResource ServerCard}"
Background="Transparent" BorderThickness="0"
Margin="12,0,12,14" Padding="0,0,4,0"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Canvas IsHitTestVisible="False" ClipToBounds="True">
<Border x:Name="ServerSelectionWindow" Visibility="Collapsed"
CornerRadius="16" BorderBrush="{DynamicResource Accent}"
BorderThickness="1.5">
<Border CornerRadius="15" Background="{DynamicResource Accent}" Opacity="0.16"/>
</Border>
</Canvas>
</Grid>
</Grid>
</Border>
@@ -714,8 +738,8 @@
StrokeThickness="1"/>
<Ellipse Width="62" Height="62" Stroke="{DynamicResource HeroRingDim}"
StrokeThickness="2" Margin="2"/>
<!-- После удачного подключения — один спокойный световой импульс. -->
<Ellipse Width="56" Height="56" Fill="{DynamicResource Ok}"
<!-- Мягкое заполнение после подключения вместо резкой вспышки. -->
<Ellipse x:Name="ConnectionGlow" Width="58" Height="58" Fill="{DynamicResource Ok}"
RenderTransformOrigin="0.5,0.5" IsHitTestVisible="False">
<Ellipse.RenderTransform><ScaleTransform/></Ellipse.RenderTransform>
<Ellipse.Style>
@@ -723,15 +747,24 @@
<Setter Property="Opacity" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ConnectionPhase}" Value="connected">
<Setter Property="Opacity" Value="0.18"/>
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<Storyboard FillBehavior="Stop">
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.36" To="0" Duration="0:0:0.65"/>
From="0" To="0.18" Duration="0:0:0.55"/>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
From="0.8" To="1.2" Duration="0:0:0.65"/>
From="0.82" To="1" Duration="0:0:0.55">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude="0.28" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
From="0.8" To="1.2" Duration="0:0:0.65"/>
From="0.82" To="1" Duration="0:0:0.55">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude="0.28" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
@@ -740,44 +773,80 @@
</Style>
</Ellipse.Style>
</Ellipse>
<Ellipse Width="62" Height="62" Stroke="{DynamicResource HeroRing}"
StrokeThickness="3" StrokeDashArray="2 5" Margin="2"
<!-- Пока соединение меняется, ободок спокойно «дышит». -->
<Ellipse x:Name="ConnectionBreathRing" Width="58" Height="58" Stroke="{DynamicResource Accent}"
StrokeThickness="2.5" IsHitTestVisible="False"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform><RotateTransform/></Ellipse.RenderTransform>
<Ellipse.RenderTransform><ScaleTransform/></Ellipse.RenderTransform>
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Opacity" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ConnectionPhase}" Value="connecting">
<Setter Property="Opacity" Value="1"/>
<DataTrigger Binding="{Binding ConnectionInProgress}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="ConnectOrbit">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
From="0" To="360" Duration="0:0:2"/>
<BeginStoryboard x:Name="ConnectionBreath">
<Storyboard RepeatBehavior="Forever" AutoReverse="True">
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.25" To="0.72" Duration="0:0:0.9">
<DoubleAnimation.EasingFunction><SineEase EasingMode="EaseInOut"/></DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
From="0.9" To="1.08" Duration="0:0:0.9">
<DoubleAnimation.EasingFunction><SineEase EasingMode="EaseInOut"/></DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
From="0.9" To="1.08" Duration="0:0:0.9">
<DoubleAnimation.EasingFunction><SineEase EasingMode="EaseInOut"/></DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions><StopStoryboard BeginStoryboardName="ConnectOrbit"/></DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionPhase}" Value="disconnecting">
<Setter Property="Opacity" Value="1"/>
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="DisconnectOrbit">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
From="360" To="0" Duration="0:0:1.4"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions><StopStoryboard BeginStoryboardName="DisconnectOrbit"/></DataTrigger.ExitActions>
<DataTrigger.ExitActions><StopStoryboard BeginStoryboardName="ConnectionBreath"/></DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Image x:Name="StatusLogoImg" Width="38" Height="38"
RenderOptions.BitmapScalingMode="HighQuality"/>
<Button x:Name="BtnPower" Width="66" Height="66"
Command="{Binding ConnectCommand}" Cursor="Hand"
ToolTip="{Binding ConnectText}"
AutomationProperties.Name="{Binding ConnectText}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid Background="Transparent">
<Viewbox Width="32" Height="32">
<Canvas Width="32" Height="32">
<Path x:Name="PowerRing"
Data="M 9,8 C 5,10 4,14 4,17 C 4,24 9,29 16,29 C 23,29 28,24 28,17 C 28,14 27,10 23,8"
Stroke="{DynamicResource Text}" StrokeThickness="3"
StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
<Path x:Name="PowerStem" Data="M 16,3 L 16,17"
Stroke="{DynamicResource Text}" StrokeThickness="3"
StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
</Canvas>
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ConnectionPhase}" Value="connected">
<Setter TargetName="PowerRing" Property="Stroke" Value="{DynamicResource Ok}"/>
<Setter TargetName="PowerStem" Property="Stroke" Value="{DynamicResource Ok}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionPhase}" Value="connecting">
<Setter TargetName="PowerRing" Property="Stroke" Value="{DynamicResource Accent}"/>
<Setter TargetName="PowerStem" Property="Stroke" Value="{DynamicResource Accent}"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PowerRing" Property="Stroke" Value="{DynamicResource Accent}"/>
<Setter TargetName="PowerStem" Property="Stroke" Value="{DynamicResource Accent}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="PowerRing" Property="Stroke" Value="{DynamicResource AccentDim}"/>
<Setter TargetName="PowerStem" Property="Stroke" Value="{DynamicResource AccentDim}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="6,0,10,0">
@@ -808,7 +877,7 @@
<TextBlock Foreground="{DynamicResource Muted}" FontSize="11" Margin="0,3,0,0"
Text="вкл — VLESS поверх WireGuard · выкл — напрямую через сетевой адаптер"/>
</StackPanel>
<ToggleButton Style="{StaticResource Switch}"
<ToggleButton x:Name="WireguardSwitch" Style="{StaticResource Switch}"
IsChecked="{Binding WireguardOn, Mode=TwoWay}"
HorizontalAlignment="Right" VerticalAlignment="Center"/>
</Grid>
@@ -822,10 +891,6 @@
<Button x:Name="BtnTabSettings" Style="{StaticResource Tab}" Content="НАСТРОЙКИ"
Foreground="{Binding TabSettingsBrush}"/>
</StackPanel>
<Button Style="{StaticResource Soft}" Content="Очистить лог"
Command="{Binding ClearLogCommand}" Visibility="{Binding LogVis}"
HorizontalAlignment="Right" VerticalAlignment="Center"
ToolTip="Удалить записи из окна лога"/>
</Grid>
<!-- содержимое вкладки: два слоя, видим всегда ровно один -->
+3 -3
View File
@@ -1,6 +1,6 @@
using System.Reflection;
// Обновляйте вместе с CHANGELOG.md перед выпуском новой версии.
[assembly: AssemblyVersion("0.1.0.0")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyVersion("0.1.1.0")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1")]
+21 -1
View File
@@ -299,13 +299,33 @@ namespace SbLauncher
public ICommand DeleteSelectedCommand { get; private set; }
// ── статус ───────────────────────────────────────────────────────
public string VersionText
{
get
{
Assembly assembly = Assembly.GetExecutingAssembly();
AssemblyInformationalVersionAttribute version =
(AssemblyInformationalVersionAttribute)Attribute.GetCustomAttribute(
assembly, typeof(AssemblyInformationalVersionAttribute));
return "v" + (version != null ? version.InformationalVersion :
assembly.GetName().Version.ToString(3));
}
}
public string StatusTitle { get { return _statusTitle; } set { _statusTitle = value; Raise(); } }
public string StatusSub { get { return _statusSub; } set { _statusSub = value; Raise(); } }
public string StatusKey { set { _statusKey = value; Raise("StatusBrush"); } }
public Brush StatusBrush { get { return Theme.Res(_statusKey); } }
public string ConnectText { get { return _connectText; } set { _connectText = value; Raise(); } }
// Лёгкая анимация в XAML зависит только от фазы, без таймеров на UI-потоке.
public string ConnectionPhase { get { return _connectionPhase; } set { _connectionPhase = value; Raise(); } }
public string ConnectionPhase
{
get { return _connectionPhase; }
set { _connectionPhase = value; Raise(); Raise("ConnectionInProgress"); }
}
public bool ConnectionInProgress
{
get { return _connectionPhase == "connecting" || _connectionPhase == "disconnecting"; }
}
public bool IsConnected { get { return _runner.IsRunning; } }
public string LogText { get { return _log; } set { _log = value; Raise(); } }
public Brush LogBrush { get { return Theme.Res("LogText"); } }
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="0.1.0.0" name="SbLauncher"/>
<assemblyIdentity version="0.1.1.0" name="SbLauncher"/>
<!-- TUN + auto_route требуют администратора.
Цена: UAC при каждом запуске и автозапуск только через Планировщик
Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 352 KiB

+3 -2
View File
@@ -26,10 +26,11 @@ try {
}
$tag = 'v' + $Version
$tagCommit = & git rev-list -n 1 $tag 2>$null
& git show-ref --verify --quiet ("refs/tags/" + $tag)
if ($LASTEXITCODE -eq 0) {
$tagCommit = (& git rev-list -n 1 $tag).Trim()
$headCommit = (& git rev-parse HEAD).Trim()
if ($tagCommit.Trim() -ne $headCommit) {
if ($tagCommit -ne $headCommit) {
throw "Тег $tag уже указывает на другой коммит. Выберите новую версию."
}
}