using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Markup; using PCL.Core.App.Localization; namespace PCL; [ContentProperty("SearchTags")] public partial class PageComp { /// /// 每页展示的结果数量。 /// public const int pageSize = 40; public int page; public ModComp.CompProjectStorage storage = new(); // 结果 UI 化 private void Load_OnFinish() { try { ModBase.Log($"[Comp] 开始可视化{TypeNameSpaced}列表,已储藏 {storage.results.Count} 个结果,当前在第 {page + 1} 页"); // 列表项 PanProjects.Children.Clear(); var index = Math.Min(page * pageSize, storage.results.Count - 1); foreach (var result in storage.results.GetRange(index, Math.Min(storage.results.Count - index, pageSize))) { var showQuickDownload = result.Type != ModComp.CompType.ModPack && result.Type != ModComp.CompType.DataPack; PanProjects.Children.Add(result.ToCompItem(loader.input.gameVersion is null, loader.input.modLoader == ModComp.CompLoaderType.Any && (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack), showQuickDownload)); } // 页码 CardPages.Visibility = storage.results.Count > 40 || storage.curseForgeOffset < storage.curseForgeTotal || storage.modrinthOffset < storage.modrinthTotal ? Visibility.Visible : Visibility.Collapsed; LabPage.Text = Lang.Number(page + 1, "N0"); BtnPageFirst.IsEnabled = page > 1; BtnPageFirst.Opacity = page > 1 ? 1d : 0.2d; BtnPageLeft.IsEnabled = page > 0; BtnPageLeft.Opacity = page > 0 ? 1d : 0.2d; var isRightEnabled = storage.results.Count > pageSize * (page + 1) || storage.curseForgeOffset < storage.curseForgeTotal || storage.modrinthOffset < storage.modrinthTotal; // 由于 WPF 的未知 bug,读取到的 IsEnabled 可能是错误的值(#3319) BtnPageRight.IsEnabled = isRightEnabled; BtnPageRight.Opacity = isRightEnabled ? 1d : 0.2d; // 错误信息 if (storage.errorMessage is null) { HintError.Visibility = Visibility.Collapsed; } else { HintError.Visibility = Visibility.Visible; HintError.Text = storage.errorMessage; } // 强制返回顶部 ScrollToTop(); } catch (Exception ex) { ModBase.Log( ex, $"可视化{TypeNameSpaced}列表出错", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Download.Comp.Error.OperationFailed")); } } // 自动重试 private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState) { switch (loader.State) { case ModBase.LoadState.Failed: { var errorMessage = ""; if (loader.Error is not null) errorMessage = loader.Error.Message; if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson"))) { ModBase.Log($"[Download] 下载的{TypeNameSpaced}列表 json 文件损坏,已自动重试", ModBase.LogLevel.Debug); ((MyPageRight)Parent).PageLoaderRestart(); } break; } } } // 切换页码 private void BtnPageFirst_Click(object sender, EventArgs e) { ChangePage(0); } private void BtnPageLeft_Click(object sender, EventArgs e) { ChangePage(page - 1); } private void BtnPageRight_Click(object sender, EventArgs e) { ChangePage(page + 1); } private void ChangePage(int newPage) { CardPages.IsEnabled = false; page = newPage; ModMain.frmMain.BackToTop(); ModBase.Log($"[Download] {TypeName}:切换到第 {page + 1} 页"); ModBase.RunInThread(() => { Thread.Sleep(100); // 等待向上滚的动画结束 ModBase.RunInUi(() => CardPages.IsEnabled = true); loader.Start(); }); } // 安装已有整合包按钮 private void BtnSearchInstallModPack_Click(object sender, EventArgs e) { ModModpack.ModpackInstall(); } /// /// 刷新所有已显示项目的收藏状态 /// public void RefreshAllFavoriteStatus() { try { foreach (var item in PanProjects.Children) if (item is MyCompItem) ((MyCompItem)item).RefreshFavoriteStatus(); } catch (Exception ex) { ModBase.Log(ex, "刷新收藏状态时出错"); } } #region 属性 /// /// 用于 XAML 快速设置的 Tag 下拉框列表。 /// public ItemCollection SearchTags => ComboSearchTag.Items; public static readonly DependencyProperty SupportCurseForgeProperty = DependencyProperty.Register("SupportCurseForge", typeof(bool), typeof(PageComp), new PropertyMetadata(true)); public bool SupportCurseForge { get => (bool)GetValue(SupportCurseForgeProperty); set => SetValue(SupportCurseForgeProperty, value); } public static readonly DependencyProperty SupportModrinthProperty = DependencyProperty.Register("SupportModrinth", typeof(bool), typeof(PageComp), new PropertyMetadata(true)); public bool SupportModrinth { get => (bool)GetValue(SupportModrinthProperty); set => SetValue(SupportModrinthProperty, value); } /// /// 英文前后不含空格的可读资源类型名,例如 "Mod"、"整合包"。 /// public string TypeName => ModComp.GetCompTypeName(PageType); /// /// 英文前后含一个空格的可读资源类型名,例如 " Mod "、"整合包"。 /// public string TypeNameSpaced => TypeName; /// /// 该页面对应的资源类型。 /// public ModComp.CompType PageType { get => field; set { if (field == value) return; field = value; BtnSearchInstallModPack.Visibility = value == ModComp.CompType.ModPack ? Visibility.Visible : Visibility.Collapsed; loader.name = Lang.Text("Download.Comp.List.Source.ResourceFetch", TypeName); PanSearchBox.HintText = ModComp.GetCompSearchName(value); Load.Text = ModComp.GetCompLoadingName(value); } } = (ModComp.CompType)(-1); #endregion #region 加载 /// /// 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。 /// public static McInstance targetVersion; // 在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项 public ModLoader.LoaderTask loader; private bool isLoaderInited; public PageComp() { loader = new ModLoader.LoaderTask(Lang.Text("Download.Comp.List.Source.ResourceFetch", "XXX"), ModComp.CompProjectsGet, LoaderInput) { reloadTimeout = 60 * 1000 }; Loaded += PageCompControls_Inited; IsVisibleChanged += PageComp_IsVisibleChanged; InitializeComponent(); Load.StateChanged += Load_State; BtnPageFirst.Click += BtnPageFirst_Click; BtnPageLeft.Click += BtnPageLeft_Click; BtnPageRight.Click += BtnPageRight_Click; PanSearchBox.Search += (_, _) => StartNewSearch(); PanSearchBox.KeyDown += EnterTrigger; TextSearchVersion.KeyDown += EnterTrigger; BtnSearchReset.Click += (_, _) => ResetFilter(); BtnSearchInstallModPack.Click += BtnSearchInstallModPack_Click; } private void PageCompControls_Inited(object sender, EventArgs e) { // 不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值 if (targetVersion is not null) { // 设置目标 ResetFilter(); // 重置筛选器 TextSearchVersion.Text = targetVersion.Info.VanillaName; MyComboBoxItem GetTargetItemByName(string name) { foreach (MyComboBoxItem Item in ComboSearchLoader.Items) if (string.Equals(Item.Content?.ToString(), name, StringComparison.OrdinalIgnoreCase)) return Item; return (MyComboBoxItem)ComboSearchLoader.Items[0]; } ; if (targetVersion.Info.HasForge) ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge"); else if (targetVersion.Info.HasFabric) ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric"); else if (targetVersion.Info.HasNeoForge) ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge"); else if (targetVersion.Info.HasQuilt) ComboSearchLoader.SelectedItem = GetTargetItemByName("Quilt"); targetVersion = null; // 如果已经完成请求,则重新开始 if (isLoaderInited) StartNewSearch(); ScrollToHome(); } // 加载器初始化 if (isLoaderInited) return; isLoaderInited = true; ((MyPageRight)Parent).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, loader, _ => Load_OnFinish(), LoaderInput); // 将最高 Drop 加入筛选 if (ModDownload.AllDrops is not null && ModDownload.AllDrops.Count != 0 && ModDownload.AllDrops.First() > 250) { var highestVersion = McInstanceInfo.DropToVersion(ModDownload.AllDrops.First()); if ((((MyComboBoxItem)TextSearchVersion.Items[1]).Content.ToString() ?? "") != (highestVersion ?? "")) // 0 是全部 TextSearchVersion.Items.Insert(1, new MyComboBoxItem { Content = highestVersion }); } // 根据页面类型控制加载器选择的显示 if (PageType == ModComp.CompType.Shader) { LabLoader.Visibility = Visibility.Visible; ComboSearchLoader.Visibility = Visibility.Collapsed; ComboSearchShaderLoader.Visibility = Visibility.Visible; } else if (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack) { LabLoader.Visibility = Visibility.Visible; ComboSearchLoader.Visibility = Visibility.Visible; ComboSearchShaderLoader.Visibility = Visibility.Collapsed; } else { LabLoader.Visibility = Visibility.Collapsed; ComboSearchLoader.Visibility = Visibility.Collapsed; ComboSearchShaderLoader.Visibility = Visibility.Collapsed; } } private void PageComp_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { // 当页面变为可见时刷新收藏按钮状态 if (IsVisible) RefreshAllFavoriteStatus(); } private ModComp.CompProjectRequest LoaderInput() { var request = new ModComp.CompProjectRequest(PageType, storage, (page + 1) * pageSize); var gameVersion = TextSearchVersion.Text == Lang.Text("Download.Comp.Filter.Version.AllInputAvailable") ? null : TextSearchVersion.Text.Contains(".") || TextSearchVersion.Text.Contains("w") ? TextSearchVersion.Text : null; var modLoader = ModComp.CompLoaderType.Any; if (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack) // 只有 Mod 考虑加载器 { modLoader = (ModComp.CompLoaderType)ModBase.Val(((MyComboBoxItem)ComboSearchLoader.SelectedItem).Tag); if (gameVersion is not null && gameVersion.Contains(".") && ModBase.Val(gameVersion.Split(".")[1]) < 14d && modLoader == ModComp.CompLoaderType.Forge) // 1.14- // 选择了 Forge modLoader = ModComp.CompLoaderType.Any; // 此时,视作没有筛选 Mod Loader(因为部分老 Mod 没有设置自己支持的加载器) } request.searchText = PanSearchBox.Text; request.gameVersion = gameVersion; var selectedTag = (ComboSearchTag.SelectedItem as FrameworkElement)?.Tag?.ToString(); var loaderTag = (ComboSearchShaderLoader.SelectedItem as FrameworkElement)?.Tag?.ToString(); request.tag = PageType == ModComp.CompType.Shader ? string.IsNullOrEmpty(loaderTag) ? selectedTag : selectedTag + loaderTag : selectedTag; request.modLoader = (ModComp.CompLoaderType)(PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack ? ModBase.Val(((MyComboBoxItem)ComboSearchLoader.SelectedItem).Tag) : (double)ModComp.CompLoaderType.Any); request.source = (ModComp.CompSourceType)ModBase.Val(((MyComboBoxItem)ComboSearchSource.SelectedItem).Tag); request.sort = (ModComp.CompSortType)ModBase.Val(((MyComboBoxItem)ComboSearchSort.SelectedItem).Tag); return request; } #endregion #region 搜索 // 搜索按钮 private void StartNewSearch() { page = 0; object argInput = LoaderInput(); if (loader.ShouldStart(ref argInput)) storage = new ModComp.CompProjectStorage(); // 避免连续搜索两次使得 CompProjectStorage 引用丢失(#1311) loader.Start(); } private void EnterTrigger(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) StartNewSearch(); } // 重置按钮 private void ResetFilter() { PanSearchBox.Text = ""; TextSearchVersion.Text = Lang.Text("Download.Comp.Filter.Version.AllInputAvailable"); TextSearchVersion.SelectedIndex = 0; ComboSearchSource.SelectedIndex = 0; ComboSearchTag.SelectedIndex = 0; ComboSearchLoader.SelectedIndex = 0; ComboSearchShaderLoader.SelectedIndex = 0; ComboSearchSort.SelectedIndex = 0; loader.lastFinishedTime = 0L; // 要求强制重新开始 } private void BtnSearchReset_Click(object sender, EventArgs e) { ResetFilter(); } #endregion }