1+ // ==========================================================================================
2+ // GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+ // GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+ // 均受中华人民共和国及相关国际法律法规保护。
5+ // are protected by the laws of the People's Republic of China and relevant international regulations.
6+ //
7+ // 使用本项目须严格遵守相应法律法规及开源许可证之规定。
8+ // Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
9+ //
10+ // 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
11+ // This project is dual-licensed under the MIT License and Apache License 2.0,
12+ // 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
13+ // please refer to the LICENSE file in the root directory of the source code for the full license text.
14+ //
15+ // 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
16+ // It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
17+ // 侵犯他人合法权益等法律法规所禁止的行为!
18+ // or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
19+ // 因基于本项目二次开发所产生的一切法律纠纷与责任,
20+ // Any legal disputes and liabilities arising from secondary development based on this project
21+ // 本项目组织与贡献者概不承担。
22+ // shall be borne solely by the developer; the project organization and contributors assume no responsibility.
23+ //
24+ // GitHub 仓库:https://github.com/GameFrameX
25+ // GitHub Repository: https://github.com/GameFrameX
26+ // Gitee 仓库:https://gitee.com/GameFrameX
27+ // Gitee Repository: https://gitee.com/GameFrameX
28+ // 官方文档:https://gameframex.doc.alianblank.com/
29+ // Official Documentation: https://gameframex.doc.alianblank.com/
30+ // ==========================================================================================
31+
32+ using GameFrameX . Foundation . Extensions ;
33+
34+ namespace GameFrameX . Foundation . Logger ;
35+
36+ /// <summary>
37+ /// 控制台日志显示辅助类
38+ /// </summary>
39+ /// <remarks>
40+ /// <para>
41+ /// 提供了一系列方法用于在控制台中以美观的框架格式显示标题、配置信息和分隔线。
42+ /// 支持自定义框架宽度,并能够自动处理文本居中对齐和字符宽度计算。
43+ /// </para>
44+ /// <para>
45+ /// 主要功能包括:
46+ /// <list type="bullet">
47+ /// <item><description>显示带双线边框的大标题(支持主标题和最多两个子标题)</description></item>
48+ /// <item><description>显示带标题的配置信息框</description></item>
49+ /// <item><description>显示分隔线(可选择包含标题文本)</description></item>
50+ /// <item><description>自定义框架宽度设置</description></item>
51+ /// <item><description>自动处理中英文混合文本的居中对齐</description></item>
52+ /// </list>
53+ /// </para>
54+ /// <para>
55+ /// 使用示例:
56+ /// <code>
57+ /// var console = new LogConsole();
58+ /// console.SetFrameLength(80);
59+ /// console.ShowMaxTitle("系统启动", "GameFrameX", "v1.0.0");
60+ /// console.ShowOption("配置文件", "config.json");
61+ /// console.ShowLineTitle("初始化完成");
62+ /// </code>
63+ /// </para>
64+ /// </remarks>
65+ internal sealed class LogConsole
66+ {
67+ /// <summary>
68+ /// 输出框线的长度,默认为76个字符
69+ /// </summary>
70+ private int _frameLength = 76 ;
71+
72+ /// <summary>
73+ /// 设置输出框架的宽度
74+ /// </summary>
75+ /// <param name="length">框架宽度,必须大于0。建议设置为终端宽度或合适的显示宽度(如80、100等)</param>
76+ /// <exception cref="ArgumentOutOfRangeException">当 <paramref name="length"/> 小于等于0时抛出</exception>
77+ /// <remarks>
78+ /// 框架宽度影响所有输出方法的显示效果,包括标题居中对齐和边框绘制。
79+ /// 设置过小的宽度可能导致文本显示异常,建议最小宽度为20个字符。
80+ /// </remarks>
81+ public void SetFrameLength ( int length )
82+ {
83+ // 验证参数有效性,确保框架宽度为正数
84+ ArgumentOutOfRangeException . ThrowIfGreaterThan ( 0 , length , nameof ( length ) ) ;
85+ _frameLength = length ;
86+ }
87+
88+ /// <summary>
89+ /// 显示带边框的大标题,支持主标题和最多两个子标题
90+ /// </summary>
91+ /// <param name="title">主标题文本,必须提供且不能为null</param>
92+ /// <param name="title2">第一个子标题文本,可选。如果为空字符串、null或仅包含空白字符则不显示</param>
93+ /// <param name="title3">第二个子标题文本,可选。如果为空字符串、null或仅包含空白字符则不显示</param>
94+ /// <remarks>
95+ /// <para>
96+ /// 该方法会创建一个带有双线边框(╔═══╗ 和 ╚═══╝)的标题框。
97+ /// 所有标题文本都会在框架内居中显示,自动处理中英文混合文本的宽度计算。
98+ /// </para>
99+ /// <para>
100+ /// 子标题的显示逻辑:
101+ /// <list type="number">
102+ /// <item><description>主标题始终显示</description></item>
103+ /// <item><description>只有当title2不为空时才显示第一个子标题</description></item>
104+ /// <item><description>只有当title3不为空时才显示第二个子标题</description></item>
105+ /// <item><description>子标题按顺序显示,每个标题占用一行</description></item>
106+ /// </list>
107+ /// </para>
108+ /// <para>
109+ /// 输出后会自动添加一个空行以便与后续内容分隔。
110+ /// </para>
111+ /// </remarks>
112+ /// <example>
113+ /// <code>
114+ /// // 显示单个标题
115+ /// console.ShowMaxTitle("系统启动");
116+ ///
117+ /// // 显示主标题和一个子标题
118+ /// console.ShowMaxTitle("GameFrameX", "日志系统");
119+ ///
120+ /// // 显示完整的三行标题
121+ /// console.ShowMaxTitle("GameFrameX", "日志系统", "v1.0.0");
122+ /// </code>
123+ /// </example>
124+ public void ShowMaxTitle ( string title , string title2 = "" , string title3 = "" )
125+ {
126+ // 生成顶部边框字符串,长度为框架宽度减去左右边框字符
127+ string character = '═' . RepeatChar ( _frameLength - 2 ) ;
128+ Console . WriteLine ( $ "╔{ character } ╗") ;
129+ WriteTitle ( title ) ;
130+
131+ // 检查并显示第一个子标题
132+ if ( title2 . IsNotNullOrEmptyOrWhiteSpace ( ) )
133+ {
134+ WriteTitle ( title2 ) ;
135+ }
136+
137+ // 检查并显示第二个子标题
138+ if ( title3 . IsNotNullOrEmptyOrWhiteSpace ( ) )
139+ {
140+ WriteTitle ( title3 ) ;
141+ }
142+
143+ // 输出底部边框并添加空行分隔
144+ Console . WriteLine ( $ "╚{ character } ╝") ;
145+ Console . WriteLine ( ) ;
146+ }
147+
148+ /// <summary>
149+ /// 在框架内居中显示标题文本
150+ /// </summary>
151+ /// <param name="title">要显示的标题文本,不能为null</param>
152+ /// <exception cref="ArgumentNullException">当 <paramref name="title"/> 为null时抛出</exception>
153+ /// <remarks>
154+ /// <para>
155+ /// 该方法会自动计算文本的显示宽度(考虑中英文字符的不同宽度),
156+ /// 然后在当前设置的框架宽度内进行居中对齐。
157+ /// </para>
158+ /// <para>
159+ /// 居中算法:
160+ /// <list type="number">
161+ /// <item><description>计算文本的实际显示宽度</description></item>
162+ /// <item><description>计算剩余空间并平均分配到左右两侧</description></item>
163+ /// <item><description>如果剩余空间为奇数,在文本后添加一个空格以保持对称</description></item>
164+ /// <item><description>使用║字符作为左右边框</description></item>
165+ /// </list>
166+ /// </para>
167+ /// </remarks>
168+ private void WriteTitle ( string title )
169+ {
170+ ArgumentNullException . ThrowIfNull ( title , nameof ( title ) ) ;
171+ // 获取文本的实际显示宽度(中文字符宽度为2,英文字符宽度为1)
172+ var stringWidth = title . GetDisplayWidth ( ) ;
173+ var remaining = _frameLength - stringWidth - 2 ;
174+ var surplus = remaining % 2 ;
175+ // 如果剩余空间为奇数,在文本后添加一个空格以保持左右对称
176+ if ( surplus > 0 )
177+ {
178+ title += " " ;
179+ }
180+
181+ remaining /= 2 ;
182+ string padding = ' ' . RepeatChar ( remaining ) ;
183+ Console . WriteLine ( $ "║{ padding } { title } { padding } ║") ;
184+ }
185+
186+ /// <summary>
187+ /// 显示带标题的配置信息框
188+ /// </summary>
189+ /// <param name="title">配置项标题,不能为null</param>
190+ /// <param name="content">配置内容,将调用ToString()方法显示,不能为null</param>
191+ /// <exception cref="ArgumentNullException">当 <paramref name="title"/> 或 <paramref name="content"/> 为null时抛出</exception>
192+ /// <remarks>
193+ /// <para>
194+ /// 该方法会创建一个带有标题的配置信息显示框,格式如下:
195+ /// <code>
196+ /// ╔═══配置标题═══╗
197+ /// 配置内容
198+ /// ╚═════════════╝
199+ /// </code>
200+ /// </para>
201+ /// <para>
202+ /// 标题会在顶部边框中居中显示,内容直接输出在框架内部。
203+ /// 输出后会自动添加一个空行以便与后续内容分隔。
204+ /// </para>
205+ /// </remarks>
206+ /// <example>
207+ /// <code>
208+ /// console.ShowOption("数据库连接", "Server=localhost;Database=GameFrameX");
209+ /// console.ShowOption("日志级别", LogLevel.Information);
210+ /// </code>
211+ /// </example>
212+ public void ShowOption ( string title , object content )
213+ {
214+ ArgumentNullException . ThrowIfNull ( title , nameof ( title ) ) ;
215+ ArgumentNullException . ThrowIfNull ( content , nameof ( content ) ) ;
216+ // 计算标题的显示宽度并生成居中的顶部边框
217+ int stringWidth = title . GetDisplayWidth ( ) ;
218+ int remaining = _frameLength - stringWidth - 2 ;
219+ remaining /= 2 ;
220+ string padding = '═' . RepeatChar ( remaining ) ;
221+ // 生成底部边框,使用完整的等号字符填充
222+ string character = '═' . RepeatChar ( _frameLength - 2 ) ;
223+ Console . WriteLine ( $ "╔{ padding } { title } { padding } ╗") ;
224+ Console . WriteLine ( content ) ;
225+ Console . WriteLine ( $ "╚{ character } ╝") ;
226+ Console . WriteLine ( ) ;
227+ }
228+
229+ /// <summary>
230+ /// 显示分隔线,可选择是否包含标题文本
231+ /// </summary>
232+ /// <param name="title">分隔线中的标题文本,为空时显示纯分隔线。不能为null</param>
233+ /// <exception cref="ArgumentNullException">当 <paramref name="title"/> 为null时抛出</exception>
234+ /// <remarks>
235+ /// <para>
236+ /// 该方法用于在控制台输出中创建视觉分隔效果,支持两种模式:
237+ /// </para>
238+ /// <para>
239+ /// 1. 纯分隔线模式(title为空字符串):
240+ /// <code>
241+ /// ═══════════════════════════════════════
242+ /// </code>
243+ /// </para>
244+ /// <para>
245+ /// 2. 带标题的分隔线模式:
246+ /// <code>
247+ /// ═══════════标题文本═══════════
248+ /// </code>
249+ /// </para>
250+ /// <para>
251+ /// 如果标题文本过长(超出框架宽度-4个字符),会使用简化格式:═══标题═══
252+ /// 输出后会自动添加一个空行以便与后续内容分隔。
253+ /// </para>
254+ /// </remarks>
255+ /// <example>
256+ /// <code>
257+ /// // 显示纯分隔线
258+ /// console.ShowLineTitle("");
259+ ///
260+ /// // 显示带标题的分隔线
261+ /// console.ShowLineTitle("初始化完成");
262+ /// console.ShowLineTitle("系统配置");
263+ /// </code>
264+ /// </example>
265+ public void ShowLineTitle ( string title = "" )
266+ {
267+ ArgumentNullException . ThrowIfNull ( title , nameof ( title ) ) ;
268+ var stringWidth = title . GetDisplayWidth ( ) ;
269+ // 如果标题过长,使用简单的三等号包围格式
270+ if ( stringWidth - 4 > _frameLength )
271+ {
272+ Console . WriteLine ( $ "═══{ title } ═══") ;
273+ }
274+ else
275+ {
276+ // 计算剩余空间并平均分配到标题两侧
277+ var remaining = _frameLength - stringWidth ;
278+ remaining /= 2 ;
279+ var padding = '═' . RepeatChar ( remaining ) ;
280+ // 输出居中的分隔线,标题两侧用等号字符填充
281+ Console . WriteLine ( ( padding + title + padding ) ) ;
282+ }
283+
284+ // 添加空行以便与后续内容分隔
285+ Console . WriteLine ( ) ;
286+ }
287+ }
0 commit comments