From 21f12c10fab8de536eb26efdc0ea12411cb42d2e Mon Sep 17 00:00:00 2001 From: SaltWood_233 <105980161+SALTWOOD@users.noreply.github.com> Date: Sun, 28 Jun 2026 12:38:43 +0800 Subject: [PATCH 1/6] refactor(mod): mod dependency detail --- .../App/Localization/Languages/en-US.xaml | 6 + .../App/Localization/Languages/zh-CN.xaml | 6 + .../Controls/MyListItem.xaml.cs | 3 + .../Controls/MyMsg/ModDependencyMsgBox.xaml | 51 +++++ .../MyMsg/ModDependencyMsgBox.xaml.cs | 203 ++++++++++++++++++ .../Modules/Minecraft/ModComp.cs | 83 ++----- Plain Craft Launcher 2/Modules/ModMain.cs | 36 +++- .../Comp/PageDownloadCompDetail.xaml.cs | 108 +++++++++- 8 files changed, 427 insertions(+), 69 deletions(-) create mode 100644 Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml create mode 100644 Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs diff --git a/PCL.Core/App/Localization/Languages/en-US.xaml b/PCL.Core/App/Localization/Languages/en-US.xaml index 096e92cce..c814689d2 100644 --- a/PCL.Core/App/Localization/Languages/en-US.xaml +++ b/PCL.Core/App/Localization/Languages/en-US.xaml @@ -757,6 +757,7 @@ Getting version list... Changelog Save as + Download Download server Show all versions Save installer @@ -1057,6 +1058,11 @@ Updated {0} Required dependencies Optional dependencies + Version details + Install to current instance + Choose download location + Please select a Minecraft instance first + None Version list Release Beta diff --git a/PCL.Core/App/Localization/Languages/zh-CN.xaml b/PCL.Core/App/Localization/Languages/zh-CN.xaml index 678b14a74..2b8bd57e3 100644 --- a/PCL.Core/App/Localization/Languages/zh-CN.xaml +++ b/PCL.Core/App/Localization/Languages/zh-CN.xaml @@ -803,6 +803,7 @@ 最新稳定版 最新测试版 选择保存位置 + 下载 Fabric 安装器 (*.jar)|*.jar LegacyFabric 安装器 (*.jar)|*.jar Quilt 安装器 (*.jar)|*.jar @@ -1057,6 +1058,11 @@ 更新于 {0} 必要前置资源 可选前置资源 + 版本详情 + 安装到当前实例 + 选择下载位置 + 请先选择一个 Minecraft 实例 + 版本列表 正式版 测试版 diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.cs b/Plain Craft Launcher 2/Controls/MyListItem.xaml.cs index dce07b624..caa7f39d7 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.cs +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.cs @@ -27,6 +27,7 @@ public partial class MyListItem : IMyRadio public event IMyRadio.ChangedEventHandler? Changed; public event ClickEventHandler? Click; + public event ClickEventHandler? RightClick; public event LogoClickEventHandler? LogoClick; public void RefreshColor(object sender, EventArgs e) @@ -474,6 +475,8 @@ public MyListItem() PreviewMouseLeftButtonDown += Button_MouseDown; MouseLeave += Button_MouseLeave; PreviewMouseLeftButtonUp += Button_MouseLeave; + // 右键事件(用于触发 RightClick,例如模组版本项右键直接另存为) + PreviewMouseRightButtonUp += (_, e) => RightClick?.Invoke(this, e); MouseEnter += RefreshColor; MouseLeave += RefreshColor; MouseLeftButtonDown += RefreshColor; diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml new file mode 100644 index 000000000..69190c7cd --- /dev/null +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs new file mode 100644 index 000000000..68c20ee03 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs @@ -0,0 +1,203 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Interop; +using PCL.Core.App.Localization; +using PCL.Core.UI.Controls; + +namespace PCL; + +/// +/// 模组版本的「前置详情」弹窗:展示该版本的版本信息、必要前置 / 可选前置, +/// 并提供「安装到当前实例」/「选择下载位置」两个动作。点击某个前置项会关闭弹窗并返回该前置工程。 +/// 布局由 XAML 定义,调用方只需传入 CompFile 数据(由 触发)。 +/// +public partial class ModDependencyMsgBox +{ + private readonly ModMain.MyMsgBoxConverter myConverter; + private readonly int uuid = ModBase.GetUuid(); + + public ModDependencyMsgBox(ModMain.MyMsgBoxConverter converter) + { + try + { + InitializeComponent(); + AppendUniqueNameSuffix(Btn1); + AppendUniqueNameSuffix(Btn2); + myConverter = converter; + LabTitle.Text = Lang.Text("Download.Comp.Detail.VersionPopup.Title"); + Btn1.Text = Lang.Text("Download.Comp.Detail.VersionPopup.ButtonInstall"); + Btn2.Text = Lang.Text("Download.Comp.Detail.VersionPopup.ButtonSaveAs"); + ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); + if (converter.Content is ModComp.CompFile file) + Populate(file); + } + catch (Exception ex) + { + ModBase.Log(ex, "前置弹窗初始化失败", ModBase.LogLevel.Hint); + } + + Loaded += Load; + Btn1.Click += Btn1_Click; + Btn2.Click += Btn2_Click; + LabTitle.MouseLeftButtonDown += Drag; + PanBorder.MouseLeftButtonDown += Drag; + } + + private void AppendUniqueNameSuffix(FrameworkElement element) + { + element.Name += ModBase.GetUuid(); + } + + /// + /// 填充版本信息与必要 / 可选前置项。 + /// + private void Populate(ModComp.CompFile file) + { + // 1. 版本信息 + var info = new List(); + if (!string.IsNullOrEmpty(file.FileName)) + info.Add(file.FileName); + if (file.GameVersions.Any()) + info.Add(Lang.Text("Download.Comp.Detail.FileList.GameVersion", string.Join("、", file.GameVersions))); + if (file.ModLoaders.Any()) + info.Add(string.Join(" / ", file.ModLoaders)); + info.Add(Lang.Text("Download.Comp.Detail.FileList.Updated", Lang.TimeSpan(file.ReleaseDate - DateTime.Now))); + if (file.Status != ModComp.CompFileStatus.Release) + info.Add(file.StatusDescription); + LabVersionInfo.Text = string.Join(" | ", info); + + // 2. 必要前置 / 可选前置 + FillDependencySection(LabReqTitle, PanReqDeps, file.Dependencies, + Lang.Text("Download.Comp.Detail.FileList.RequiredDependencies")); + FillDependencySection(LabOptTitle, PanOptDeps, file.OptionalDependencies, + Lang.Text("Download.Comp.Detail.FileList.OptionalDependencies")); + } + + /// + /// 填充一个前置分区:标题 + 可点击的前置项(点击以该前置工程关闭弹窗)。 + /// + private void FillDependencySection(TextBlock header, StackPanel panel, List depIds, string title) + { + var projects = new List(); + foreach (var id in depIds) + if (ModComp.compProjectCache.TryGetValue(id, out var project)) + projects.Add(project); + + header.Text = $"{title}({projects.Count})"; + + if (!projects.Any()) + { + panel.Children.Add(new TextBlock + { + Text = Lang.Text("Download.Comp.Detail.VersionPopup.NoDeps"), + FontSize = 13d, + Margin = new Thickness(14d, 0d, 0d, 4d), + Opacity = 0.6d + }); + return; + } + + foreach (var project in projects) + { + var captured = project; + var item = new MyListItem + { + Title = string.IsNullOrEmpty(project.TranslatedName) ? project.RawName : project.TranslatedName, + Info = project.FromCurseForge ? "CurseForge" : "Modrinth", + Logo = project.LogoUrl ?? "", + Height = 40, + Type = MyListItem.CheckType.Clickable + }; + item.Click += (_, _) => ReturnResult(captured); + panel.Children.Add(item); + } + } + + /// + /// 以指定返回值关闭弹窗:按钮返回 1 / 2,前置项返回对应 CompProject。 + /// + private void ReturnResult(object result) + { + if (myConverter.IsExited) + return; + myConverter.IsExited = true; + myConverter.Result = result; + Close(); + } + + private void Load(object sender, EventArgs e) + { + try + { + // UI 初始化 + if (Btn2.IsVisible && !(Btn1.ColorType == MyButton.ColorState.Red)) + Btn1.ColorType = MyButton.ColorState.Highlight; + Btn1.Focus(); + // 动画 + Opacity = 0d; + ModAnimation.AniStart( + ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, + new ModBase.MyColor(90d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200), + "PanMsgBackground Background"); + ModAnimation.AniStart( + new[] + { + ModAnimation.AaOpacity(this, 1d, 120, 60), + ModAnimation.AaDouble(i => TransformPos.Y += (double)i, + -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), + ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, + -TransformRotate.Angle, 300, 60, + new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) + }, "MyMsgBox " + uuid); + ModBase.Log("[Control] 前置详情弹窗:" + LabTitle.Text); + } + catch (Exception ex) + { + ModBase.Log(ex, "前置详情弹窗加载失败", ModBase.LogLevel.Hint); + } + } + + private void Close() + { + // 结束线程阻塞 + myConverter.WaitFrame.Continue = false; + ComponentDispatcher.PopModal(); + // 动画 + ModAnimation.AniStart(new[] + { + ModAnimation.AaCode(() => + { + if (!ModMain.WaitingMyMsgBox.Any()) + ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, + BlurBorder.BackgroundProperty, + new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, + ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); + }, 30), + ModAnimation.AaOpacity(this, -Opacity, 80, 20), + ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, + 150, 0, new ModAnimation.AniEaseOutFluent()), + ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, + 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), + ModAnimation.AaCode(() => ((Grid)Parent).Children.Remove(this), after: true) + }, "MyMsgBox " + uuid); + } + + public void Btn1_Click(object sender, MouseButtonEventArgs e) => ReturnResult(1); + + public void Btn2_Click(object sender, MouseButtonEventArgs e) => ReturnResult(2); + + private void Drag(object sender, MouseButtonEventArgs e) + { + try + { + if (e.LeftButton == MouseButtonState.Pressed) + if (e.GetPosition(ShapeLine).Y <= 2d) + ModMain.frmMain.DragMove(); + } + catch (Exception ex) + { + ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint); + } + } +} diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs index 99cd9498f..72298091c 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs @@ -3125,7 +3125,9 @@ public JsonObject ToJson() /// public MyVirtualizingElement ToListItem(MyListItem.ClickEventHandler onClick, MyIconButton.ClickEventHandler? onSaveClick = null, - bool badDisplayName = false) + bool badDisplayName = false, + MyListItem.ClickEventHandler? onRightClick = null, + MyIconButton.ClickEventHandler? onDownloadClick = null) { return new MyVirtualizingElement(() => { @@ -3172,8 +3174,11 @@ public MyVirtualizingElement ToListItem(MyListItem.ClickEventHandler } }; newItem.Click += onClick; + if (onRightClick is not null) + newItem.RightClick += onRightClick; - // 4. 建立另存为按钮 + // 4. 建立右侧按钮(另存为 / 下载) + var buttons = new List(); if (onSaveClick is not null) { var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") }; @@ -3181,8 +3186,19 @@ public MyVirtualizingElement ToListItem(MyListItem.ClickEventHandler ToolTipService.SetVerticalOffset(btnSave, 30); ToolTipService.SetHorizontalOffset(btnSave, 2); btnSave.Click += onSaveClick; - newItem.Buttons = new[] { btnSave }; + buttons.Add(btnSave); } + if (onDownloadClick is not null) + { + var btnDownload = new MyIconButton { SvgIcon = "lucide/download", ToolTip = Lang.Text("Download.Version.Download") }; + ToolTipService.SetPlacement(btnDownload, PlacementMode.Center); + ToolTipService.SetVerticalOffset(btnDownload, 30); + ToolTipService.SetHorizontalOffset(btnDownload, 2); + btnDownload.Click += onDownloadClick; + buttons.Add(btnDownload); + } + if (buttons.Any()) + newItem.Buttons = buttons.ToArray(); return newItem; }) @@ -3622,66 +3638,5 @@ public static bool IsInstanceSuitableForFile(McInstance? version, CompFile file, #endregion - /// - /// 预载包含大量 CompFile 的卡片,添加必要的元素和前置列表。 - /// 前置列表(必要 / 可选)会被放入可折叠栏:必要前置默认展开,可选前置默认收起。 - /// - public static void CompFilesCardPreload(StackPanel stack, List files) - { - // 获取卡片对应的前置 ID - // 如果为整合包就不会有 Dependencies 信息,所以不用管 - var deps = files.SelectMany(f => f.Dependencies).Distinct().ToList(); - var optionalDeps = files.SelectMany(f => f.OptionalDependencies).Distinct().ToList(); - if (!deps.Any() && !optionalDeps.Any()) - return; - - // 必要前置:默认展开 - _AddDependencyBar(stack, deps, - Lang.Text("Download.Comp.Detail.FileList.RequiredDependencies"), collapsed: false); - // 可选前置:默认收起(库 Mod 可能有大量可选前置,参见 Issue #2873) - _AddDependencyBar(stack, optionalDeps, - Lang.Text("Download.Comp.Detail.FileList.OptionalDependencies"), collapsed: true); - - // 添加结尾间隔(版本列表标题) - stack.Children.Add(new TextBlock - { - Text = Lang.Text("Download.Comp.Detail.FileList.VersionList"), FontSize = 14d, - HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(6d, 12d, 0d, 5d) - }); - } - - /// - /// 将一组前置依赖(按工程 ID)渲染为一个可折叠栏并加入 。 - /// 仅保留在 compProjectCache 中有信息的前置;若过滤后为空则不添加任何折叠栏。 - /// - /// 是否默认收起。前置 item 全部加入即可,靠 MyVirtualizingElement 在可见时才实例化。 - private static void _AddDependencyBar(StackPanel stack, List depIds, string title, bool collapsed) - { - if (depIds is null || !depIds.Any()) - return; - - depIds.Sort(); - var projects = new List(); - foreach (var dep in depIds) - { - if (compProjectCache.TryGetValue(dep, out var project)) - projects.Add(project); - else - ModBase.Log($"[Comp] 未找到 ID {dep} 的前置信息", ModBase.LogLevel.Debug); - } - if (!projects.Any()) - return; - - var bar = new MyCollapseBar - { - Title = $"{title} ({projects.Count})", - IsCollapsed = collapsed - }; - foreach (var project in projects) - bar.ContentPanel.Children.Add(project.ToCompItem(false, false)); - - stack.Children.Add(bar); - } - #endregion } diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index 1a653f3e3..703c26381 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -289,7 +289,8 @@ public enum MyMsgBoxType Select, Input, Login, - Markdown + Markdown, + ModDependency } private static string GetDefaultDialogTitle() => Lang.Text("Common.Dialog.Title"); @@ -566,6 +567,34 @@ public static string MyMsgBoxInput(string title, string text = "", string defaul return (int?)converter.Result; } + /// + /// 显示模组版本的「前置详情」弹窗。返回值:1 = 安装到当前实例;2 = 选择下载位置; + /// 若点击某个前置项,则返回该前置的 ;关闭返回 null。 + /// + /// 要展示前置信息的模组版本文件。 + public static object? ModDependencyMsgBox(ModComp.CompFile file) + { + var converter = new MyMsgBoxConverter + { + Type = MyMsgBoxType.ModDependency, Content = file + }; + WaitingMyMsgBox.Add(converter); + try + { + if (frmMain is not null) + frmMain.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + + ModBase.Log($"[Control] 前置详情弹框返回:{converter.Result ?? "null"}"); + return converter.Result; + } + public static void MyMsgBoxTick() { @@ -609,6 +638,11 @@ public static void MyMsgBoxTick() frmMain.PanMsg.Children.Add(new MyMsgMarkdown(WaitingMyMsgBox[0])); break; } + case MyMsgBoxType.ModDependency: + { + frmMain.PanMsg.Children.Add(new ModDependencyMsgBox(WaitingMyMsgBox[0])); + break; + } } WaitingMyMsgBox.RemoveAt(0); diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs index cfa71cfbb..d0349655f 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs @@ -5,6 +5,7 @@ using System.Text; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using FluentValidation; using PCL.Core.App; using PCL.Core.App.Localization; @@ -242,15 +243,29 @@ public void InstallWorld_Click(MyListItem sender, EventArgs e) public void Save_Click(object sender, EventArgs e) { - // 获取点击项关联的文件对象 - var file = sender switch + SaveCompFile(ResolveFileFromSender(sender)); + } + + /// + /// 从点击事件 sender(MyListItem 或其内部按钮)解析出关联的 CompFile。 + /// + private static ModComp.CompFile? ResolveFileFromSender(object sender) + { + return sender switch { FrameworkElement Element when Element.Tag is ModComp.CompFile CompFile => CompFile, FrameworkElement Element when Element.Parent is FrameworkElement Parent && Parent.Tag is ModComp.CompFile CompFile => CompFile, FrameworkElement Element when Element.Parent is FrameworkElement Parent && Parent.Parent is FrameworkElement GrandParent && GrandParent.Tag is ModComp.CompFile CompFile => CompFile, _ => null }; + } + /// + /// 另存为:弹窗选择保存位置(模组可选自动安装必需前置),与历史行为完全一致。 + /// + private void SaveCompFile(ModComp.CompFile file) + { + if (file is null) return; ModBase.RunInNewThread(() => { try @@ -542,6 +557,89 @@ void DownloadDependencies() }, "Download CompDetail Save"); } + /// + /// 左键单击版本项:弹窗展示该版本的前置 / 可选前置,并提供“安装到当前实例” / “选择下载位置”。 + /// 把前置信息按版本拆分展示,避免合并所有版本导致的误导(参见 Issue #3265)。 + /// + public void ShowVersionPopup_Click(object sender, MouseButtonEventArgs e) + { + var file = ResolveFileFromSender(sender); + if (file is null) return; + var result = ModMain.ModDependencyMsgBox(file); + switch (result) + { + case ModComp.CompProject project: + // 点击了某个前置项,跳转到该前置的详情页 + ModMain.frmMain.PageChange(new FormMain.PageStackData + { + page = FormMain.PageType.CompDetail, + additional = (project, new List(), "", ModComp.CompLoaderType.Any, + ModComp.CompType.Any, null) + }); + break; + case 1: + InstallToInstance(file); + break; + case 2: + SaveCompFile(file); + break; + } + } + + /// + /// 安装到当前实例:仅把模组本体下载到当前实例的 mods 文件夹,不安装前置。 + /// + private void InstallToInstance(ModComp.CompFile file) + { + var instance = ModInstanceList.McMcInstanceSelected; + if (instance is null) + { + ModMain.MyMsgBox(Lang.Text("Download.Comp.Detail.VersionPopup.NoInstance"), + Lang.Text("Download.Comp.Detail.VersionPopup.Title"), button1: null, isWarn: true, forceWait: true); + return; + } + if (!instance.IsLoaded) instance.Load(); + var modsFolder = instance.PathIndie + "mods\\"; + ModBase.RunInNewThread(() => + { + try + { + Directory.CreateDirectory(modsFolder); + var target = modsFolder + ModComp.CompFileNameGet(_project, file); + var loaderName = Lang.Text("Download.Comp.Detail.DownloadResource", + Lang.Text("Download.Comp.Type.Mod"), ModBase.GetFileNameWithoutExtentionFromPath(target)); + var loaders = new List + { + new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"), + new List { file.ToNetFile(target) }) + { + ProgressWeight = 6, + block = true + } + }; + var loader = new ModLoader.LoaderCombo(loaderName, loaders); + loader.OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly; + loader.Start(1); + ModLoader.LoaderTaskbarAdd(loader); + ModMain.frmMain.BtnExtraDownload.ShowRefresh(); + ModMain.frmMain.BtnExtraDownload.Ribble(); + } + catch (Exception ex) + { + ModBase.Log(ex, "安装到当前实例失败", ModBase.LogLevel.Feedback); + } + }, "Download CompDetail Install"); + } + + /// + /// 版本项右侧下载按钮(占位,逻辑待实现)。 + /// + public void Download_Click(object sender, EventArgs e) + { + // TODO: 版本项右侧下载按钮逻辑 + ModBase.Log("[Comp] 版本项右侧下载按钮被点击(占位,暂未实现)", ModBase.LogLevel.Debug); + } + private void BtnIntroWeb_Click(object sender, EventArgs e) { ModBase.OpenWebsite(_project.Website); @@ -1069,9 +1167,11 @@ private void UpdateFilterResult() default: { - ModComp.CompFilesCardPreload(stack, list); foreach (var item in list) - stack.Children.Add(item.ToListItem(ModMain.frmDownloadCompDetail.Save_Click, + stack.Children.Add(item.ToListItem( + ModMain.frmDownloadCompDetail.ShowVersionPopup_Click, + onRightClick: ModMain.frmDownloadCompDetail.Save_Click, + onDownloadClick: ModMain.frmDownloadCompDetail.Download_Click, badDisplayName: badDisplayName)); break; } From f8fc18d47d79b2db43ad899ff7b22c8677ed9e8e Mon Sep 17 00:00:00 2001 From: SaltWood_233 <105980161+SALTWOOD@users.noreply.github.com> Date: Sun, 28 Jun 2026 12:43:41 +0800 Subject: [PATCH 2/6] fix(ui): add cancel button --- .../Controls/MyMsg/ModDependencyMsgBox.xaml | 2 ++ .../Controls/MyMsg/ModDependencyMsgBox.xaml.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml index 69190c7cd..c7f4c4090 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml @@ -45,6 +45,8 @@ TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False" /> + diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs index 68c20ee03..8fd110ec1 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs @@ -24,10 +24,12 @@ public ModDependencyMsgBox(ModMain.MyMsgBoxConverter converter) InitializeComponent(); AppendUniqueNameSuffix(Btn1); AppendUniqueNameSuffix(Btn2); + AppendUniqueNameSuffix(Btn3); myConverter = converter; LabTitle.Text = Lang.Text("Download.Comp.Detail.VersionPopup.Title"); Btn1.Text = Lang.Text("Download.Comp.Detail.VersionPopup.ButtonInstall"); Btn2.Text = Lang.Text("Download.Comp.Detail.VersionPopup.ButtonSaveAs"); + Btn3.Text = Lang.Text("Common.Action.Cancel"); ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); if (converter.Content is ModComp.CompFile file) Populate(file); @@ -40,6 +42,7 @@ public ModDependencyMsgBox(ModMain.MyMsgBoxConverter converter) Loaded += Load; Btn1.Click += Btn1_Click; Btn2.Click += Btn2_Click; + Btn3.Click += Btn3_Click; LabTitle.MouseLeftButtonDown += Drag; PanBorder.MouseLeftButtonDown += Drag; } @@ -187,6 +190,8 @@ private void Close() public void Btn2_Click(object sender, MouseButtonEventArgs e) => ReturnResult(2); + public void Btn3_Click(object sender, MouseButtonEventArgs e) => ReturnResult(null); + private void Drag(object sender, MouseButtonEventArgs e) { try From f857531d71ee18b142e82b7e8ca1a0511c5b491d Mon Sep 17 00:00:00 2001 From: SaltWood_233 <105980161+SALTWOOD@users.noreply.github.com> Date: Sun, 28 Jun 2026 12:53:35 +0800 Subject: [PATCH 3/6] feat(download): quick download in mod detail page --- .../Modules/Minecraft/ModComp.cs | 81 ++++++++++++++----- .../Comp/PageDownloadCompDetail.xaml.cs | 7 +- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs index 72298091c..cf67b2f37 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.cs @@ -3403,27 +3403,46 @@ public static void QuickDownload(CompProject project) return; } - var behavior = Config.Download.Comp.QuickDownloadBehavior; - if (behavior == 0) + var behavior = _ResolveQuickDownloadBehavior(); + if (behavior is null) return; // 用户取消 + + switch (behavior) { - // 总是询问:弹「方式选择」 - int? choice = ModBase.RunInUiWait(() => + case 1: // 下载到当前选中实例 + _QuickDownloadToInstance(project, files, ModInstanceList.McMcInstanceSelected); + break; + case 2: // 询问并下载到选择的实例 { - var options = new List - { - new MyRadioBox { Text = Lang.Text("Download.Comp.QuickDownload.ChooseMethod.CurrentInstance") }, - new MyRadioBox { Text = Lang.Text("Download.Comp.QuickDownload.ChooseMethod.AskInstance") }, - new MyRadioBox { Text = Lang.Text("Download.Comp.QuickDownload.ChooseMethod.AskPath") } - }; - return ModMain.MyMsgBoxSelect(options, - Lang.Text("Download.Comp.QuickDownload.ChooseMethod.Title"), - button1: Lang.Text("Common.Action.Continue"), - button2: Lang.Text("Common.Action.Cancel")); - }); - if (choice is null) return; // 用户取消 - behavior = choice.Value + 1; // 0→1 当前实例, 1→2 选实例, 2→3 选路径 + var instance = _QuickDownloadPickInstance(project, files); + if (instance is null) return; + _QuickDownloadToInstance(project, files, instance); + break; + } + case 3: // 询问并下载到一个路径 + _QuickDownloadToFolder(project, files); + break; } + } + catch (Exception ex) + { + ModBase.Log(ex, "[Comp] 快速下载失败", ModBase.LogLevel.Feedback); + } + }, "Comp QuickDownload"); + } + /// + /// 快速下载指定版本(版本项右侧下载按钮入口):行为与 一致, + /// 但下载用户指定的具体版本,而非自动挑选的最新兼容版本。 + /// + public static void QuickDownload(CompProject project, CompFile file) + { + ModBase.RunInNewThread(() => + { + try + { + var files = new List { file }; + var behavior = _ResolveQuickDownloadBehavior(); + if (behavior is null) return; // 用户取消 switch (behavior) { case 1: // 下载到当前选中实例 @@ -3443,9 +3462,33 @@ public static void QuickDownload(CompProject project) } catch (Exception ex) { - ModBase.Log(ex, "[Comp] 快速下载失败", ModBase.LogLevel.Feedback); + ModBase.Log(ex, "[Comp] 快速下载(指定版本)失败", ModBase.LogLevel.Feedback); } - }, "Comp QuickDownload"); + }, "Comp QuickDownload (version)"); + } + + /// + /// 解析快速下载行为:配置为「总是询问」时弹「方式选择」让用户选;用户取消返回 null,否则返回 1/2/3。 + /// + private static int? _ResolveQuickDownloadBehavior() + { + var behavior = Config.Download.Comp.QuickDownloadBehavior; + if (behavior != 0) return behavior; + int? choice = ModBase.RunInUiWait(() => + { + var options = new List + { + new MyRadioBox { Text = Lang.Text("Download.Comp.QuickDownload.ChooseMethod.CurrentInstance") }, + new MyRadioBox { Text = Lang.Text("Download.Comp.QuickDownload.ChooseMethod.AskInstance") }, + new MyRadioBox { Text = Lang.Text("Download.Comp.QuickDownload.ChooseMethod.AskPath") } + }; + return ModMain.MyMsgBoxSelect(options, + Lang.Text("Download.Comp.QuickDownload.ChooseMethod.Title"), + button1: Lang.Text("Common.Action.Continue"), + button2: Lang.Text("Common.Action.Cancel")); + }); + if (choice is null) return null; + return choice.Value + 1; // 0→1 当前实例, 1→2 选实例, 2→3 选路径 } /// 下载到指定实例的最新兼容版本。 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs index d0349655f..61f128f7c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.cs @@ -632,12 +632,13 @@ private void InstallToInstance(ModComp.CompFile file) } /// - /// 版本项右侧下载按钮(占位,逻辑待实现)。 + /// 版本项右侧下载按钮:快速下载该指定版本(行为与搜索结果快速下载一致,受 QuickDownloadBehavior 设置控制)。 /// public void Download_Click(object sender, EventArgs e) { - // TODO: 版本项右侧下载按钮逻辑 - ModBase.Log("[Comp] 版本项右侧下载按钮被点击(占位,暂未实现)", ModBase.LogLevel.Debug); + var file = ResolveFileFromSender(sender); + if (file is null) return; + ModComp.QuickDownload(_project, file); } private void BtnIntroWeb_Click(object sender, EventArgs e) From a1e69080479ef347622e0681875ae633934ddfeb Mon Sep 17 00:00:00 2001 From: SaltWood_233 <105980161+SALTWOOD@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:00:23 +0800 Subject: [PATCH 4/6] feat(ui): esc to close dialog --- .../Controls/MyMsg/ModDependencyMsgBox.xaml.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs index 8fd110ec1..b7ed1a2ae 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs @@ -40,6 +40,7 @@ public ModDependencyMsgBox(ModMain.MyMsgBoxConverter converter) } Loaded += Load; + PreviewKeyDown += ModDependencyMsgBox_PreviewKeyDown; Btn1.Click += Btn1_Click; Btn2.Click += Btn2_Click; Btn3.Click += Btn3_Click; @@ -192,6 +193,16 @@ private void Close() public void Btn3_Click(object sender, MouseButtonEventArgs e) => ReturnResult(null); + /// 按 Esc 关闭弹窗(等同点击「取消」,返回 null)。 + private void ModDependencyMsgBox_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Escape) + { + e.Handled = true; + ReturnResult(null); + } + } + private void Drag(object sender, MouseButtonEventArgs e) { try From bb9e86609ce7439ddf55bfbe3e3c7832f956fddd Mon Sep 17 00:00:00 2001 From: SaltWood_233 <105980161+SALTWOOD@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:15:22 +0800 Subject: [PATCH 5/6] refactor(ui): use `MyCompItem` --- .../Controls/MyMsg/ModDependencyMsgBox.xaml.cs | 12 ++++-------- .../Pages/PageDownload/Comp/MyCompItem.xaml.cs | 6 ++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs index b7ed1a2ae..ff840262e 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs @@ -105,14 +105,10 @@ private void FillDependencySection(TextBlock header, StackPanel panel, List ReturnResult(captured); panel.Children.Add(item); } diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.cs b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.cs index 2cebfa044..d3057b889 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.cs @@ -266,6 +266,11 @@ public void RefreshFavoriteStatus() // 触发点击事件 public event ClickEventHandler? Click; + /// + /// 点击时是否自动跳转到该工程的详情页。默认 true;在弹窗等需要自行处理点击的场景设为 false。 + /// + public bool AutoNavigate = true; + public delegate void ClickEventHandler(object sender, MouseButtonEventArgs e); private void BtnDelete_Click(object sender, EventArgs e) @@ -285,6 +290,7 @@ private void _BtnDownload_Click(object sender, EventArgs e) private void MyCompItem_Click(MyCompItem sender, EventArgs e) { + if (!AutoNavigate) return; // 记录当前展开的卡片标题(#2712) var titles = new List(); if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.CompDetail) From e70d306be06a3aedc8b614cbde7ae56ac6651285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=90=E5=BA=90=E7=BB=AB?= Date: Sun, 28 Jun 2026 22:05:36 +0800 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF=E4=B8=BA=20Tag=20=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=B9=B6=E5=9C=A8=E6=97=A0=E5=89=8D=E7=BD=AE=E6=97=B6=E9=9A=90?= =?UTF-8?q?=E8=97=8F=E5=88=86=E5=8C=BA=20(#3277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/MyMsg/ModDependencyMsgBox.xaml | 11 ++++- .../MyMsg/ModDependencyMsgBox.xaml.cs | 46 +++++++++++-------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml index c7f4c4090..0f3e5f189 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml @@ -32,7 +32,16 @@ VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7"> - + + + + + + + diff --git a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs index ff840262e..98e43b4a7 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs +++ b/Plain Craft Launcher 2/Controls/MyMsg/ModDependencyMsgBox.xaml.cs @@ -2,6 +2,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; +using System.Windows.Media; using PCL.Core.App.Localization; using PCL.Core.UI.Controls; @@ -59,17 +60,11 @@ private void AppendUniqueNameSuffix(FrameworkElement element) private void Populate(ModComp.CompFile file) { // 1. 版本信息 - var info = new List(); - if (!string.IsNullOrEmpty(file.FileName)) - info.Add(file.FileName); - if (file.GameVersions.Any()) - info.Add(Lang.Text("Download.Comp.Detail.FileList.GameVersion", string.Join("、", file.GameVersions))); - if (file.ModLoaders.Any()) - info.Add(string.Join(" / ", file.ModLoaders)); - info.Add(Lang.Text("Download.Comp.Detail.FileList.Updated", Lang.TimeSpan(file.ReleaseDate - DateTime.Now))); - if (file.Status != ModComp.CompFileStatus.Release) - info.Add(file.StatusDescription); - LabVersionInfo.Text = string.Join(" | ", info); + LabVersionInfo.Text = file.DisplayName; + _AddTag(file.StatusDescription); + foreach (var loader in file.ModLoaders) + _AddTag(loader.ToString()); + LabDateInfo.Text = Lang.Text("Download.Comp.Detail.FileList.Updated", file.ReleaseDate.ToString("G")); // 2. 必要前置 / 可选前置 FillDependencySection(LabReqTitle, PanReqDeps, file.Dependencies, @@ -78,6 +73,26 @@ private void Populate(ModComp.CompFile file) Lang.Text("Download.Comp.Detail.FileList.OptionalDependencies")); } + private void _AddTag(string text) + { + var tag = new Border + { + Background = new SolidColorBrush(Color.FromArgb(17, 0, 0, 0)), + Padding = new Thickness(3d, 1d, 3d, 1d), + CornerRadius = new CornerRadius(3d), + Margin = new Thickness(0d, 0d, 3d, 0d), + SnapsToDevicePixels = true, + UseLayoutRounding = false + }; + tag.Child = new TextBlock + { + Text = text, + Foreground = new SolidColorBrush(Color.FromRgb(134, 134, 134)), + FontSize = 11d + }; + PanVersionTags.Children.Add(tag); + } + /// /// 填充一个前置分区:标题 + 可点击的前置项(点击以该前置工程关闭弹窗)。 /// @@ -92,13 +107,8 @@ private void FillDependencySection(TextBlock header, StackPanel panel, List