using System.Drawing; namespace Shigure; /// /// 动态单位 / 数量字段的编辑弹窗: 选择类别(单位/数量)与选择器, 按所选类型动态显隐参数控件。 /// 光环候选来自当前职业/专精的 group 字段。校验名称非空、唯一、非纯数字、不含 '.'/'$'。 /// public sealed class UnitEditorForm : Form { private const int RowWidth = 800; private const int LabelWidth = 132; private const int ControlLeft = LabelWidth + 10; private static readonly RoleOption[] RoleOptions = [ new("坦克 (1)", 1), new("治疗 (2)", 2), new("输出 (3)", 3) ]; private static readonly DispelTypeOption[] DispelTypeOptions = [ new("1: 魔法", 1), new("2: 诅咒", 2), new("3: 疾病", 3), new("4: 中毒", 4) ]; private static readonly LowestHealthAuraFilterItem[] LowestHealthAuraFilterOptions = [ new("不筛选光环", LowestHealthAuraFilterKind.None), new("带任一光环", LowestHealthAuraFilterKind.WithAnyAura), new("不带任一光环", LowestHealthAuraFilterKind.WithoutAnyAura), new("不带某光环", LowestHealthAuraFilterKind.WithoutAura), new("带某光环", LowestHealthAuraFilterKind.WithAura), new("某光环值等于", LowestHealthAuraFilterKind.WithAuraCount) ]; private static readonly LowestHealthRoleFilterItem[] LowestHealthRoleFilterOptions = [ new("不筛选职责", null), new("包含某职责", UnitRoleFilterKind.Include), new("不含某职责", UnitRoleFilterKind.Exclude) ]; private static readonly SelectorItem[] UnitSelectors = [ new("生命值最低", UnitSelectorKind.LowestHealth), new("治疗吸收最高", UnitSelectorKind.HighestHealingAbsorb), new("按职责", UnitSelectorKind.UnitWithRole), new("按职责且不带某光环", UnitSelectorKind.UnitWithRoleWithoutAura), new("带某光环(持续最久)", UnitSelectorKind.UnitWithAura), new("带某驱散类型", UnitSelectorKind.UnitWithDispelType) ]; private static readonly CountItem[] CountSelectors = [ new("血量 - 低于阈值", CountKind.UnitsBelowHealth), new("血量 - 低于阈值不带某光环", CountKind.UnitsWithoutAuraBelowHealth), new("血量 - 低于阈值带某光环", CountKind.UnitsWithAuraBelowHealth), new("治疗吸收 - 大于阈值", CountKind.UnitsAboveHealingAbsorb), new("治疗吸收 - 大于阈值不带某光环", CountKind.UnitsWithoutAuraAboveHealingAbsorb), new("治疗吸收 - 大于阈值带某光环", CountKind.UnitsWithAuraAboveHealingAbsorb), new("光环 - 带某光环", CountKind.UnitsWithAura) ]; private static readonly ThresholdModeItem[] ThresholdModeOptions = [ new("固定阈值", false), new("动态阈值", true) ]; private readonly IReadOnlyList _auraFields; private readonly IReadOnlyList _thresholdFields; private readonly HashSet _takenNames; private readonly Label _healthNameLabel = new(); private readonly TextBox _nameBox = new(); private readonly TextBox _healthNameBox = new(); private readonly UiDropDown _categoryBox = new(); private readonly UiDropDown _selectorBox = new(); private readonly UiDropDown _lowestHealthAuraFilterBox = new(); private readonly UiDropDown _lowestHealthRoleFilterBox = new(); private readonly FlowLayoutPanel _paramPanel = new(); private readonly Label _previewLabel = new(); private readonly ToolTip _toolTip = new(); private readonly NumericUpDown _thresholdBox = new(); private readonly UiDropDown _thresholdModeBox = new(); private readonly UiDropDown _thresholdFieldBox = new(); private readonly UiDropDown _roleBox = new(); private readonly CheckBox _reverseBox = new(); private readonly UiDropDown _auraBox = new(); private readonly CheckedListBox _aurasBox = new(); private readonly NumericUpDown _auraCountBox = new(); private readonly UiDropDown _dispelTypeBox = new(); private Panel _thresholdModeRow = null!; private Panel _thresholdRow = null!; private Label _thresholdLabel = null!; private Panel _thresholdFieldRow = null!; private Panel _lowestHealthAuraFilterRow = null!; private Panel _lowestHealthRoleFilterRow = null!; private Panel _roleRow = null!; private Panel _reverseRow = null!; private Panel _auraRow = null!; private Panel _aurasRow = null!; private Panel _auraCountRow = null!; private Panel _dispelRow = null!; private bool _usesHealingAbsorbThreshold; public ModuleUnit? ResultUnit { get; private set; } public ModuleCountField? ResultCount { get; private set; } public UnitEditorForm( IReadOnlyList auraFields, IReadOnlyList thresholdFields, IReadOnlyCollection takenNames, ModuleUnit? existingUnit, ModuleCountField? existingCount) { _auraFields = auraFields; _thresholdFields = thresholdFields; _takenNames = new HashSet(takenNames, StringComparer.OrdinalIgnoreCase); InitializeComponent(); Seed(existingUnit, existingCount); UpdateParamVisibility(); UpdateHealthNameState(); } protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); UiTheme.ApplyDarkTitleBar(this); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); RestoreCachedWindowSize(); } protected override void OnResizeEnd(EventArgs e) { base.OnResizeEnd(e); SaveWindowSize(); } protected override void OnFormClosed(FormClosedEventArgs e) { SaveWindowSize(); base.OnFormClosed(e); } protected override void OnShown(EventArgs e) { base.OnShown(e); _nameBox.Focus(); _nameBox.SelectAll(); } private void RestoreCachedWindowSize() { var cached = UiCacheStore.Load().UnitEditorWindowSize; if (cached is null || cached.Width <= 0 || cached.Height <= 0) { return; } var workingArea = Owner is not null ? Screen.FromControl(Owner).WorkingArea : Screen.FromControl(this).WorkingArea; var maximumWidth = Math.Max(MinimumSize.Width, workingArea.Width - 40); var maximumHeight = Math.Max(MinimumSize.Height, workingArea.Height - 40); Size = new Size( Math.Clamp(cached.Width, MinimumSize.Width, maximumWidth), Math.Clamp(cached.Height, MinimumSize.Height, maximumHeight)); if (Owner is not null) { CenterToParent(); } else { CenterToScreen(); } } private void SaveWindowSize() { if (WindowState != FormWindowState.Normal || Width <= 0 || Height <= 0) { return; } var cache = UiCacheStore.Load(); cache.UnitEditorWindowSize = new WindowSize { Width = Width, Height = Height }; UiCacheStore.Save(cache); } private void InitializeComponent() { Text = "编辑单位"; Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point); AutoScaleMode = AutoScaleMode.Dpi; BackColor = UiTheme.Surface; ForeColor = UiTheme.Text; ClientSize = new Size(RowWidth + 36, 534); FormBorderStyle = FormBorderStyle.Sizable; MaximizeBox = false; MinimizeBox = false; ShowInTaskbar = false; StartPosition = FormStartPosition.CenterParent; MinimumSize = new Size(RowWidth + 52, 420); var root = new TableLayoutPanel { Dock = DockStyle.Fill, BackColor = UiTheme.Surface, Padding = new Padding(UiTheme.CardPadding, 12, UiTheme.CardPadding, 12), ColumnCount = 1, RowCount = 4 }; root.RowStyles.Add(new RowStyle(SizeType.Absolute, 112)); root.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 48)); root.RowStyles.Add(new RowStyle(SizeType.Absolute, 56)); Controls.Add(root); UiTheme.StyleComboBox(_categoryBox); _categoryBox.DropDownWidth = 180; _categoryBox.Items.AddRange(["单位 (可作目标)", "数量 (仅条件)"]); _categoryBox.SelectedIndex = 0; _categoryBox.SelectedIndexChanged += (_, _) => { PopulateSelectors(); UpdateParamVisibility(); UpdateHealthNameState(); }; UiTheme.StyleComboBox(_selectorBox); _selectorBox.DropDownWidth = 360; _selectorBox.SelectedIndexChanged += (_, _) => { UpdateParamVisibility(); UpdateHealthNameState(); }; var headerCard = new UiCardPanel { Dock = DockStyle.Fill, Padding = new Padding(4), Margin = new Padding(0, 0, 0, UiTheme.PageGap), ColumnCount = 1, RowCount = 2 }; headerCard.RowStyles.Add(new RowStyle(SizeType.Percent, 50)); headerCard.RowStyles.Add(new RowStyle(SizeType.Percent, 50)); headerCard.Controls.Add(BuildSplitRow("类别", _categoryBox, "选择器", _selectorBox), 0, 0); headerCard.Controls.Add(BuildNameRow(), 0, 1); root.Controls.Add(headerCard, 0, 0); _paramPanel.Dock = DockStyle.Fill; _paramPanel.BackColor = Color.Transparent; _paramPanel.FlowDirection = FlowDirection.TopDown; _paramPanel.WrapContents = false; _paramPanel.AutoScroll = true; _paramPanel.Margin = new Padding(0); _paramPanel.Padding = new Padding(4, 6, 4, 6); BuildParamRows(); var paramsCard = new UiCardPanel { Dock = DockStyle.Fill, Padding = new Padding(4), Margin = new Padding(0, 0, 0, UiTheme.PageGap), ColumnCount = 1, RowCount = 1 }; paramsCard.Controls.Add(_paramPanel, 0, 0); root.Controls.Add(paramsCard, 0, 1); _previewLabel.Dock = DockStyle.Fill; _previewLabel.ForeColor = UiTheme.Muted; _previewLabel.TextAlign = ContentAlignment.MiddleLeft; _previewLabel.AutoEllipsis = true; _previewLabel.Margin = new Padding(0); var previewCard = new UiCardPanel { Dock = DockStyle.Fill, Padding = new Padding(UiTheme.CardPadding, 6, UiTheme.CardPadding, 6), Margin = new Padding(0, 0, 0, UiTheme.PageGap), ColumnCount = 1, RowCount = 1 }; previewCard.Controls.Add(_previewLabel, 0, 0); root.Controls.Add(previewCard, 0, 2); root.Controls.Add(BuildActionRow(), 0, 3); PopulateSelectors(); } private void BuildParamRows() { _thresholdBox.Minimum = 1; _thresholdBox.Maximum = int.MaxValue; _thresholdBox.Value = 100; UiTheme.StyleNumericUpDown(_thresholdBox); UiTheme.StyleComboBox(_thresholdModeBox); _thresholdModeBox.DropDownWidth = 160; _thresholdModeBox.Items.AddRange(ThresholdModeOptions.Cast().ToArray()); _thresholdModeBox.SelectedIndex = 0; _thresholdModeBox.SelectedIndexChanged += (_, _) => UpdateParamVisibility(); UiTheme.StyleComboBox(_thresholdFieldBox); _thresholdFieldBox.DropDownWidth = 360; foreach (var field in _thresholdFields) { if (!_thresholdFieldBox.Items.Contains(field)) { _thresholdFieldBox.Items.Add(field); } } if (_thresholdFieldBox.Items.Count > 0) { _thresholdFieldBox.SelectedIndex = 0; } UiTheme.StyleComboBox(_lowestHealthAuraFilterBox); _lowestHealthAuraFilterBox.DropDownWidth = 220; _lowestHealthAuraFilterBox.Items.AddRange(LowestHealthAuraFilterOptions.Cast().ToArray()); _lowestHealthAuraFilterBox.SelectedIndex = 0; _lowestHealthAuraFilterBox.SelectedIndexChanged += (_, _) => UpdateParamVisibility(); UiTheme.StyleComboBox(_lowestHealthRoleFilterBox); _lowestHealthRoleFilterBox.DropDownWidth = 220; _lowestHealthRoleFilterBox.Items.AddRange(LowestHealthRoleFilterOptions.Cast().ToArray()); _lowestHealthRoleFilterBox.SelectedIndex = 0; _lowestHealthRoleFilterBox.SelectedIndexChanged += (_, _) => UpdateParamVisibility(); UiTheme.StyleComboBox(_roleBox); _roleBox.DropDownWidth = 160; _roleBox.Items.AddRange(RoleOptions.Cast().ToArray()); _roleBox.SelectedIndex = 0; _reverseBox.Text = "取逆序最后一个匹配单位"; UiTheme.StyleCheckBox(_reverseBox, UiTheme.SurfaceRaised); _reverseBox.AutoSize = false; _reverseBox.TextAlign = ContentAlignment.MiddleLeft; UiTheme.StyleComboBox(_auraBox); _auraBox.DropDownWidth = 360; foreach (var aura in _auraFields) { _auraBox.Items.Add(aura); } if (_auraBox.Items.Count > 0) { _auraBox.SelectedIndex = 0; } UiTheme.StyleCheckedListBox(_aurasBox); foreach (var aura in _auraFields) { _aurasBox.Items.Add(aura); } _auraCountBox.Minimum = 0; _auraCountBox.Maximum = 100; _auraCountBox.Value = 1; UiTheme.StyleNumericUpDown(_auraCountBox); UiTheme.StyleComboBox(_dispelTypeBox); _dispelTypeBox.DropDownWidth = 180; _dispelTypeBox.Items.AddRange(DispelTypeOptions.Cast().ToArray()); _dispelTypeBox.SelectedIndex = 0; // 参数值变化刷新底部实时预览(类别/选择器/阈值类型/光环筛选经 UpdateParamVisibility 间接刷新)。 _thresholdBox.ValueChanged += (_, _) => UpdatePreview(); _thresholdFieldBox.SelectedIndexChanged += (_, _) => UpdatePreview(); _roleBox.SelectedIndexChanged += (_, _) => UpdatePreview(); _reverseBox.CheckedChanged += (_, _) => UpdatePreview(); _auraBox.SelectedIndexChanged += (_, _) => UpdatePreview(); _auraCountBox.ValueChanged += (_, _) => UpdatePreview(); _dispelTypeBox.SelectedIndexChanged += (_, _) => UpdatePreview(); // ItemCheck 在勾选状态提交前触发, 延后到提交后再读 CheckedItems。 // Seed 期间 CheckAuras 也会触发本事件, 此时窗口句柄尚未创建, 跳过(构造末尾会统一刷新)。 _aurasBox.ItemCheck += (_, _) => { if (IsHandleCreated) { BeginInvoke(new Action(UpdatePreview)); } }; _thresholdModeRow = BuildLabeledRow("阈值类型", _thresholdModeBox); _thresholdRow = BuildLabeledRow("血量阈值 (<)", _thresholdBox); _thresholdLabel = _thresholdRow.Controls.OfType