feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,145 @@
<Grid x:Class="PCL.MyCompItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL"
xmlns:svgIcon="clr-namespace:PCL.Core.UI.Controls.SvgIcon;assembly=PCL.Core"
x:Name="PanBack" Height="64"
RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}"
SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="20" />
<RowDefinition Height="18" />
<RowDefinition Height="16" />
<RowDefinition Height="1.1*" />
</Grid.RowDefinitions>
<!-- Logo -->
<Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" IsHitTestVisible="False" SnapsToDevicePixels="True"
UseLayoutRounding="True"
HorizontalAlignment="Right" VerticalAlignment="Center" Width="50" Height="50">
<Border.Clip>
<RectangleGeometry Rect="0,0,50,50" RadiusX="6" RadiusY="6" />
</Border.Clip>
<local:MyImage x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality"
FallbackSource="pack://application:,,,/images/Icons/NoIcon.png" />
</Border>
<!-- 标题 -->
<StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Center" Orientation="Horizontal"
IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
<TextBlock x:Name="LabTitle" TextTrimming="WordEllipsis" FontSize="14"
Foreground="{DynamicResource ColorBrush1}" />
<TextBlock x:Name="LabTitleRaw" TextTrimming="WordEllipsis" FontSize="12"
Foreground="{StaticResource ColorBrushGray1}" Opacity="0.4" VerticalAlignment="Center"
Visibility="Collapsed" />
</StackPanel>
<!-- Tag 与 详情 -->
<StackPanel x:Name="PanTags" Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center"
Margin="-1,0,1,0" Visibility="Collapsed">
<!--<corelocal:BlurBorder Background="{DynamicResource ColorBrush6}" Padding="3,1" CornerRadius="3" Margin="0,0,4,0" SnapsToDevicePixels="True" UseLayoutRounding="False">
<TextBlock Text="科技" Foreground="{DynamicResource ColorBrush2}" FontSize="11" />
</corelocal:BlurBorder>-->
</StackPanel>
<TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Center" Margin="0,0,3,0.5"
TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray3}"
IsHitTestVisible="True" SnapsToDevicePixels="False" UseLayoutRounding="False"
ToolTipService.InitialShowDelay="100" ToolTipService.BetweenShowDelay="100"
ToolTipService.Placement="Relative" ToolTipService.PlacementRectangle="-16,-10,500,10000">
<TextBlock.ToolTip>
<ToolTip x:Name="ToolTipInfo" />
</TextBlock.ToolTip>
</TextBlock>
<!-- 下边栏 -->
<Grid Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="2" VerticalAlignment="Center"
IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnVersion1" Width="Auto" />
<ColumnDefinition x:Name="ColumnVersion2" Width="Auto" SharedSizeGroup="CompItemVersion" />
<ColumnDefinition x:Name="ColumnVersion3" Width="0.25*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="LabDownload" />
<ColumnDefinition Width="0.25*" />
<ColumnDefinition x:Name="ColumnTime1" Width="Auto" />
<ColumnDefinition x:Name="ColumnTime2" Width="Auto" MinWidth="57" SharedSizeGroup="CompItemTime" />
<ColumnDefinition x:Name="ColumnTime3" Width="0.25*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="CompItemSource" />
<ColumnDefinition Width="0.25*" />
</Grid.ColumnDefinitions>
<svgIcon:SvgIcon x:Name="SvgIconVersion"
Grid.Column="0"
IconBrush="{StaticResource ColorBrushGray3}"
Stretch="Uniform"
Height="12"
Icon="lucide/layout-list" />
<TextBlock x:Name="LabVersion"
Grid.Column="1"
Margin="4,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray3}"
FontSize="12"
TextTrimming="WordEllipsis" />
<svgIcon:SvgIcon x:Name="SvgIconDownload"
Grid.Column="3"
IconBrush="{StaticResource ColorBrushGray4}"
Stretch="Uniform"
Height="12"
Icon="lucide/download" />
<TextBlock x:Name="LabDownload"
Grid.Column="4"
Margin="5,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray4}"
FontSize="12" />
<svgIcon:SvgIcon x:Name="SvgIconTime"
Grid.Column="6"
IconBrush="{StaticResource ColorBrushGray4}"
Stretch="Uniform"
Height="12"
Margin="0,0,1,0"
Icon="lucide/arrow-up-from-line" />
<TextBlock x:Name="LabTime"
Grid.Column="7"
Margin="5,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray4}"
FontSize="12" />
<svgIcon:SvgIcon x:Name="SvgIconSource"
Grid.Column="9"
IconBrush="{StaticResource ColorBrushGray4}"
Stretch="Uniform"
Height="12"
Icon="lucide/globe" />
<TextBlock x:Name="LabSource"
Grid.Column="10"
Margin="5,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray4}"
FontSize="12" />
</Grid>
<!-- 右侧按钮组 -->
<StackPanel x:Name="PanButtons" Grid.Column="5" Grid.Row="0" Grid.RowSpan="5"
Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,5,0" SnapsToDevicePixels="False" UseLayoutRounding="False" Opacity="0">
<!-- 快速下载按钮 -->
<local:MyIconButton x:Name="BtnDownload" Height="25" Width="25" SvgIcon="lucide/download"
Margin="0,0,4,0" Visibility="Collapsed"
ToolTip="{DynamicResource Download.Comp.QuickDownload.Btn.ToolTip}" />
<!-- 删除收藏按钮 -->
<local:MyIconButton x:Name="BtnDelete" Height="25" Width="25" SvgIcon="lucide/heart" />
</StackPanel>
</Grid>
@@ -0,0 +1,467 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace PCL;
public partial class MyCompItem
{
private string stateLast;
/// <summary>
/// 是否允许交互。目前仅用于 PageDownloadCompDetail 的顶部栏展示:若关闭碰撞检测,则无法展开 Tooltip。
/// </summary>
public bool CanInteraction { get; set; } = true;
public void RefreshColor(object sender, EventArgs e)
{
if (!CanInteraction)
return;
// 判断当前颜色
string stateNew;
int time;
if (IsMouseOver)
{
if (isMouseDown)
{
stateNew = "MouseDown";
time = 120;
}
else
{
stateNew = "MouseOver";
time = 120;
}
}
else
{
stateNew = "Idle";
time = 180;
}
if ((stateLast ?? "") == (stateNew ?? ""))
return;
stateLast = stateNew;
// 触发颜色动画
if (IsLoaded && ModAnimation.AniControlEnabled == 0) // 防止默认属性变更触发动画
{
// 有动画
var ani = new List<ModAnimation.AniData>();
if (IsMouseOver)
{
if (PanButtons is not null && _HasActionButtons)
ani.Add(ModAnimation.AaOpacity(PanButtons, 1d - PanButtons.Opacity, (int)Math.Round(time * 0.35d),
(int)Math.Round(time * 0.15d)));
ani.AddRange(new[]
{
ModAnimation.AaColor(RectBack, Border.BackgroundProperty,
isMouseDown ? "ColorBrush6" : "ColorBrushBg1", time),
ModAnimation.AaOpacity(RectBack, 1d - RectBack.Opacity, time,
ease: new ModAnimation.AniEaseOutFluent())
});
if (isMouseDown)
ani.Add(ModAnimation.AaScaleTransform(RectBack,
0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
ease: new ModAnimation.AniEaseOutFluent()));
else
ani.Add(ModAnimation.AaScaleTransform(RectBack,
1d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
ease: new ModAnimation.AniEaseOutFluent()));
}
else
{
if (PanButtons is not null && _HasActionButtons)
ani.Add(ModAnimation.AaOpacity(PanButtons, -PanButtons.Opacity, (int)Math.Round(time * 0.4d)));
ani.AddRange(new[]
{
ModAnimation.AaOpacity(RectBack, -RectBack.Opacity, time),
ModAnimation.AaColor(RectBack, Border.BackgroundProperty,
isMouseDown ? "ColorBrush6" : "ColorBrush7", time),
ModAnimation.AaScaleTransform(RectBack, 0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX,
time, ease: new ModAnimation.AniEaseOutFluent()),
ModAnimation.AaScaleTransform(RectBack, -0.196d, 1, after: true)
});
}
ModAnimation.AniStart(ani, "CompItem Color " + Uuid);
}
else
{
// 无动画
ModAnimation.AniStop("CompItem Color " + Uuid);
if (_RectBack is not null)
RectBack.Opacity = 0d;
if (PanButtons is not null)
PanButtons.Opacity = 0d;
}
}
#region
public int Uuid = ModBase.GetUuid();
// Logo
public string Logo
{
get => PathLogo.Source;
set => PathLogo.Source = value;
}
// 标题
public string Title
{
get => LabTitle.Text;
set
{
if ((LabTitle.Text ?? "") == (value ?? ""))
return;
LabTitle.Text = value;
}
}
// 副标题
public string SubTitle
{
get => LabTitleRaw?.Text ?? "";
set
{
if ((LabTitleRaw.Text ?? "") == (value ?? ""))
return;
LabTitleRaw.Text = value;
LabTitleRaw.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible;
}
}
// 描述
public string Description
{
get => LabInfo.Text;
set
{
if ((LabInfo.Text ?? "") == (value ?? ""))
return;
LabInfo.Text = value;
}
}
public MyCompItem()
{
InitializeComponent();
Click += (sender, e) => MyCompItem_Click((MyCompItem)sender, e);
PreviewMouseLeftButtonUp += Button_MouseUp;
PreviewMouseLeftButtonDown += Button_MouseDown;
MouseLeave += Button_MouseLeave;
PreviewMouseLeftButtonUp += Button_MouseLeave;
MouseEnter += RefreshColor;
MouseLeave += RefreshColor;
MouseLeftButtonDown += RefreshColor;
MouseLeftButtonUp += RefreshColor;
// Handles
LabInfo.MouseEnter += LabInfo_MouseEnter;
BtnDelete.Click += BtnDelete_Click;
BtnDownload.Click += _BtnDownload_Click;
}
// 指向时扩展描述
private void LabInfo_MouseEnter(object sender, MouseEventArgs e)
{
if (IsTextTrimmed(LabInfo))
{
ToolTipInfo.Content = LabInfo.Text;
ToolTipInfo.Width = LabInfo.ActualWidth + 25d;
LabInfo.ToolTip = ToolTipInfo;
}
else
{
LabInfo.ToolTip = null;
}
}
private bool IsTextTrimmed(TextBlock textBlock)
{
var typeface = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight,
textBlock.FontStretch);
var formattedText = new FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground, ModBase.dpi);
return formattedText.Width > textBlock.ActualWidth;
}
// Tag
public List<string> Tags
{
set
{
PanTags.Children.Clear();
PanTags.Visibility = value.Any() ? Visibility.Visible : Visibility.Collapsed;
foreach (var tagText in value)
{
var newTag = 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
};
var tagTextBlock = new TextBlock
{
Text = tagText,
Foreground = new SolidColorBrush(Color.FromRgb(134, 134, 134)),
FontSize = 11d
};
newTag.Child = tagTextBlock;
PanTags.Children.Add(newTag);
}
}
}
// ‘收藏按钮
public bool ShowFavoriteBtn
{
get => BtnDelete.Visibility == Visibility.Visible;
set
{
BtnDelete.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
_UpdatePanButtons();
}
}
// 快速下载按钮
public bool ShowDownloadBtn
{
get => BtnDownload.Visibility == Visibility.Visible;
set
{
BtnDownload.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
_UpdatePanButtons();
}
}
/// <summary>右侧是否存在任意可见的操作按钮(收藏 / 下载),用于决定悬停时是否淡入按钮区。</summary>
private bool _HasActionButtons => ShowFavoriteBtn || ShowDownloadBtn;
private void _UpdatePanButtons()
{
if (PanButtons is null) return;
PanButtons.Visibility = _HasActionButtons ? Visibility.Visible : Visibility.Collapsed;
}
/// <summary>
/// 刷新收藏状态(仅更新图标,不影响 hover 展示逻辑)
/// </summary>
public void RefreshFavoriteStatus()
{
if (Tag is not ModComp.CompProject project) return;
var isFavourite = ModComp.CompFavorites.IsFavourite(project.Id);
BtnDelete.SvgIcon = isFavourite ? "lucide/heart-filled" : "lucide/heart";
}
#endregion
#region
// 触发点击事件
public event ClickEventHandler? Click;
public delegate void ClickEventHandler(object sender, MouseButtonEventArgs e);
private void BtnDelete_Click(object sender, EventArgs e)
{
if (PanButtons.Opacity > 0d && Tag is ModComp.CompProject)
{
var project = (ModComp.CompProject)Tag;
ModComp.CompFavorites.ShowMenu(project, (UIElement)sender, () => RefreshFavoriteStatus());
}
}
private void _BtnDownload_Click(object sender, EventArgs e)
{
if (PanButtons.Opacity > 0d && Tag is ModComp.CompProject project)
ModComp.QuickDownload(project);
}
private void MyCompItem_Click(MyCompItem sender, EventArgs e)
{
// 记录当前展开的卡片标题(#2712)
var titles = new List<string>();
if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.CompDetail)
{
foreach (MyCard Card in ModMain.frmDownloadCompDetail.PanResults.Children)
if (!string.IsNullOrEmpty(Card.Title) && !Card.IsSwapped)
titles.Add(Card.Title);
ModBase.Log("[Comp] 记录当前已展开的卡片:" + string.Join("、", titles));
var additional = ModMain.frmMain.pageCurrent.additional.Value;
ModMain.frmMain.pageCurrent.additional = additional with { ExpandedTitles = titles };
}
// 打开详情页
var targetType = default(ModComp.CompType);
string targetVersion = null;
var targetLoader = ModComp.CompLoaderType.Any;
if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.Download)
{
if (ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.DownloadCompFavorites)
{
targetVersion = "";
targetLoader = ModComp.CompLoaderType.Any;
}
else
{
// 从下载页进入
switch (ModMain.frmMain.PageCurrentSub)
{
case FormMain.PageSubType.DownloadMod:
{
targetType = ModComp.CompType.Mod;
targetVersion = ModMain.frmDownloadMod.Content.loader.input.gameVersion;
targetLoader = ModMain.frmDownloadMod.Content.loader.input.modLoader;
break;
}
case FormMain.PageSubType.DownloadPack:
{
targetType = ModComp.CompType.ModPack;
targetVersion = ModMain.frmDownloadPack.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadDataPack:
{
targetType = ModComp.CompType.DataPack;
targetVersion = ModMain.frmDownloadDataPack.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadResourcePack:
{
targetType = ModComp.CompType.ResourcePack;
targetVersion = ModMain.frmDownloadResourcePack.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadShader:
{
targetType = ModComp.CompType.Shader;
targetVersion = ModMain.frmDownloadShader.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadWorld:
{
targetType = ModComp.CompType.World;
targetVersion = ModMain.frmDownloadWorld.Content.loader.input.gameVersion;
break;
}
}
}
}
else if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.InstanceSetup)
{
// 从实例设置页进入(查看整合包信息)
targetType = ModComp.CompType.ModPack;
}
else
{
// 从详情页进入(查看前置)
targetType = ModComp.CompType.Any; // 允许任意类别
var additional = ModMain.frmMain.pageCurrent.additional.Value;
targetVersion = additional.TargetVersion;
targetLoader = additional.TargetLoader;
}
ModMain.frmMain.PageChange(new FormMain.PageStackData
{
page = FormMain.PageType.CompDetail,
additional = ((ModComp.CompProject)sender.Tag, new List<string>(), targetVersion, targetLoader, targetType, null)
});
}
// 鼠标点击判定
private bool isMouseDown;
// 触发点击事件
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!isMouseDown)
return;
Click?.Invoke(sender, e);
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!CanInteraction)
return;
// 检查点击位置是否在按钮区域内
var clickPosition = e.GetPosition(this);
var isClickOnButton = false;
if (PanButtons.Visibility == Visibility.Visible)
isClickOnButton = _IsClickOnActionButton(BtnDelete, clickPosition) ||
_IsClickOnActionButton(BtnDownload, clickPosition);
// 如果点击在按钮上,不处理主项目点击事件
if (isClickOnButton) return;
// 如果点击在其他区域,按原逻辑处理
// 也要检查是否点击在LabInfo区域(支持ToolTip点击)
var isClickOnLabInfo = false;
if (LabInfo.Visibility == Visibility.Visible)
{
var labInfoBounds = new Rect(LabInfo.TranslatePoint(new Point(0d, 0d), this), LabInfo.RenderSize);
isClickOnLabInfo = labInfoBounds.Contains(clickPosition);
}
if (IsMouseDirectlyOver || isClickOnLabInfo) isMouseDown = true;
}
private void Button_MouseLeave(object sender, object e)
{
isMouseDown = false;
}
// 判断点击是否落在某个操作按钮(收藏 / 下载)上
private bool _IsClickOnActionButton(FrameworkElement button, Point clickPosition)
{
if (button is null || button.Visibility != Visibility.Visible) return false;
var bounds = new Rect(button.TranslatePoint(new Point(0d, 0d), this), button.RenderSize);
return bounds.Contains(clickPosition);
}
#endregion
#region
private Border _RectBack;
public Border RectBack
{
get
{
if (_RectBack is null)
{
var rect = new Border
{
Name = "RectBack",
CornerRadius = new CornerRadius(3d),
RenderTransform = new ScaleTransform(0.8d, 0.8d),
RenderTransformOrigin = new Point(0.5d, 0.5d),
BorderThickness = new Thickness(ModBase.GetWPFSize(1d)),
SnapsToDevicePixels = true,
IsHitTestVisible = false,
Opacity = 0d
};
rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7");
rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6");
SetColumnSpan(rect, 999);
SetRowSpan(rect, 999);
Children.Insert(0, rect);
_RectBack = rect;
// <!--<corelocal:BlurBorder x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True"
// IsHitTestVisible = "False" Opacity="0" BorderThickness="1"
// Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
}
return _RectBack;
}
}
#endregion
}
@@ -0,0 +1,146 @@
<local:MyScrollViewer x:Class="PCL.PageComp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PCL"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<local:MyScrollViewer.Resources>
<BooleanToVisibilityConverter x:Key="VisibilityConverter" />
</local:MyScrollViewer.Resources>
<StackPanel Orientation="Vertical" Margin="25">
<StackPanel x:Name="PanAlways">
<local:MySearchBox x:Name="PanSearchBox" Margin="0,0,0,15" SearchButtonVisibility="Visible" HintText="{DynamicResource Download.Comp.Search.Hint}" />
<local:MyCard Margin="0,0,0,15">
<Grid Margin="7" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.0*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.0*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="8" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<!--来源-->
<TextBlock x:Name="LabSource" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Left" Text="{DynamicResource Download.Comp.Filter.Source}"
Margin="5,0,5,0" />
<local:MyComboBox x:Name="ComboSearchSource" Grid.Row="0" Grid.Column="1">
<local:MyComboBoxItem Content="{DynamicResource Common.Option.All}" Tag="3" IsSelected="True" />
<local:MyComboBoxItem Content="CurseForge" Tag="1"
Visibility="{Binding SupportCurseForge, Converter={StaticResource VisibilityConverter}}" />
<local:MyComboBoxItem Content="Modrinth" Tag="2"
Visibility="{Binding SupportModrinth, Converter={StaticResource VisibilityConverter}}" />
</local:MyComboBox>
<!--Tags-->
<TextBlock x:Name="LabTag" VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left"
Text="{DynamicResource Download.Comp.Filter.Tag}" Margin="5,0,5,0" />
<local:MyComboBox x:Name="ComboSearchTag" Grid.Column="3" MaxDropDownHeight="320" />
<!--排序方式-->
<TextBlock x:Name="LabSort" VerticalAlignment="Center" Grid.Row="0" Grid.Column="4"
HorizontalAlignment="Left" Text="{DynamicResource Download.Comp.Filter.Sort}"
Margin="5,0,5,0" />
<local:MyComboBox x:Name="ComboSearchSort" Grid.Row="0" Grid.Column="5" MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Common.Option.Default}" Tag="1"
IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Relevance}" Tag="2" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Downloads}" Tag="3" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Follows}" Tag="4" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Latest}" Tag="5" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Updated}" Tag="6" />
</local:MyComboBox>
<!--版本-->
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{DynamicResource Download.Comp.Filter.Version}" Margin="5,0,5,0"
Grid.Row="2" />
<local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" IsEditable="True"
MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Version.Any}"
IsSelected="True" />
<local:MyComboBoxItem Content="1.21.11" />
<local:MyComboBoxItem Content="1.21.1" />
<local:MyComboBoxItem Content="1.20.6" />
<local:MyComboBoxItem Content="1.20.1" />
<local:MyComboBoxItem Content="1.19.4" />
<local:MyComboBoxItem Content="1.19.2" />
<local:MyComboBoxItem Content="1.18.2" />
<local:MyComboBoxItem Content="1.16.5" />
<local:MyComboBoxItem Content="1.12.2" />
<local:MyComboBoxItem Content="1.10.2" />
<local:MyComboBoxItem Content="1.8.9" />
<local:MyComboBoxItem Content="1.7.10" />
</local:MyComboBox>
<!--加载器-->
<TextBlock x:Name="LabLoader" VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{DynamicResource Download.Comp.Filter.Loader}"
Margin="5,0,5,0" Grid.Row="2" Grid.Column="2" />
<local:MyComboBox x:Name="ComboSearchLoader" Grid.Row="2" Grid.Column="3" MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.Any}"
IsSelected="True" Tag="0" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Forge}" Tag="1" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.NeoForge}" Tag="6" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Fabric}" Tag="4" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Quilt}" Tag="5" />
</local:MyComboBox>
<local:MyComboBox x:Name="ComboSearchShaderLoader" Visibility="Collapsed" Grid.Row="2"
Grid.Column="3" MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.AnyShader}" Tag=""
IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.VanillaAvailable}"
Tag="/vanilla"
ToolTip="{DynamicResource Download.Comp.Filter.Loader.VanillaAvailable.ToolTip}" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Iris}" Tag="/iris" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.OptiFine}" Tag="/optifine" />
</local:MyComboBox>
<!--按钮-->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="6">
<local:MyIconButton x:Name="BtnSearchReset"
SvgIcon="lucide/rotate-ccw"
ToolTip="{DynamicResource Download.Comp.Filter.Reset}" LogoScale="0.9"
Margin="3,0,0,0" />
<local:MyIconButton x:Name="BtnSearchInstallModPack"
SvgIcon="lucide/download"
Visibility="Collapsed"
ToolTip="{DynamicResource Download.Comp.Filter.InstallModpack.ToolTip}"
LogoScale="0.9"
ToolTipService.HorizontalOffset="-55" ToolTipService.VerticalOffset="5" />
</StackPanel>
</Grid>
</local:MyCard>
</StackPanel>
<StackPanel x:Name="PanContent">
<local:MyHint x:Name="HintError" HasBorder="True"
Text="{DynamicResource Download.Comp.Hint.ModrinthUnreachable}"
Theme="Yellow" Margin="0,0,0,15" />
<local:MyCard x:Name="CardProjects" UseAnimation="False">
<StackPanel x:Name="PanProjects" Margin="12" />
</local:MyCard>
<local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
<local:MyIconButton x:Name="BtnPageFirst" IsEnabled="False" Opacity="0.2" Margin="0,0.5,5,0.5"
SvgIcon="lucide/chevrons-left" />
<local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.2" Margin="0,0,5,0"
SvgIcon="lucide/chevron-left" />
<TextBlock x:Name="LabPage" Text="1"
Margin="8,0,13,0" FontSize="15" VerticalAlignment="Center"
Foreground="{DynamicResource ColorBrush3}" />
<local:MyIconButton x:Name="BtnPageRight" Margin="0,0,30,0"
SvgIcon="lucide/chevron-right" />
</StackPanel>
</local:MyCard>
</StackPanel>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True"
x:Name="PanLoad" UseAnimation="False" Margin="40,50">
<local:MyLoading Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
@@ -0,0 +1,389 @@
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
{
/// <summary>
/// 每页展示的结果数量。
/// </summary>
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();
}
/// <summary>
/// 刷新所有已显示项目的收藏状态
/// </summary>
public void RefreshAllFavoriteStatus()
{
try
{
foreach (var item in PanProjects.Children)
if (item is MyCompItem)
((MyCompItem)item).RefreshFavoriteStatus();
}
catch (Exception ex)
{
ModBase.Log(ex, "刷新收藏状态时出错");
}
}
#region
/// <summary>
/// 用于 XAML 快速设置的 Tag 下拉框列表。
/// </summary>
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);
}
/// <summary>
/// 英文前后不含空格的可读资源类型名,例如 "Mod"、"整合包"。
/// </summary>
public string TypeName => ModComp.GetCompTypeName(PageType);
/// <summary>
/// 英文前后含一个空格的可读资源类型名,例如 " Mod "、"整合包"。
/// </summary>
public string TypeNameSpaced => TypeName;
/// <summary>
/// 该页面对应的资源类型。
/// </summary>
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
/// <summary>
/// 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。
/// </summary>
public static McInstance targetVersion;
// 在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项
public ModLoader.LoaderTask<ModComp.CompProjectRequest, int> loader;
private bool isLoaderInited;
public PageComp()
{
loader = new ModLoader.LoaderTask<ModComp.CompProjectRequest, int>(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
}
@@ -0,0 +1,67 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadCompDetail"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<Grid Margin="25,25,25,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<local:MyCard Margin="0,0,0,25" x:Name="CardIntro">
<StackPanel Margin="22,20,2,20" x:Name="PanIntro">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<local:MyIconTextButton x:Name="BtnIntroWeb" Text="CurseForge"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/globe"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnIntroWiki"
Text="{DynamicResource Download.Comp.Detail.McMod}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/globe"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnIntroCopy"
Text="{DynamicResource Download.Comp.Detail.CopyName}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/copy"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnIntroLinkCopy"
Text="{DynamicResource Download.Comp.Detail.CopyLink}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/copy"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnTranslate"
Text="{DynamicResource Download.Comp.Detail.TranslateDescription}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/earth"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnFavorites"
Text="{DynamicResource Download.Comp.Detail.Favorite}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/heart"
Margin="0,0,2,0" HorizontalAlignment="Left" />
</StackPanel>
</StackPanel>
</local:MyCard>
<StackPanel Grid.Row="1" Grid.RowSpan="2" Name="PanMain" Grid.IsSharedSizeScope="True">
<local:MyCard Margin="0,0,0,15" x:Name="CardFilter">
<StackPanel>
<StackPanel x:Name="PanInstanceFilter" Margin="10,10,0,5" Orientation="Horizontal"
VerticalAlignment="Top" HorizontalAlignment="Left" />
<StackPanel x:Name="PanModLoaderFilter" Margin="10,5,0,10" Orientation="Horizontal"
VerticalAlignment="Top" HorizontalAlignment="Left" />
</StackPanel>
</local:MyCard>
<StackPanel Name="PanResults" />
</StackPanel>
<local:MyCard Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="0,0,0,8">
<local:MyLoading Text="{DynamicResource Download.Version.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</Grid>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,1183 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using FluentValidation;
using PCL.Core.App;
using PCL.Core.App.Localization;
using PCL.Core.Logging;
using PCL.Core.Minecraft.ResourceProject;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Core.Utils.Validate;
using PCL.Network;
using PCL.Network.Loaders;
using Control = System.Windows.Forms.Control;
namespace PCL;
public partial class PageDownloadCompDetail
{
// 资源下载;整合包另存为
public static Dictionary<ModComp.CompType, string> cachedFolder = new(); // 仅在本次缓存的下载文件夹
private MyCompItem _compItem;
private bool _isFirstInit = true;
private void Init()
{
ModAnimation.AniControlEnabled += 1;
_project = ModMain.frmMain.pageCurrent.additional.Value.CompProject;
PanBack.ScrollToHome();
// 重启加载器
if (_isFirstInit)
// 在 Me.Initialized 已经初始化了加载器,不再重复初始化
_isFirstInit = false;
else
PageLoaderRestart(isForceRestart: true);
// 放置当前工程
if (_compItem is not null)
PanIntro.Children.Remove(_compItem);
_compItem = _project.ToCompItem(true, true);
_compItem.CanInteraction = false;
_compItem.ShowFavoriteBtn = false;
_compItem.Margin = new Thickness(-7, -7, 0d, 8d);
PanIntro.Children.Insert(0, _compItem);
// 决定按钮显示
BtnIntroWeb.Text = _project.FromCurseForge ? "CurseForge" : "Modrinth";
BtnIntroWiki.Visibility = Lang.IsChineseMainland && _project.WikiId != 0
? Visibility.Visible
: Visibility.Collapsed;
BtnTranslate.Visibility = Lang.IsChineseMainland
? Visibility.Visible
: Visibility.Collapsed;
RefreshFavoriteButton();
ModAnimation.AniControlEnabled -= 1;
}
// 整合包安装
public void Install_Click(MyListItem sender, EventArgs e)
{
try
{
// 获取基本信息
var file = (ModComp.CompFile)sender.Tag;
var loaderName =
$"{(_project.FromCurseForge ? "CurseForge" : "Modrinth")} {Lang.Text("Download.Comp.Detail.ModpackDownload")}{_project.TranslatedName} ";
// 获取实例名
var packName = _project.TranslatedName.Replace(".zip", "").Replace(".rar", "").Replace(".mrpack", "")
.Replace(@"\", "").Replace("/", "").Replace("|", "").Replace(":", "").Replace("<", "")
.Replace(">", "").Replace("*", "").Replace("?", "").Replace("\"", "").Replace(" ", "");
var validate = new FolderNameValidator(ModFolder.mcFolderSelected + "versions");
if (!validate.Validate(packName).IsValid)
packName = "";
var instanceName = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Detail.InputInstanceName"), "", packName, [validate]);
if (string.IsNullOrEmpty(instanceName))
return;
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
var target =
$@"{ModFolder.mcFolderSelected}versions\{instanceName}\原始整合包.{(_project.FromCurseForge ? "zip" : "mrpack")}";
var logoFileAddress = MyImage.GetTempPath(_compItem.Logo);
loaders.Add(new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadModpackFile"),
new List<DownloadFile> { file.ToNetFile(target, ModComp.DownloadReason.ModPack) })
{ ProgressWeight = 10d, block = true });
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.PrepareModpackInstall"),
_ => ModModpack.ModpackInstall(target, instanceName,
System.IO.File.Exists(logoFileAddress) ? logoFileAddress : null, file.ProjectId,
true)) { ProgressWeight = 0.1d });
// 启动
var loader = new ModLoader.LoaderCombo<string>(loaderName, loaders)
{
OnStateChanged = myLoader =>
{
switch (myLoader.State)
{
case ModBase.LoadState.Failed:
{
HintService.Hint(
Lang.Text("Download.Comp.Detail.Task.Failed", myLoader.name,
myLoader.Error.ToString()), HintType.Error);
break;
}
case ModBase.LoadState.Aborted:
{
HintService.Hint(Lang.Text("Download.Comp.Detail.Task.Cancelled", myLoader.name));
break;
}
case ModBase.LoadState.Loading:
{
return; // 不重新加载版本列表
}
}
ModDownloadLib.McInstallFailedClearFolder(myLoader);
}
};
loader.Start(Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName));
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"下载资源整合包失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
// 世界下载
public void InstallWorld_Click(MyListItem sender, EventArgs e)
{
try
{
// 获取基本信息
var file = (ModComp.CompFile)sender.Tag;
var loaderName = $"{(_project.FromCurseForge ? "CurseForge" : "Modrinth")} {Lang.Text("Download.Comp.Detail.WorldDownload")}{_project.TranslatedName} ";
// 确认默认保存位置
string defaultFolder = null;
var subFolder = @"saves\";
Func<McInstance, bool> isVersionSuitable = null;
// 获取资源所需的加载器
var allowedLoaders = new List<ModComp.CompLoaderType>();
if (file.ModLoaders.Any())
allowedLoaders = file.ModLoaders;
else if (_project.ModLoaders.Any()) allowedLoaders = _project.ModLoaders;
ModBase.Log("[Comp] 世界要求的加载器种类:" + (allowedLoaders.Any() ? allowedLoaders.Join(" / ") : "无要求"));
// 判断某个版本是否符合资源要求
isVersionSuitable = version =>
{
if (version is null)
return false;
if (!version.IsLoaded)
version.Load();
if (file.GameVersions.Any(v => v.Contains(".")) && !file.GameVersions.Any(v =>
v.Contains(".") && (v ?? "") == (version.Info.VanillaName ?? "")))
return false;
// 加载器
if (!allowedLoaders.Any())
return true; // 无要求
return false;
};
// 获取常规资源默认下载位置
if (cachedFolder.ContainsKey(file.Type) && !string.IsNullOrEmpty(cachedFolder[file.Type]))
{
defaultFolder = cachedFolder.GetOrDefault(file.Type,
ModInstanceList.McMcInstanceSelected?.PathIndie ?? ModBase.exePath);
ModBase.Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{defaultFolder}");
}
else if (ModInstanceList.McMcInstanceSelected is not null && isVersionSuitable(ModInstanceList.McMcInstanceSelected))
{
defaultFolder = $"{ModInstanceList.McMcInstanceSelected.PathIndie}{subFolder}";
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用当前实例作为默认下载位置:{defaultFolder}");
}
else
{
// 查找所有可能的实例
var needLoad = ModInstanceList.mcInstanceListLoader.State != ModBase.LoadState.Finished;
if (needLoad)
{
HintService.Hint(Lang.Text("Download.Comp.Detail.FindingApplicableInstance"));
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\", true);
}
var suitableVersions = ModInstanceList.mcInstanceList.Values.SelectMany(l => l)
.Where(v => isVersionSuitable(v)).Select(v => new DirectoryInfo($"{v.PathIndie}{subFolder}"));
if (suitableVersions.Any())
{
var selectedVersion = suitableVersions
.OrderByDescending(dir => dir.Exists ? dir.LastWriteTimeUtc : DateTime.MinValue)
.ThenByDescending(dir => dir.Exists ? dir.GetFiles().Length : -1).First(); // 先按文件夹更改时间降序
// 再按文件夹中的文件数量降序
defaultFolder = selectedVersion.FullName;
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用适合的游戏实例作为默认下载位置:{defaultFolder}");
}
else
{
defaultFolder = ModFolder.mcFolderSelected;
if (needLoad)
HintService.Hint(Lang.Text("Download.Comp.Detail.NoApplicableInstance"));
else
ModBase.Log("[Comp] 由于当前实例不兼容,使用当前的 MC 文件夹作为默认下载位置");
}
}
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Comp.Detail.SelectWorldInstallLocation"), file.FileName, Lang.Text("Download.Comp.Detail.WorldFile.Filter"),
defaultFolder);
if (string.IsNullOrEmpty(target))
return;
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
var targetPath = target.BeforeLast(@"\");
var logoFileAddress = MyImage.GetTempPath(_compItem.Logo);
loaders.Add(new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadWorldFile"),
new List<DownloadFile>
{
file.ToNetFile(target, ModComp.DownloadReason.Standalone, file.RawGameVersions.FirstOrDefault())
})
{ ProgressWeight = 10d, block = true });
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.InstallWorld"),
_ => ModBase.ExtractFile(target, targetPath, Encoding.UTF8)) { ProgressWeight = 0.1d, block = true });
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.CleanCache"),
_ => System.IO.File.Delete(target)));
// 启动
var loader = new ModLoader.LoaderCombo<int>(loaderName, loaders)
{ OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly };
loader.Start();
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"下载世界资源失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
public void Save_Click(object sender, EventArgs e)
{
// 获取点击项关联的文件对象
var file = 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
};
ModBase.RunInNewThread(() =>
{
try
{
var desc = file.Type switch
{
ModComp.CompType.ModPack => Lang.Text("Download.Comp.Type.Modpack"),
ModComp.CompType.Mod => Lang.Text("Download.Comp.Type.Mod"),
ModComp.CompType.ResourcePack => Lang.Text("Download.Comp.Type.ResourcePack"),
ModComp.CompType.Shader => Lang.Text("Download.Comp.Type.Shader"),
ModComp.CompType.DataPack => Lang.Text("Download.Comp.Type.DataPack"),
ModComp.CompType.World => Lang.Text("Download.Comp.Type.World"),
_ => ""
};
// 确认默认保存位置
string defaultFolder = null;
var allowedLoaders = new List<ModComp.CompLoaderType>();
if (file.Type != ModComp.CompType.ModPack)
{
var subFolder = "";
switch (file.Type)
{
case ModComp.CompType.Mod: subFolder = "mods\\"; break;
case ModComp.CompType.ResourcePack: subFolder = "resourcepacks\\"; break;
case ModComp.CompType.Shader: subFolder = "shaderpacks\\"; break;
case ModComp.CompType.World: subFolder = "saves\\"; break;
case ModComp.CompType.DataPack: subFolder = ""; break; // 导航到版本根目录
}
// 获取资源所需的加载器
if (file.ModLoaders.Any())
allowedLoaders = file.ModLoaders;
else if (_project.ModLoaders.Any()) allowedLoaders = _project.ModLoaders;
ModBase.Log(
$"[Comp] {desc}要求的加载器种类:{(allowedLoaders.Any() ? string.Join(" / ", allowedLoaders) : "")}");
// 判断某个版本是否符合资源要求 (局部函数)
Func<McInstance, bool> isVersionSuitable = version =>
{
if (version is null) return false;
if (!version.IsLoaded) version.Load();
// 只对 Mod 和数据包进行版本检测
if (file.Type == ModComp.CompType.Mod || file.Type == ModComp.CompType.DataPack)
if (file.GameVersions.Any(v => v.Contains(".")) &&
!file.GameVersions.Any(v => v.Contains(".") && v == version.Info.VanillaName))
return false;
// 加载器判定
if (!allowedLoaders.Any()) return true; // 无要求
if (allowedLoaders.Contains(ModComp.CompLoaderType.Forge) && version.Info.HasForge) return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.Forge) && version.Info.HasCleanroom)
return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.Fabric) &&
(version.Info.HasFabric || version.Info.HasLegacyFabric)) return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.NeoForge) && version.Info.HasNeoForge)
return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.LiteLoader) && version.Info.HasLiteLoader)
return true;
return false;
};
// 获取常规资源默认下载位置逻辑
if (cachedFolder.ContainsKey(file.Type) && !string.IsNullOrEmpty(cachedFolder[file.Type]))
{
defaultFolder = cachedFolder.GetOrDefault(file.Type,
ModInstanceList.McMcInstanceSelected?.PathIndie ?? ModBase.exePath);
ModBase.Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{defaultFolder}");
}
else if (ModInstanceList.McMcInstanceSelected is not null &&
isVersionSuitable(ModInstanceList.McMcInstanceSelected))
{
defaultFolder = $"{ModInstanceList.McMcInstanceSelected.PathIndie}{subFolder}";
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用当前实例作为默认下载位置:{defaultFolder}");
}
else
{
// 查找所有可能的实例
var needLoad = ModInstanceList.mcInstanceListLoader.State != ModBase.LoadState.Finished;
if (needLoad)
{
HintService.Hint(Lang.Text("Download.Comp.Detail.FindingApplicableInstance"));
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
ModLoader.LoaderFolderRunType.ForceRun, 1, "versions\\", true);
}
var suitableVersions = ModInstanceList.mcInstanceList.Values.SelectMany(l => l)
.Where(v => isVersionSuitable(v))
.Select(v => new DirectoryInfo($"{v.PathIndie}{subFolder}"));
if (suitableVersions.Any())
{
var selectedVersion = suitableVersions
.OrderByDescending(dir => dir.Exists ? dir.LastWriteTimeUtc : DateTime.MinValue)
.ThenByDescending(dir => dir.Exists ? dir.GetFiles().Length : -1)
.First();
defaultFolder = selectedVersion.FullName;
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用适合的游戏实例作为默认下载位置:{defaultFolder}");
}
else
{
defaultFolder = ModFolder.mcFolderSelected;
if (needLoad)
HintService.Hint(Lang.Text("Download.Comp.Detail.NoApplicableInstance"));
else
ModBase.Log("[Comp] 由于当前实例不兼容,使用当前的 MC 文件夹作为默认下载位置");
}
}
}
// 获取文件名并弹窗
var fileName = ModComp.CompFileNameGet(_project, file);
ModBase.RunInUi(() =>
{
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Comp.Detail.SelectSaveLocation"),
fileName, Lang.Text("Download.Comp.Detail.ResourceFile.Filter", desc) + "|" +
(file.Type == ModComp.CompType.Mod
? file.FileName.EndsWith(".litemod") ? "*.litemod" : "*.jar"
: file.FileName.EndsWith(".mrpack")
? "*.mrpack"
: "*.zip"),
defaultFolder);
if (!target.Contains("\\")) return;
// 记录缓存路径
var targetDir = ModBase.GetPathFromFullPath(target);
if (target != defaultFolder)
{
if (cachedFolder.ContainsKey(file.Type))
cachedFolder[file.Type] = targetDir;
else
cachedFolder.Add(file.Type, targetDir);
}
if (file.Type == ModComp.CompType.Mod && Config.Download.Comp.AutoInstallDependencies &&
file.Dependencies.Any())
{
try
{
McInstance? targetInstance = null;
var knownInstances = new List<McInstance>();
if (ModInstanceList.McMcInstanceSelected is not null)
{
knownInstances.Add(ModInstanceList.McMcInstanceSelected);
}
knownInstances.AddRange(ModInstanceList.mcInstanceList.Values.SelectMany(list => list)
.Where(instance => instance is not null));
targetInstance = knownInstances
.Distinct()
.FirstOrDefault(instance =>
targetDir.StartsWith(instance.PathIndie, StringComparison.OrdinalIgnoreCase));
if (targetInstance is not null && !targetInstance.IsLoaded)
{
targetInstance.Load();
}
var mcVersion = targetInstance?.Info?.VanillaName
?? file.GameVersions.FirstOrDefault(version => version.Contains("."))
?? string.Empty;
var targetLoaders = new List<ModComp.CompLoaderType>();
if (targetInstance is not null)
{
if (targetInstance.Info.HasForge || targetInstance.Info.HasCleanroom)
targetLoaders.Add(ModComp.CompLoaderType.Forge);
if (targetInstance.Info.HasFabric || targetInstance.Info.HasLegacyFabric)
targetLoaders.Add(ModComp.CompLoaderType.Fabric);
if (targetInstance.Info.HasQuilt)
targetLoaders.Add(ModComp.CompLoaderType.Quilt);
if (targetInstance.Info.HasNeoForge)
targetLoaders.Add(ModComp.CompLoaderType.NeoForge);
if (targetInstance.Info.HasLiteLoader)
targetLoaders.Add(ModComp.CompLoaderType.LiteLoader);
}
if (!targetLoaders.Any())
{
targetLoaders = allowedLoaders.ToList();
}
ModBase.Log($"[CompDeps] 开始解析必需前置: {file.Dependencies.Count} 个依赖");
var request = ModCompDependency.BuildRequest(file, _project, mcVersion, targetLoaders,
targetDir);
var resolver = new ModDependencyResolver();
var result = resolver.Resolve(request);
void DownloadDependencies()
{
if (!result.ToInstall.Any())
{
ModBase.Log("[CompDeps] 所有前置均无法解析,仅下载 Mod 本体");
return;
}
ModBase.Log($"[CompDeps] 准备下载: {result.ToInstall.Count} 个前置");
var depDownloads = ModCompDependency.BuildDependencyDownloads(result, targetDir);
foreach (var (depFilename, downloadFile) in depDownloads)
{
var depLoaderName = Lang.Text("Download.Comp.Detail.DownloadResource", desc,
ModBase.GetFileNameWithoutExtentionFromPath(depFilename));
var depLoaders = new List<ModLoader.LoaderBase>
{
new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"),
new List<DownloadFile> { downloadFile })
{
ProgressWeight = 6,
block = true
}
};
// 启动加载器
var depLoader = new ModLoader.LoaderCombo<int>(depLoaderName, depLoaders);
depLoader.OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly;
depLoader.Start(1);
ModLoader.LoaderTaskbarAdd(depLoader);
}
}
if (result.Unresolved.Any() || result.ToInstall.Any())
{
var installChoice = ModCompDependency.ConfirmDependencyInstall(result);
switch (installChoice)
{
case ModComp.CompDepsInstallTypes.Unresolved:
ModBase.Log("[CompDeps] 发现无法解析的前置");
DownloadDependencies();
break;
case ModComp.CompDepsInstallTypes.WithDeps:
DownloadDependencies();
break;
case ModComp.CompDepsInstallTypes.WithoutDeps:
ModBase.Log("[CompDeps] 用户选择仅下载 Mod 本体,跳过前置下载");
break;
case ModComp.CompDepsInstallTypes.Cancel:
ModBase.Log("[CompDeps] 用户取消安装");
return;
default:
ModBase.Log($"[CompDeps] 未知返回值: {installChoice} ,终止下载");
return;
}
}
else
{
ModBase.Log("[CompDeps] 已满足: 所有必需前置已安装");
}
}
catch (Exception depEx)
{
ModBase.Log(depEx, "[CompDeps] 依赖解析失败,跳过前置安装");
var message = ExceptionDetails.Compose(
Lang.Text("Download.Comp.Dependency.ResolveFailed.Message"),
depEx);
ModMain.MyMsgBox(
message,
Lang.Text("Download.Comp.Dependency.ResolveFailed.Title"),
Lang.Text("Download.Comp.Dependency.ResolveFailed.Continue"),
isWarn: true,
forceWait: true);
}
}
// 构造下载任务
var loaderName = Lang.Text("Download.Comp.Detail.DownloadResource", desc,
ModBase.GetFileNameWithoutExtentionFromPath(target));
var loaders = new List<ModLoader.LoaderBase>
{
new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"),
new List<DownloadFile>
{
file.Type == ModComp.CompType.Mod
? file.ToNetFile(target)
: file.ToNetFile(target, ModComp.DownloadReason.Standalone, null)
})
{
ProgressWeight = 6,
block = true
}
};
// 启动加载器
var loader = new ModLoader.LoaderCombo<int>(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,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}, "Download CompDetail Save");
}
private void BtnIntroWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite(_project.Website);
}
private void BtnIntroWiki_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://www.mcmod.cn/class/" + _project.WikiId + ".html");
}
private void BtnIntroCopy_Click(object sender, EventArgs e)
{
ModBase.ClipboardSet(_compItem.LabTitle.Text + _compItem.LabTitleRaw.Text);
}
private void BtnFavorites_Click(object sender, EventArgs e)
{
ModComp.CompFavorites.ShowMenu(_project, (UIElement)sender, RefreshFavoriteButton);
}
private void BtnIntroLinkCopy_Click(object sender, EventArgs e)
{
ModComp.CompClipboard.currentText = _project.Website;
ModBase.ClipboardSet(_project.Website);
}
// 翻译简介
private async void BtnTranslate_Click(object sender, EventArgs e)
{
HintService.Hint(Lang.Text("Download.Comp.Detail.DescriptionTranslating", _project.TranslatedName));
var chineseDescription = await _project.ChineseDescription;
if (chineseDescription is null)
return;
ModMain.MyMsgBox(Lang.Text("Download.Comp.Detail.DescriptionTranslationResult", _project.Description,
chineseDescription));
}
/// <summary>
/// 刷新收藏按钮的显示状态
/// </summary>
public void RefreshFavoriteButton()
{
try
{
if (_project is null) return;
var isFavourite = ModComp.CompFavorites.IsFavourite(_project.Id);
BtnFavorites.SvgIcon = isFavourite ? "lucide/heart-filled" : "lucide/heart";
// 刷新顶部的项目卡片收藏状态
if (_compItem is not null)
_compItem.RefreshFavoriteStatus();
}
catch (Exception ex)
{
ModBase.Log(ex, "刷新收藏按钮状态时出错");
}
}
#region
private readonly ModLoader.LoaderTask<int, List<ModComp.CompFile>> _compFileLoader;
public PageDownloadCompDetail()
{
_compFileLoader = new ModLoader.LoaderTask<int, List<ModComp.CompFile>>("Comp File", task =>
{
LoadTargetFromAdditional();
var result = ModComp.CompFilesGet(_project.Id, _project.FromCurseForge);
if (task.IsAborted)
return;
task.output = result;
});
Initialized += PageDownloadCompDetail_Inited;
Loaded += (_, _) => LoadTargetFromAdditional();
PageEnter += Init;
InitializeComponent();
Load.StateChanged += Load_State;
BtnIntroWeb.Click += BtnIntroWeb_Click;
BtnIntroWiki.Click += BtnIntroWiki_Click;
BtnIntroCopy.Click += BtnIntroCopy_Click;
BtnFavorites.Click += BtnFavorites_Click;
BtnIntroLinkCopy.Click += BtnIntroLinkCopy_Click;
BtnTranslate.Click += BtnTranslate_Click;
}
// 初始化加载器信息
private void PageDownloadCompDetail_Inited(object sender, EventArgs e)
{
LoadTargetFromAdditional();
PageLoaderInit(Load, PanLoad, PanMain, CardIntro, _compFileLoader, _ => Load_OnFinish());
}
public void LoadTargetFromAdditional()
{
var additional = ModMain.frmMain.pageCurrent.additional.Value;
_project = additional.CompProject;
_targetInstance = additional.TargetVersion;
_targetLoader = additional.TargetLoader;
_pageType = additional.ResourceType;
}
private ModComp.CompProject _project;
private string _targetInstance;
private ModComp.CompLoaderType _targetLoader;
/// <summary>
/// 当前页面应展示的内容类别。可能为 Any。
/// </summary>
private ModComp.CompType _pageType;
// 自动重试
private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState)
{
switch (_compFileLoader.State)
{
case ModBase.LoadState.Failed:
{
var errorMessage = "";
if (_compFileLoader.Error is not null)
errorMessage = _compFileLoader.Error.Message;
if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson")))
{
ModBase.Log("[Comp] 下载的文件 Json 列表损坏,已自动重试", ModBase.LogLevel.Debug);
PageLoaderRestart();
}
break;
}
}
}
// 结果 UI 化
private class CardSorter : IComparer<string>
{
public readonly string topmost = "";
public CardSorter(string topmost = "")
{
this.topmost = topmost ?? "";
}
public int Compare(string x, string y)
{
// 相同
if ((x ?? "") == (y ?? ""))
return 0;
// 置顶
if ((x ?? "") == (topmost ?? ""))
return -1;
if ((y ?? "") == (topmost ?? ""))
return 1;
// 特殊版本
var isXSpecial = !x.Contains(".");
var isYSpecial = !y.Contains(".");
if (isXSpecial && isYSpecial)
return string.Compare(x, y, StringComparison.Ordinal);
if (isXSpecial)
return 1;
if (isYSpecial)
return -1;
// 比较版本号
var versionCodeSort = -McVersionComparer.CompareVersion(x.Replace(x.BeforeFirst(" ") + " ", ""),
y.Replace(y.BeforeFirst(" ") + " ", ""));
if (versionCodeSort != 0)
return versionCodeSort;
// 比较全部
return -McVersionComparer.CompareVersion(x, y);
}
}
private string? _instanceFilter;
private string? _modLoaderFilter;
private bool groupedDrop; // 是否按 Drop 筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...
private bool groupedOld; // 是否折叠远古版本为一个选项
// 筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
private List<ModComp.CompFile> GetResults()
{
var results = _compFileLoader.output;
if (_pageType == ModComp.CompType.Any)
{
results = results.Where(r => r.Type != ModComp.CompType.Plugin).ToList();
}
else if (_pageType == ModComp.CompType.Shader || _pageType == ModComp.CompType.ResourcePack)
{
}
// 不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(Meloong-Git/#6473
else
{
results = results.Where(r => r.Type == _pageType).ToList();
}
return results;
}
private void Load_OnFinish()
{
var results = GetResults();
// 初始化筛选器
List<string> instanceFilters = null;
List<string> modLoaderFilters = null;
void updateFilters()
{
instanceFilters = results.SelectMany(v => v.GameVersions)
.Select(v => GetGroupedVersionName(v, groupedDrop, groupedOld)).Distinct()
.OrderByDescending(s => s, new McVersionComparer.VersionComparer()).ToList();
modLoaderFilters = results.SelectMany(v => v.ModLoaders).Select(l => l.ToString()).Distinct()
.OrderByDescending(s => s).ToList();
}
;
// 确定分组方式
groupedDrop = false;
groupedOld = false;
updateFilters();
if (instanceFilters.Count >= 9)
{
groupedDrop = true;
groupedOld = false;
updateFilters();
if (instanceFilters.Count >= 9)
{
groupedDrop = false;
groupedOld = true;
updateFilters();
if (instanceFilters.Count >= 9)
{
groupedDrop = true;
groupedOld = true;
updateFilters();
}
}
}
// UI 化筛选器
PanInstanceFilter.Children.Clear();
PanModLoaderFilter.Children.Clear();
if (_pageType == ModComp.CompType.Mod)
{
PanInstanceFilter.Margin = new Thickness(10d, 10d, 0d, 5d);
PanModLoaderFilter.Margin = new Thickness(10d, 5d, 0d, 10d);
}
else
{
PanInstanceFilter.Margin = new Thickness(10d, 10d, 0d, 10d);
PanModLoaderFilter.Margin = new Thickness(0d);
}
if (instanceFilters.Count < 2)
{
CardFilter.Visibility = Visibility.Collapsed;
_instanceFilter = null;
}
else
{
CardFilter.Visibility = Visibility.Visible;
// 插入标签
if (_pageType == ModComp.CompType.Mod)
{
var instanceTextBlock = new TextBlock
{
Text = Lang.Text("Download.Comp.Detail.InstanceFilter"),
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(2d, 0d, 0d, 0d)
};
PanInstanceFilter.Children.Add(instanceTextBlock);
var modLoaderTextBlock = new TextBlock
{
Text = Lang.Text("Download.Comp.Detail.LoaderFilter"),
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(2d, 0d, 0d, 0d)
};
PanModLoaderFilter.Children.Add(modLoaderTextBlock);
}
instanceFilters.Insert(0, Lang.Text("Common.Option.All"));
modLoaderFilters.Insert(0, Lang.Text("Common.Option.All"));
// 转化为按钮
foreach (var version in instanceFilters)
{
var newButton = new MyRadioButton
{
Text = version, Margin = new Thickness(2d, 0d, 2d, 0d),
ColorType = MyRadioButton.ColorState.Highlight
};
newButton.LabText.Margin = new Thickness(-2, 0d, 10d, 0d);
newButton.Check += (sender, raiseByMouse) =>
{
_instanceFilter = sender.Text == Lang.Text("Common.Option.All") ? null : sender.Text;
UpdateFilterResult();
};
PanInstanceFilter.Children.Add(newButton);
}
if (_pageType == ModComp.CompType.Mod)
foreach (var loader in modLoaderFilters)
{
var newButton = new MyRadioButton
{
Text = loader,
Margin = new Thickness(2d, 0d, 2d, 0d),
ColorType = MyRadioButton.ColorState.Highlight
};
newButton.LabText.Margin = new Thickness(-2, 0d, 10d, 0d);
newButton.Check += (sender, raiseByMouse) =>
{
_modLoaderFilter = sender.Text == Lang.Text("Common.Option.All") ? null : sender.Text;
UpdateFilterResult();
};
PanModLoaderFilter.Children.Add(newButton);
}
// 自动选择
MyRadioButton instanceToCheck = null;
MyRadioButton modLoaderToCheck = null;
if (!string.IsNullOrEmpty(_targetInstance))
{
var targetFile = results.FirstOrDefault(v => v.GameVersions.Contains(_targetInstance));
if (targetFile is not null)
{
var targetGroup = GetGroupedVersionName(_targetInstance, groupedDrop, groupedOld);
var children = _pageType == ModComp.CompType.Mod
? PanInstanceFilter.Children.Cast<UIElement>().Skip(1)
: PanInstanceFilter.Children.Cast<UIElement>();
foreach (MyRadioButton button in (IEnumerable)children)
{
if ((button.Text ?? "") != (targetGroup ?? ""))
continue;
instanceToCheck = button;
break;
}
}
}
if (_pageType == ModComp.CompType.Mod)
if (_targetLoader != ModComp.CompLoaderType.Any)
{
var targetFile = results.FirstOrDefault(v => v.ModLoaders.Contains(_targetLoader));
if (targetFile is not null)
{
var children = _pageType == ModComp.CompType.Mod
? PanInstanceFilter.Children.Cast<UIElement>().Skip(1)
: PanInstanceFilter.Children.Cast<UIElement>();
foreach (MyRadioButton button in (IEnumerable)children)
{
if ((button.Text ?? "") != (_targetLoader.ToString() ?? ""))
continue;
modLoaderToCheck = button;
break;
}
}
}
// 注意:在 Mod 下 index 0 是 TextBlock
var index = _pageType == ModComp.CompType.Mod ? 1 : 0;
if (instanceToCheck is null)
instanceToCheck = (MyRadioButton)PanInstanceFilter.Children[index];
if (modLoaderToCheck is null && (_pageType == ModComp.CompType.Mod))
modLoaderToCheck = (MyRadioButton)PanModLoaderFilter.Children[index];
instanceToCheck.Checked = true;
if (_pageType == ModComp.CompType.Mod)
modLoaderToCheck.Checked = true;
}
// 更新筛选结果(文件列表 UI 化)
UpdateFilterResult();
}
private void UpdateFilterResult()
{
var results = GetResults();
if (results is null)
return;
// 1. 预处理基础变量
var targetVersionText = _targetLoader != ModComp.CompLoaderType.Any ? _targetLoader + " " : "";
var targetCardName = !string.IsNullOrEmpty(_targetInstance) || _targetLoader != ModComp.CompLoaderType.Any
? Lang.Text("Download.Comp.Detail.SelectedVersion", targetVersionText, _targetInstance)
: "";
// 使用 HashSet 提高查询性能 O(1)
var supportedLoaders =
new HashSet<ModComp.CompLoaderType>(Enum.GetValues(typeof(ModComp.CompLoaderType))
.Cast<ModComp.CompLoaderType>());
var ignoreQuilt = Config.Download.Comp.IgnoreQuilt;
var hasMultipleLoaders = _project.ModLoaders.Count > 1;
// 2. 核心数据归类 (使用 Dictionary 配合 HashSet 去重)
var dict = new SortedDictionary<string, List<ModComp.CompFile>>(new CardSorter(targetCardName));
dict.Add(Lang.Text("Download.Comp.Detail.VersionGroup.Other"), new List<ModComp.CompFile>());
// 用于记录每个卡片内已存在的 version,防止 Contains(version) 的 O(n) 消耗
var versionDuplicateChecker = new Dictionary<string, HashSet<ModComp.CompFile>>();
foreach (var version in results)
{
// 处理普通卡片归类
foreach (var gameVersion in version.GameVersions)
{
// 筛选器预检查
var currentGroupedName = GetGroupedVersionName(gameVersion, groupedDrop, groupedOld);
if (_instanceFilter is not null && (currentGroupedName ?? "") != (_instanceFilter ?? ""))
continue;
var verName = GetGroupedVersionName(gameVersion, false, false);
var loaders = new List<string>();
// 判定 Loader 逻辑
if (hasMultipleLoaders && version.Type == ModComp.CompType.Mod &&
McInstanceInfo.IsFormatFit(verName))
{
foreach (var loader in version.ModLoaders)
{
if (loader == ModComp.CompLoaderType.Quilt && ignoreQuilt)
continue;
if (!supportedLoaders.Contains(loader))
continue;
// 模组加载器筛选器
if (_modLoaderFilter is not null && (loader.ToString() ?? "") != (_modLoaderFilter ?? ""))
continue;
loaders.Add(loader + " ");
}
if (loaders.Count == 0 && _modLoaderFilter is not null) continue;
}
if (loaders.Count == 0)
loaders.Add("");
// 填充数据
foreach (var loaderPrefix in loaders)
{
var targetKey = loaderPrefix + verName;
AddVersionToDict(dict, versionDuplicateChecker, targetKey, version);
}
}
// 处理“所选版本”卡片 (逻辑合并,减少二次循环)
if (!string.IsNullOrEmpty(targetCardName))
{
var isMatchFilter = _instanceFilter is null ||
GetGroupedVersionName(_targetInstance, groupedDrop, groupedOld)
.StartsWithF(_instanceFilter);
if (isMatchFilter && version.GameVersions.Contains(_targetInstance))
if (_targetLoader == ModComp.CompLoaderType.Any || version.ModLoaders.Contains(_targetLoader))
// 再次检查 version 是否符合筛选器(针对该文件的所有游戏版本)
if (_instanceFilter is null || version.GameVersions.Any(v =>
(GetGroupedVersionName(v, groupedDrop, groupedOld) ?? "") == (_instanceFilter ?? "")))
AddVersionToDict(dict, versionDuplicateChecker, targetCardName, version);
}
}
// 3. 渲染 UI
try
{
PanResults.Children.Clear();
var additional = ModMain.frmMain.pageCurrent.additional;
var additionalTitles = additional is not null
? additional.Value.ExpandedTitles
: new List<string>();
foreach (var pair in dict)
{
if (pair.Value.Count == 0)
continue;
// 创建卡片组件
var newCard = new MyCard
{
Title = pair.Key,
Margin = new Thickness(0d, 0d, 0d, 15d)
};
// 闭包引用:避免在 Sub 内做高耗时操作
var files = pair.Value;
var currentKey = pair.Key;
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top,
Tag = files
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
// 延迟加载安装项的逻辑
newCard.InstallMethod = stack =>
{
var list = (List<ModComp.CompFile>)stack.Tag;
// 排序和去重检查
list.Sort((a, b) => b.ReleaseDate.CompareTo(a.ReleaseDate));
var distinctCount = list.Select(f => f.DisplayName).Distinct().Count();
var badDisplayName = distinctCount != list.Count;
// 批量添加子项
switch (_project.Type)
{
case ModComp.CompType.ModPack:
{
foreach (var item in list)
stack.Children.Add(item.ToListItem(
(sender, e) => ModMain.frmDownloadCompDetail.Install_Click((MyListItem)sender, e),
ModMain.frmDownloadCompDetail.Save_Click, badDisplayName));
break;
}
case ModComp.CompType.World:
{
foreach (var item in list)
stack.Children.Add(item.ToListItem(
(sender, e) =>
ModMain.frmDownloadCompDetail.InstallWorld_Click((MyListItem)sender, e),
ModMain.frmDownloadCompDetail.Save_Click, badDisplayName));
break;
}
default:
{
ModComp.CompFilesCardPreload(stack, list);
foreach (var item in list)
stack.Children.Add(item.ToListItem(ModMain.frmDownloadCompDetail.Save_Click,
badDisplayName: badDisplayName));
break;
}
}
};
PanResults.Children.Add(newCard);
// 展开逻辑
if ((currentKey ?? "") == (targetCardName ?? "") || additionalTitles.Contains(newCard.Title))
newCard.StackInstall();
else
newCard.IsSwapped = true;
// 特殊提示
if (currentKey == Lang.Text("Download.Comp.Detail.VersionGroup.Other"))
newStack.Children.Add(new MyHint
{
Text = Lang.Text("Download.Comp.Detail.VersionRecognitionDelayHint"),
Theme = MyHint.Themes.Yellow,
Margin = new Thickness(5d, 0d, 0d, 8d)
});
}
// 单卡片自动展开
if (PanResults.Children.Count == 1) ((MyCard)PanResults.Children[0]).IsSwapped = false;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化工程下载列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
/// <summary>
/// 辅助方法:向字典添加数据并处理去重
/// </summary>
private void AddVersionToDict(SortedDictionary<string, List<ModComp.CompFile>> dict,
Dictionary<string, HashSet<ModComp.CompFile>> checker, string key, ModComp.CompFile version)
{
if (!dict.ContainsKey(key))
{
dict.Add(key, new List<ModComp.CompFile>());
checker.Add(key, new HashSet<ModComp.CompFile>());
}
// 使用 HashSet.Add 判断是否重复,比 List.Contains 快得多
if (checker[key].Add(version)) dict[key].Add(version);
}
private string GetGroupedVersionName(string name, bool groupedByDrop, bool foldOld)
{
if (name is null)
return Lang.Text("Download.Comp.Detail.VersionGroup.Other");
if (name.Contains('w'))
return Lang.Text("Download.Comp.Detail.VersionGroup.Snapshot");
if (foldOld && McInstanceInfo.VersionToDrop(name, true) < 120)
return Lang.Text("Download.Comp.Detail.VersionGroup.Old");
if (groupedByDrop)
{
var drop = McInstanceInfo.VersionToDrop(name, true);
if (drop >= 0)
return McInstanceInfo.DropToVersion(drop);
return name;
}
return name;
}
#endregion
}
@@ -0,0 +1,27 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadDataPack"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="DataPack">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.WorldGen}" Tag="/worldgen" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Technology}" Tag="6951/technology" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.GameMechanics}" Tag="/game-mechanics" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Transportation}" Tag="/transportation" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Storage}" Tag="/storage" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Magic}" Tag="6952/magic" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Adventure}" Tag="6948/adventure" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.DataPack.Fantasy}" Tag="6949/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="/decoration" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mobs}" Tag="/mobs" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Utility}" Tag="6953/utility" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Equipment}" Tag="/equipment" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Optimization}" Tag="/optimization" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Server}" Tag="/social" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Library}" Tag="6950/library" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mod.ModRelated}" Tag="6946/" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadDataPack
{
public PageDownloadDataPack()
{
InitializeComponent();
}
}
@@ -0,0 +1,37 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadMod"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="Mod">
<local:MyComboBoxItem Content="{DynamicResource Common.Option.All}" Tag="" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.WorldGen}" Tag="406/worldgen" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Biomes}" Tag="407/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Dimensions}" Tag="410/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.OresResources}" Tag="408/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Structures}" Tag="409/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Technology}" Tag="412/technology" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.PipesLogistics}" Tag="415/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Automation}" Tag="4843/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Energy}" Tag="417/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Redstone}" Tag="4558/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.FoodCooking}" Tag="436/food" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Farming}" Tag="416/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.GameMechanics}" Tag="/game-mechanics" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Transportation}" Tag="414/transportation" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Storage}" Tag="420/storage" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Magic}" Tag="419/magic" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Adventure}" Tag="422/adventure" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="424/decoration" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mobs}" Tag="411/mobs" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Utility}" Tag="5191/utility" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Equipment}" Tag="434/equipment" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Creative}" Tag="9026/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Optimization}" Tag="6814/optimization" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Display}" Tag="423/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Server}" Tag="435/social" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Library}" Tag="421/library" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,10 @@
namespace PCL;
public partial class PageDownloadMod
{
public PageDownloadMod()
{
InitializeComponent();
}
}
@@ -0,0 +1,29 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadPack"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="ModPack">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Tag="4484/" Content="{DynamicResource Download.Comp.Category.Multiplayer}" />
<local:MyComboBoxItem Tag="/optimization" Content="{DynamicResource Download.Comp.Category.Optimization}" />
<local:MyComboBoxItem Tag="4479/challenging" Content="{DynamicResource Download.Comp.Category.Modpack.Hardcore}" />
<local:MyComboBoxItem Tag="4483/combat" Content="{DynamicResource Download.Comp.Category.Combat}" />
<local:MyComboBoxItem Tag="4478/quests" Content="{DynamicResource Download.Comp.Category.Modpack.Quests}" />
<local:MyComboBoxItem Tag="4472/technology" Content="{DynamicResource Download.Comp.Category.Technology}" />
<local:MyComboBoxItem Tag="4473/magic" Content="{DynamicResource Download.Comp.Category.Magic}" />
<local:MyComboBoxItem Tag="4475/adventure" Content="{DynamicResource Download.Comp.Category.Adventure}" />
<local:MyComboBoxItem Tag="/kitchen-sink" Content="{DynamicResource Download.Comp.Category.Modpack.KitchenSink}" />
<local:MyComboBoxItem Tag="4476/" Content="{DynamicResource Download.Comp.Category.Modpack.Exploration}" />
<local:MyComboBoxItem Tag="4477/" Content="{DynamicResource Download.Comp.Category.Modpack.MiniGame}" />
<local:MyComboBoxItem Tag="4471/" Content="{DynamicResource Download.Comp.Category.Modpack.SciFi}" />
<local:MyComboBoxItem Tag="4736/" Content="{DynamicResource Download.Comp.Category.Modpack.Skyblock}" />
<local:MyComboBoxItem Tag="5128/" Content="{DynamicResource Download.Comp.Category.Modpack.VanillaPlus}" />
<local:MyComboBoxItem Tag="4487/" Content="{DynamicResource Download.Comp.Category.Modpack.Ftb}" ToolTip="{DynamicResource Download.Comp.Category.Modpack.Ftb.ToolTip}" ToolTipService.HorizontalOffset="70" />
<local:MyComboBoxItem Tag="4480/" Content="{DynamicResource Download.Comp.Category.Modpack.MapBased}" />
<local:MyComboBoxItem Tag="4481/lightweight" Content="{DynamicResource Download.Comp.Category.Modpack.SmallLight}" />
<local:MyComboBoxItem Tag="4482/" Content="{DynamicResource Download.Comp.Category.Modpack.ExtraLarge}" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadPack
{
public PageDownloadPack()
{
InitializeComponent();
}
}
@@ -0,0 +1,46 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadResourcePack"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="ResourcePack">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Style}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.VanillaLike}" Tag="403/vanilla-like" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Realistic}" Tag="400/realistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Modern}" Tag="401/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Medieval}" Tag="402/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Steampunk}" Tag="399/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Themed}" Tag="/themed" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Simplistic}" Tag="/simplistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="/decoration" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Combat}" Tag="/combat" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Utility}" Tag="/utility" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Tweaks}" Tag="/tweaks" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Cursed}" Tag="/cursed" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Feature}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Entities}" Tag="/entities" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Audio}" Tag="/audio" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Fonts}" Tag="5244/fonts" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Models}" Tag="/models" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Locale}" Tag="/locale" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Gui}" Tag="/gui" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.CoreShaders}" Tag="/core-shaders" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Animated}" Tag="404/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ModSupport}" Tag="4465/modded" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Resolution}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution8xOrLower}" Tag="/8x-" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution16x}" Tag="393/16x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution32x}" Tag="394/32x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution48x}" Tag="/48x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution64x}" Tag="395/64x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution128x}" Tag="396/128x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution256x}" Tag="397/256x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution512xOrHigher}" Tag="398/512x+" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadResourcePack
{
public PageDownloadResourcePack()
{
InitializeComponent();
}
}
@@ -0,0 +1,30 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadShader"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="Shader">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Style}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.VanillaLike}" Tag="6555/vanilla-like" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Fantasy}" Tag="6554/fantasy" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Realistic}" Tag="6553/realistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.SemiRealistic}" Tag="/semi-realistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Cartoon}" Tag="/cartoon" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Feature}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ColoredLighting}" Tag="/colored-lighting" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PathTracing}" Tag="/path-tracing" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Pbr}" Tag="/pbr" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Reflections}" Tag="/reflections" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Performance}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformancePotato}" Tag="/potato" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceLow}" Tag="/low" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceMedium}" Tag="/medium" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceHigh}" Tag="/high" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadShader
{
public PageDownloadShader()
{
InitializeComponent();
}
}
@@ -0,0 +1,19 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadWorld"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="World" SupportModrinth="False">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Adventure}" Tag="248/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Creative}" Tag="249/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Modpack.MiniGame}" Tag="250/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Parkour}" Tag="251/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Puzzle}" Tag="252/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Survival}" Tag="253/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.ModWorld}" Tag="4464/" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadWorld
{
public PageDownloadWorld()
{
InitializeComponent();
}
}