Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions PCL.Core/UI/Dialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using PCL.Core.App.Localization;

namespace PCL.Core.UI;

public class DialogButton
{
public string Text { get; set; }
public Action? OnClick { get; set; }
public bool IsPrimary { get; set; }

public DialogButton(string text, Action? onClick = null, bool isPrimary = false)
{
Text = text;
OnClick = onClick;
IsPrimary = isPrimary;
}
}

public static class Dialog
{
public static event Action<DialogContext>? OnShow;

public static int Show(string caption, string? title = null,
DialogTheme theme = DialogTheme.Info, bool block = true,
params string[] buttons)
{
return Show(new DialogContext
{
Caption = caption,
Title = title ?? Lang.Text("Common.Dialog.Title"),
Theme = theme,
Block = block,
Content = null,
Buttons = BuildButtons(buttons, theme),
});
}

public static int Show(DialogContext context)
{
if (context.Buttons.Count == 0)
context.Buttons.Add(new DialogButton(Lang.Text("Common.Action.Confirm")));
OnShow?.Invoke(context);
return context.Result;
}

private static Collection<DialogButton> BuildButtons(string[] buttonTexts, DialogTheme theme)
{
var list = new Collection<DialogButton>();
for (var i = 0; i < buttonTexts.Length; i++)
{
list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0));
}
return list;
}
}

public enum DialogTheme
{
Info,
Warning,
Error
}

public class DialogContext
{
public string Caption { get; set; } = "";
public string Title { get; set; } = "";
public DialogTheme Theme { get; set; } = DialogTheme.Info;
public bool Block { get; set; } = true;
public object? Content { get; set; }
public Collection<DialogButton> Buttons { get; set; } = [];
public int Result { get; set; }
}
21 changes: 18 additions & 3 deletions PCL.Core/UI/MsgBoxWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using PCL.Core.App.Localization;

namespace PCL.Core.UI;

[Obsolete("Use PCL.Core.UI.Dialog instead")]
public record MsgBoxButtonInfo(
string Context,
int Value = 0,
Action? OnClick = null
);

