Release v0.1.1

This commit is contained in:
nikit3s
2026-09-27 15:25:46 +03:00
parent 7ac8c78d8b
commit b2ae91b5b3
11 changed files with 315 additions and 91 deletions
+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);
}
}
}