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