[Obsolete("Use PCL.Core.UI.DialogTheme instead")]
public enum MsgBoxTheme
{
Info,
Expand All @@ -21,15 +24,16 @@
public delegate void MsgBoxHandler(
string message,
string caption,
ICollection<MsgBoxButtonInfo> buttons,

Check warning on line 27 in PCL.Core/UI/MsgBoxWrapper.cs

View workflow job for this annotation

GitHub Actions / build (CI, ARM64) / Build

'MsgBoxButtonInfo' is obsolete: 'Use PCL.Core.UI.Dialog instead'

Check warning on line 27 in PCL.Core/UI/MsgBoxWrapper.cs

View workflow job for this annotation

GitHub Actions / build (CI, x64) / Build

'MsgBoxButtonInfo' is obsolete: 'Use PCL.Core.UI.Dialog instead'
MsgBoxTheme theme,

Check warning on line 28 in PCL.Core/UI/MsgBoxWrapper.cs

View workflow job for this annotation

GitHub Actions / build (CI, ARM64) / Build

'MsgBoxTheme' is obsolete: 'Use PCL.Core.UI.DialogTheme instead'

Check warning on line 28 in PCL.Core/UI/MsgBoxWrapper.cs

View workflow job for this annotation

GitHub Actions / build (CI, x64) / Build

'MsgBoxTheme' is obsolete: 'Use PCL.Core.UI.DialogTheme instead'
bool block,
ref int result
);

[Obsolete("Use PCL.Core.UI.Dialog instead")]
public static class MsgBoxWrapper
{
public static event MsgBoxHandler? OnShow;

Check warning on line 36 in PCL.Core/UI/MsgBoxWrapper.cs

View workflow job for this annotation

GitHub Actions / build (CI, ARM64) / Build

The event 'MsgBoxWrapper.OnShow' is never used

Check warning on line 36 in PCL.Core/UI/MsgBoxWrapper.cs

View workflow job for this annotation

GitHub Actions / build (CI, x64) / Build

The event 'MsgBoxWrapper.OnShow' is never used

public static int ShowWithCustomButtons(
string message,
Expand All @@ -38,9 +42,20 @@
bool block,
ICollection<MsgBoxButtonInfo> buttonCollection)
{
var result = 0;
if (buttonCollection.Count == 0) buttonCollection = [new MsgBoxButtonInfo(Lang.Text("Common.Action.Confirm"))];
OnShow?.Invoke(message, caption, buttonCollection, theme, block, ref result);
var buttons = new Collection<DialogButton>();
foreach (var btn in buttonCollection)
{
buttons.Add(new DialogButton(btn.Context, btn.OnClick));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve custom button result values

When legacy callers use MsgBoxWrapper.ShowWithCustomButtons with MsgBoxButtonInfo.Value values, this conversion drops the value and creates each DialogButton with the default id of 0. Dialog_OnShow then falls back to positional IDs, so selecting a button returns 1/2/3 instead of the caller-specified value; pass btn.Value into the DialogButton id to keep the existing result contract.

Useful? React with 👍 / 👎.

var result = Dialog.Show(new DialogContext

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve legacy MsgBoxWrapper handlers

When existing code subscribes to MsgBoxWrapper.OnShow, this wrapper path now bypasses that public event and calls Dialog.Show(...) directly, so legacy hosts/tests that still rely on the obsolete API never get a chance to render the dialog or set the ref result. Since the event remains exposed and FormMain still registers a handler for it, either bridge/invoke the legacy event here or make the old API fail explicitly instead of silently ignoring subscribers.

Useful? React with 👍 / 👎.

{
Caption = message,
Title = caption,
Theme = (DialogTheme)(int)theme,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): MsgBoxButtonInfo.Value 被丢弃了,可能会影响依赖自定义结果代码的调用方。

之前,ShowWithCustomButtons 会把每个 MsgBoxButtonInfo.Value 传递给 ref int result,但新的实现只把 Context/文本和 OnClick 带入 DialogButton / DialogContext.Result。依赖特定 Value 码的调用方现在只能看到位置索引(或者在从未设置时为 0)。如果这个 API 仍然需要保持向后兼容,就需要要么通过 DialogContext 传递 Value 并在选择时映射回去,要么在文档中明确说明会忽略 Value,且结果仅基于索引。

Original comment in English

issue (bug_risk): MsgBoxButtonInfo.Value is dropped, potentially breaking callers that depend on custom result codes.

Previously, ShowWithCustomButtons propagated each MsgBoxButtonInfo.Value into the ref int result, but the new implementation only carries Context/text and OnClick into DialogButton / DialogContext.Result. Callers that depended on specific Value codes will now see only positional indices (or 0 if never set). If this API is still expected to be backward-compatible, you’ll need to either pass Value through DialogContext and map it back on selection, or clearly document that Value is ignored and results are purely index-based.

Block = block,
Content = null,
Buttons = buttons,
});
return result;
}

Expand Down
181 changes: 181 additions & 0 deletions Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;
using PCL.Core.UI.Controls;

namespace PCL;

public partial class DialogControl
{
private int _result;
private bool _exited;
private readonly int _uuid = ModBase.GetUuid();
private readonly List<MyButton> _buttons = [];

public DispatcherFrame WaitFrame { get; } = new(true);

public string Title
{
get => LabTitle.Text;
set => LabTitle.Text = value;
}

public bool IsWarn { get; set; }

public UIElement? DialogContent
{
get => (UIElement?)ContentArea.Content;
set => ContentArea.Content = value;
}

public int Result => _result;

public DialogControl()
{
try
{
InitializeComponent();
ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d);
}
catch (Exception ex)
{
ModBase.Log(ex, "DialogControl 初始化失败", ModBase.LogLevel.Hint);
}

Loaded += OnLoad;
}

public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false)
{
var btn = new MyButton
{
Text = text,
ColorType = isPrimary
? (IsWarn ? MyButton.ColorState.Red : MyButton.ColorState.Highlight)
: MyButton.ColorState.Normal,
Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible,
IsEnabled = true,
};
btn.ApplyTemplate();
btn.TextPadding = new Thickness(7);
btn.Padding = new Thickness(5, 0, 5, 0);
btn.Margin = new Thickness(12, 0, 0, 0);
btn.Name += ModBase.GetUuid();
var index = _buttons.Count + 1;
btn.Click += (_, _) =>
{
if (_exited) return;
if (onClick is not null)
{
onClick();
}
else
{
_exited = true;
_result = index;
Close();
}
};
_buttons.Add(btn);
PanBtn.Children.Add(btn);
return btn;
}

public event Action<int>? OnClosed;

private void OnLoad(object sender, RoutedEventArgs e)
{
try
{
if (_buttons.Count > 1 && _buttons[0].ColorType != MyButton.ColorState.Red)
_buttons[0].ColorType = MyButton.ColorState.Highlight;
if (_buttons.Count > 0)
_buttons[0].Focus();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore initial focus to input prompts

DialogControl always focuses the first button during load. Since MyMsgBoxInput prompts are now built with this container, the text box no longer receives the caret as the old input dialog did, so users typing immediately into prompts such as instance/profile names type nowhere until they click the field. Let input dialogs request focus for their MyTextBox instead of unconditionally focusing the first button.

Useful? React with 👍 / 👎.


Opacity = 0d;
ModAnimation.AniStart(
ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty,
(IsWarn
? new ModBase.MyColor(140d, 80d, 0d, 0d)
: new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200),
"PanMsgBackground Background");
ModAnimation.AniStart(
new ModAnimation.AniData[]
{
ModAnimation.AaOpacity(this, 1d, 120, 60),
ModAnimation.AaDouble(i => TransformPos.Y += (double)i,
-TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)),
ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i,
-TransformRotate.Angle, 300, 60,
new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))
}, "DialogControl " + _uuid);

