mirror of
https://github.com/wangdage12/Snap.Hutao.git
synced 2026-07-31 04:10:25 +08:00
右键显示缺项带图片
This commit is contained in:
@@ -352,7 +352,7 @@ internal sealed partial class CultivationService : ICultivationService
|
|||||||
entryByInnerId[e.InnerId] = e;
|
entryByInnerId[e.InnerId] = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<uint, List<string>> unfinishedSegmentsByMaterial = [];
|
Dictionary<uint, List<(string SortKey, StatisticsConsumerMenuLine Line)>> unfinishedRowsByMaterial = [];
|
||||||
|
|
||||||
foreach ((CultivateEntry entry, CultivateItem item) in pairs.AsSpan())
|
foreach ((CultivateEntry entry, CultivateItem item) in pairs.AsSpan())
|
||||||
{
|
{
|
||||||
@@ -361,33 +361,75 @@ internal sealed partial class CultivationService : ICultivationService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string name = FormatStatisticsConsumerEntryName(entry, context, entryByInnerId);
|
string sortKey = $"{FormatStatisticsConsumerEntryName(entry, context, entryByInnerId)}×{item.Count}";
|
||||||
string segment = $"{name}×{item.Count}";
|
StatisticsConsumerMenuLine line = CreateStatisticsConsumerMenuLine(entry, item, context, entryByInnerId);
|
||||||
ref List<string>? list = ref CollectionsMarshal.GetValueRefOrAddDefault(unfinishedSegmentsByMaterial, item.ItemId, out _);
|
ref List<(string SortKey, StatisticsConsumerMenuLine Line)>? list = ref CollectionsMarshal.GetValueRefOrAddDefault(unfinishedRowsByMaterial, item.ItemId, out _);
|
||||||
list ??= [];
|
list ??= [];
|
||||||
list.Add(segment);
|
list.Add((sortKey, line));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ((uint materialId, StatisticsCultivateItem stat) in resultItems)
|
foreach ((uint materialId, StatisticsCultivateItem stat) in resultItems)
|
||||||
{
|
{
|
||||||
if (MaterialIds.IsExcludedFromStatisticsConsumerMenu(materialId))
|
if (MaterialIds.IsExcludedFromStatisticsConsumerMenu(materialId))
|
||||||
{
|
{
|
||||||
stat.StatisticsConsumerMenuLines = ImmutableArray.Create(SH.ViewPageCultivationStatisticsConsumerMenuExcluded);
|
stat.StatisticsConsumerMenuLines = ImmutableArray.Create(StatisticsConsumerMenuLine.Plain(SH.ViewPageCultivationStatisticsConsumerMenuExcluded));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unfinishedSegmentsByMaterial.TryGetValue(materialId, out List<string>? segments))
|
if (unfinishedRowsByMaterial.TryGetValue(materialId, out List<(string SortKey, StatisticsConsumerMenuLine Line)>? rows))
|
||||||
{
|
{
|
||||||
segments.Sort(StringComparer.Ordinal);
|
rows.Sort(static (a, b) => StringComparer.Ordinal.Compare(a.SortKey, b.SortKey));
|
||||||
stat.StatisticsConsumerMenuLines = ImmutableArray.CreateRange(segments);
|
stat.StatisticsConsumerMenuLines = ImmutableArray.CreateRange(rows.ConvertAll(static r => r.Line));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stat.StatisticsConsumerMenuLines = ImmutableArray.Create(SH.ViewPageCultivationStatisticsUnfinishedConsumersEmptyList);
|
stat.StatisticsConsumerMenuLines = ImmutableArray.Create(StatisticsConsumerMenuLine.Plain(SH.ViewPageCultivationStatisticsUnfinishedConsumersEmptyList));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static StatisticsConsumerMenuLine CreateStatisticsConsumerMenuLine(
|
||||||
|
CultivateEntry entry,
|
||||||
|
CultivateItem item,
|
||||||
|
ICultivationMetadataContext context,
|
||||||
|
Dictionary<Guid, CultivateEntry> entryByInnerId)
|
||||||
|
{
|
||||||
|
string countSuffix = $"×{item.Count}";
|
||||||
|
|
||||||
|
switch (entry.Type)
|
||||||
|
{
|
||||||
|
case CultivateType.AvatarAndSkill:
|
||||||
|
{
|
||||||
|
ModelItem avatarItem = context.GetAvatarItem(entry.Id);
|
||||||
|
return StatisticsConsumerMenuLine.SingleIcon(avatarItem.Icon, avatarItem.Quality, avatarItem.Name, countSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
case CultivateType.Weapon:
|
||||||
|
{
|
||||||
|
ModelItem weaponItem = context.GetWeaponItem(entry.Id);
|
||||||
|
if (entry.RelatedEntryId is Guid relatedId
|
||||||
|
&& entryByInnerId.TryGetValue(relatedId, out CultivateEntry? related)
|
||||||
|
&& related.Type is CultivateType.AvatarAndSkill)
|
||||||
|
{
|
||||||
|
ModelItem avatarItem = context.GetAvatarItem(related.Id);
|
||||||
|
return StatisticsConsumerMenuLine.AvatarAndWeapon(
|
||||||
|
avatarItem.Icon,
|
||||||
|
avatarItem.Quality,
|
||||||
|
avatarItem.Name,
|
||||||
|
weaponItem.Icon,
|
||||||
|
weaponItem.Quality,
|
||||||
|
weaponItem.Name,
|
||||||
|
countSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StatisticsConsumerMenuLine.SingleIcon(weaponItem.Icon, weaponItem.Quality, weaponItem.Name, countSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return StatisticsConsumerMenuLine.Plain($"{Material.Default.Name}{countSuffix}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string FormatStatisticsConsumerEntryName(
|
private static string FormatStatisticsConsumerEntryName(
|
||||||
CultivateEntry entry,
|
CultivateEntry entry,
|
||||||
ICultivationMetadataContext context,
|
ICultivationMetadataContext context,
|
||||||
|
|||||||
@@ -238,12 +238,52 @@
|
|||||||
<ScrollViewer MaxHeight="320" MinWidth="220" Padding="12,10">
|
<ScrollViewer MaxHeight="320" MinWidth="220" Padding="12,10">
|
||||||
<ItemsControl ItemsSource="{Binding StatisticsConsumerMenuLines, Mode=OneWay}">
|
<ItemsControl ItemsSource="{Binding StatisticsConsumerMenuLines, Mode=OneWay}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate x:DataType="x:String">
|
<DataTemplate x:DataType="shvcu:StatisticsConsumerMenuLine">
|
||||||
<TextBlock
|
<Grid Margin="0,3">
|
||||||
Margin="0,3"
|
<TextBlock
|
||||||
Style="{ThemeResource BodyTextBlockStyle}"
|
Style="{ThemeResource BodyTextBlockStyle}"
|
||||||
Text="{x:Bind Mode=OneWay}"
|
Text="{x:Bind PlainMessage, Mode=OneWay}"
|
||||||
TextWrapping="WrapWholeWords"/>
|
TextWrapping="WrapWholeWords"
|
||||||
|
Visibility="{x:Bind IsPlainMessage, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="4"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="{x:Bind ShowRichRow, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<shuxc:ItemIcon
|
||||||
|
Width="28"
|
||||||
|
Height="28"
|
||||||
|
Icon="{x:Bind LeadingIcon, Mode=OneWay}"
|
||||||
|
Quality="{x:Bind LeadingQuality, Mode=OneWay}"/>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{ThemeResource BodyTextBlockStyle}"
|
||||||
|
Text="{x:Bind FirstName, Mode=OneWay}"/>
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="4"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="{x:Bind HasSecondIcon, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{ThemeResource BodyTextBlockStyle}"
|
||||||
|
Text="{x:Bind BetweenSeparator, Mode=OneWay}"/>
|
||||||
|
<shuxc:ItemIcon
|
||||||
|
Width="28"
|
||||||
|
Height="28"
|
||||||
|
Icon="{x:Bind SecondIcon, Mode=OneWay}"
|
||||||
|
Quality="{x:Bind SecondQuality, Mode=OneWay}"/>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{ThemeResource BodyTextBlockStyle}"
|
||||||
|
Text="{x:Bind SecondName, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{ThemeResource BodyTextBlockStyle}"
|
||||||
|
Text="{x:Bind CountSuffix, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.ViewModel.Cultivation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 材料统计右键浮层中一行「未完成」养成条目:名称前展示角色/武器图标。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class StatisticsConsumerMenuLine
|
||||||
|
{
|
||||||
|
private StatisticsConsumerMenuLine()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPlainMessage { get; private init; }
|
||||||
|
|
||||||
|
public string? PlainMessage { get; private init; }
|
||||||
|
|
||||||
|
public bool ShowRichRow => !IsPlainMessage;
|
||||||
|
|
||||||
|
public Uri LeadingIcon { get; private init; } = default!;
|
||||||
|
|
||||||
|
public QualityType LeadingQuality { get; private init; }
|
||||||
|
|
||||||
|
public bool HasSecondIcon { get; private init; }
|
||||||
|
|
||||||
|
public Uri SecondIcon { get; private init; } = default!;
|
||||||
|
|
||||||
|
public QualityType SecondQuality { get; private init; }
|
||||||
|
|
||||||
|
public string FirstName { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>双名称行中间分隔(与武器关联条目的「角色·武器」展示一致,使用间隔号)。</summary>
|
||||||
|
public string BetweenSeparator { get; private init; } = "\u00B7";
|
||||||
|
|
||||||
|
public string? SecondName { get; private init; }
|
||||||
|
|
||||||
|
public string CountSuffix { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
public static StatisticsConsumerMenuLine Plain(string message)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
IsPlainMessage = true,
|
||||||
|
PlainMessage = message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StatisticsConsumerMenuLine SingleIcon(Uri icon, QualityType quality, string name, string countSuffix)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
LeadingIcon = icon,
|
||||||
|
LeadingQuality = quality,
|
||||||
|
FirstName = name,
|
||||||
|
CountSuffix = countSuffix,
|
||||||
|
HasSecondIcon = false,
|
||||||
|
SecondIcon = default!,
|
||||||
|
SecondQuality = QualityType.QUALITY_NONE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StatisticsConsumerMenuLine AvatarAndWeapon(
|
||||||
|
Uri avatarIcon,
|
||||||
|
QualityType avatarQuality,
|
||||||
|
string avatarName,
|
||||||
|
Uri weaponIcon,
|
||||||
|
QualityType weaponQuality,
|
||||||
|
string weaponName,
|
||||||
|
string countSuffix)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
LeadingIcon = avatarIcon,
|
||||||
|
LeadingQuality = avatarQuality,
|
||||||
|
FirstName = avatarName,
|
||||||
|
HasSecondIcon = true,
|
||||||
|
SecondIcon = weaponIcon,
|
||||||
|
SecondQuality = weaponQuality,
|
||||||
|
SecondName = weaponName,
|
||||||
|
CountSuffix = countSuffix,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,9 +68,9 @@ internal sealed class StatisticsCultivateItem
|
|||||||
public bool IsToday { get => Inner.IsItemOfToday(offset, true); }
|
public bool IsToday { get => Inner.IsItemOfToday(offset, true); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 材料统计右键浮层中按行展示的「未完成」养成条目(每人一行,<c>名×数量</c>)。
|
/// 材料统计右键浮层中按行展示的「未完成」养成条目(每人一行,名称前为角色/武器图标,<c>名×数量</c>)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ImmutableArray<string> StatisticsConsumerMenuLines { get; set; }
|
public ImmutableArray<StatisticsConsumerMenuLine> StatisticsConsumerMenuLines { get; set; }
|
||||||
|
|
||||||
internal bool ExcludedFromPresentation { get; set; }
|
internal bool ExcludedFromPresentation { get; set; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user