初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
using System.Windows;
|
||||
using PCL.Core.UI;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public static class HintService
|
||||
{
|
||||
private struct HintMessage
|
||||
{
|
||||
public string Text;
|
||||
public HintType Type;
|
||||
public bool Log;
|
||||
}
|
||||
|
||||
private static ModBase.SafeList<HintMessage> HintWaiting
|
||||
{
|
||||
get => field ??= new ModBase.SafeList<HintMessage>();
|
||||
set;
|
||||
}
|
||||
|
||||
public static void Hint(string? text, HintType type = HintType.Info, bool log = true)
|
||||
{
|
||||
var normalized = (text ?? "").Replace("\r\n", " ").Replace("\r", " ").Replace("\n", " ");
|
||||
if (HintWaiting.Any(h => h.Text == normalized && h.Type == type)) return;
|
||||
HintWaiting.Add(new HintMessage { Text = normalized, Type = type, Log = log });
|
||||
}
|
||||
|
||||
public static void HintWrapper_OnShow(string message, HintTheme messageTheme)
|
||||
{
|
||||
var hintType = messageTheme switch
|
||||
{
|
||||
HintTheme.Success => HintType.Success,
|
||||
HintTheme.Error => HintType.Error,
|
||||
HintTheme.Warning => HintType.Warning,
|
||||
_ => HintType.Info
|
||||
};
|
||||
Hint(message, hintType);
|
||||
}
|
||||
|
||||
internal static void Tick()
|
||||
{
|
||||
try
|
||||
{
|
||||
ModMain.frmMain!.PanHint.HorizontalAlignment = HorizontalAlignment.Right;
|
||||
ModMain.frmMain.PanHint.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
|
||||
var extraHeight = ModMain.frmMain.PanExtraButtons.ActualHeight;
|
||||
ModMain.frmMain.PanHint.Margin = new Thickness(0, 0, 0, extraHeight > 0 ? extraHeight + 20 : 20);
|
||||
|
||||
if (!HintWaiting.Any())
|
||||
return;
|
||||
|
||||
var currentHint = HintWaiting[0];
|
||||
|
||||
var duplicate = ModMain.frmMain.PanHint.Children.OfType<MyToast>()
|
||||
.FirstOrDefault(t => !t.IsDismissing && t.Context == currentHint.Text && t.ToastType == currentHint.Type);
|
||||
if (duplicate != null)
|
||||
{
|
||||
duplicate.Emphasize();
|
||||
HintWaiting.RemoveAt(0);
|
||||
return;
|
||||
}
|
||||
|
||||
var activeCount = ModMain.frmMain.PanHint.Children.OfType<MyToast>().Count(t => !t.IsDismissing);
|
||||
if (activeCount >= 5)
|
||||
{
|
||||
var oldest = ModMain.frmMain.PanHint.Children.OfType<MyToast>().FirstOrDefault(t => !t.IsDismissing);
|
||||
oldest?.Dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
var toast = new MyToast
|
||||
{
|
||||
Context = currentHint.Text,
|
||||
ToastType = currentHint.Type,
|
||||
Icon = currentHint.Type switch
|
||||
{
|
||||
HintType.Success => "lucide/circle-check",
|
||||
HintType.Error => "lucide/circle-minus",
|
||||
HintType.Warning => "lucide/triangle-alert",
|
||||
_ => "lucide/info"
|
||||
},
|
||||
DisplayDuration = (800d + ModBase.MathClamp(currentHint.Text.Length, 5d, 23d) * 180d) * ModAnimation.aniSpeed
|
||||
};
|
||||
|
||||
ModMain.frmMain.PanHint.Children.Add(toast);
|
||||
toast.Show();
|
||||
|
||||
if (currentHint.Log)
|
||||
ModBase.Log("[UI] 弹出提示:" + currentHint.Text);
|
||||
HintWaiting.RemoveAt(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "显示弹出提示失败", ModBase.LogLevel.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
public static void HideAll()
|
||||
{
|
||||
foreach (MyToast toast in ModMain.frmMain!.PanHint.Children.OfType<MyToast>().ToList())
|
||||
toast.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public enum HintType
|
||||
{
|
||||
Info,
|
||||
Success,
|
||||
Error,
|
||||
Warning
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using PCL.Core.App;
|
||||
using PCL.Core.UI.Theme;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
public static class ThemeManager
|
||||
{
|
||||
private static bool _contextMenuHandlerRegistered;
|
||||
|
||||
public static bool IsDarkMode => ThemeService.IsDarkMode;
|
||||
|
||||
public static ResourceDictionary AppResources => System.Windows.Application.Current.Resources;
|
||||
|
||||
public static ModBase.MyColor colorGray1 = new(AppResources["ColorObjectGray1"]);
|
||||
public static ModBase.MyColor colorGray4 = new(AppResources["ColorObjectGray4"]);
|
||||
public static ModBase.MyColor colorGray5 = new(AppResources["ColorObjectGray5"]);
|
||||
public static ModBase.MyColor colorSemiTransparent = new(AppResources["ColorBrushSemiTransparent"]);
|
||||
|
||||
public static void ThemeRefresh(int newTheme = -1)
|
||||
{
|
||||
colorGray1 = new ModBase.MyColor(AppResources["ColorObjectGray1"]);
|
||||
colorGray4 = new ModBase.MyColor(AppResources["ColorObjectGray4"]);
|
||||
colorGray5 = new ModBase.MyColor(AppResources["ColorObjectGray5"]);
|
||||
colorSemiTransparent = new ModBase.MyColor(AppResources["ColorBrushSemiTransparent"]);
|
||||
ThemeRefreshMain();
|
||||
}
|
||||
|
||||
public static void ThemeRefreshMain()
|
||||
{
|
||||
ModBase.RunInUi(() =>
|
||||
{
|
||||
if (!ModMain.frmMain.IsLoaded) return;
|
||||
RefreshBackground();
|
||||
RefreshAllContextMenuThemes();
|
||||
});
|
||||
}
|
||||
|
||||
// 主页面背景
|
||||
private static void RefreshBackground()
|
||||
{
|
||||
if (Config.Preference.Background.BackgroundColorful)
|
||||
{
|
||||
var brush = new LinearGradientBrush
|
||||
{
|
||||
EndPoint = new Point(0.1, 1),
|
||||
StartPoint = new Point(0.9, 0)
|
||||
};
|
||||
|
||||
var hue = ThemeService.GetCurrentThemeArgs().Hue;
|
||||
var hue1 = hue - 15;
|
||||
var hue2 = hue + 15;
|
||||
var tone = ThemeService.CurrentTone;
|
||||
var darkLight = IsDarkMode ? 0.2d : 1d;
|
||||
brush.GradientStops.Add(new GradientStop
|
||||
{ Offset = -0.1d, Color = LabColor.FromLch(0.84d * darkLight, tone.C5, hue1) });
|
||||
brush.GradientStops.Add(new GradientStop
|
||||
{ Offset = 0.4d, Color = LabColor.FromLch(0.96d * darkLight, tone.C7, hue) });
|
||||
brush.GradientStops.Add(new GradientStop
|
||||
{ Offset = 1.1d, Color = LabColor.FromLch(0.84d * darkLight, tone.C5, hue2) });
|
||||
ModMain.frmMain.PanForm.Background = brush;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModMain.frmMain.PanForm.Background = (Brush)System.Windows.Application.Current.Resources["ColorBrushBackground"];
|
||||
}
|
||||
|
||||
ModMain.frmMain.PanForm.Background.Freeze();
|
||||
}
|
||||
|
||||
// 通用ContextMenu主题刷新
|
||||
private static void RefreshAllContextMenuThemes()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_contextMenuHandlerRegistered)
|
||||
{
|
||||
EventManager.RegisterClassHandler(typeof(ContextMenu), ContextMenu.OpenedEvent,
|
||||
new RoutedEventHandler(OnContextMenuOpened));
|
||||
_contextMenuHandlerRegistered = true;
|
||||
}
|
||||
|
||||
foreach (Window window in System.Windows.Application.Current.Windows)
|
||||
RefreshContextMenusInElement(window);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModBase.Log(ex, "刷新ContextMenu主题时出错");
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnContextMenuOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (sender is ContextMenu contextMenu)
|
||||
{
|
||||
contextMenu.ClearValue(FrameworkElement.StyleProperty);
|
||||
contextMenu.UpdateDefaultStyle();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略个别错误
|
||||
}
|
||||
}
|
||||
|
||||
private static void RefreshContextMenusInElement(DependencyObject element)
|
||||
{
|
||||
if (element is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (element is FrameworkElement { ContextMenu: not null } fe)
|
||||
{
|
||||
fe.ContextMenu.ClearValue(FrameworkElement.StyleProperty);
|
||||
fe.ContextMenu.UpdateDefaultStyle();
|
||||
}
|
||||
|
||||
var childrenCount = VisualTreeHelper.GetChildrenCount(element);
|
||||
for (int i = 0; i < childrenCount; i++)
|
||||
RefreshContextMenusInElement(VisualTreeHelper.GetChild(element, i));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略个别元素的错误,继续处理其他元素
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user