ModBase.Log("[Dialog] " + LabTitle.Text);
}
catch (Exception ex)
{
ModBase.Log(ex, "DialogControl 加载失败", ModBase.LogLevel.Hint);
}
}

public void Close()
{
if (_exited && _result == 0)
_result = -1;
_exited = true;

try
{
WaitFrame.Continue = false;
}
catch
{
// ignore
}

try
{
ComponentDispatcher.PopModal();
}
catch
{
// ignore
}

OnClosed?.Invoke(_result);

ModAnimation.AniStart(
new ModAnimation.AniData[]
{
ModAnimation.AaCode(() =>
{
if (!ModMain.WaitingMyMsgBox.Any())
ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground,
BlurBorder.BackgroundProperty,
new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200,
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)));
}, 30),
ModAnimation.AaOpacity(this, -Opacity, 80, 20),
ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y,
150, 0, new ModAnimation.AniEaseOutFluent()),
ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i,
6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)),
ModAnimation.AaCode(() => ((Grid)Parent)?.Children.Remove(this), after: true)
}, "DialogControl " + _uuid);
}

private void Drag(object sender, MouseButtonEventArgs e)
{
try
{
if (e.LeftButton == MouseButtonState.Pressed && e.GetPosition(ShapeLine).Y <= 2d)
ModMain.frmMain.DragMove();
}
catch (Exception ex)
{
ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint);
}
}
}
41 changes: 41 additions & 0 deletions Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Grid x:Class="PCL.DialogControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:PCL"
RenderTransformOrigin="0,0.5" UseLayoutRounding="True" SnapsToDevicePixels="True" MinWidth="400"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="25">
<Grid.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="TransformRotate" Angle="-4" />
<TranslateTransform x:Name="TransformPos" X="0" Y="40" />
</TransformGroup>
</Grid.RenderTransform>
<Border Name="PanBorder" CornerRadius="7" Background="{DynamicResource ColorBrushBackground}">
<Border.Effect>
<DropShadowEffect Color="{DynamicResource ColorObjectMsgBoxShadow}" BlurRadius="20" ShadowDepth="2"
RenderingBias="Performance" Opacity="0.8" x:Name="EffectShadow" />
</Border.Effect>
<Grid Name="PanMain" VerticalAlignment="Top" Margin="22,22,22,23">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2" />
<RowDefinition Height="13" />
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}"
HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9"
VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False"
MouseLeftButtonDown="Drag" />
<Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" />
<my:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="ContentScrollViewer" Margin="0,0,0,17"
Padding="7,0,15,0"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
DeltaMult="0.7">
<ContentControl x:Name="ContentArea" Focusable="False" />
</my:MyScrollViewer>
<StackPanel Grid.Row="4" Name="PanBtn" VerticalAlignment="Top" HorizontalAlignment="Right"
Margin="150,0,8,0" Orientation="Horizontal" />
</Grid>
</Border>
</Grid>
2 changes: 2 additions & 0 deletions Plain Craft Launcher 2/FormMain.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public FormMain()
AddHandler(DragDrop.DragOverEvent, new DragEventHandler(HandleDrag), true);
// 注册 MsgBox 事件
MsgBoxWrapper.OnShow += ModMain.MsgBoxWrapper_OnShow;
// 注册 Dialog 事件
PCL.Core.UI.Dialog.OnShow += ModMain.Dialog_OnShow;
// 注册 Hint 事件
HintWrapper.OnShow += ModMain.HintWrapper_OnShow;
// 加载 UI
Expand Down
Loading
Loading