Files
PiliPlus/lib/common/widgets/svg/level_icon.dart
dom e8a4b19f0f drawPath
Signed-off-by: dom <githubaccount56556@proton.me>
2026-06-25 13:51:29 +08:00

430 lines
9.6 KiB
Dart

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
class UserLevel extends LeafRenderObjectWidget {
const UserLevel(
this.level, {
super.key,
this.height = 11,
this.flash = false,
});
final double height;
final int level;
final bool flash;
@override
RenderObject createRenderObject(BuildContext context) {
return RenderLevel(height, level, flash);
}
@override
void updateRenderObject(
BuildContext context,
RenderLevel renderObject,
) {
renderObject
..height = height
..level = level
..flash = flash;
}
}
class RenderLevel extends RenderBox {
RenderLevel(this._height, this._level, this._flash);
double _height;
set height(double value) {
if (_height == value) return;
_height = value;
markNeedsLayout();
}
int _level;
set level(int value) {
if (_level == value) return;
_level = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
bool _flash;
set flash(bool value) {
if (_flash == value) return;
_flash = value;
markNeedsLayout();
}
@override
Size computeDryLayout(covariant BoxConstraints constraints) {
return Size(
(_flash ? LevelCanvas._extendR : LevelCanvas._totalR) *
_height /
LevelCanvas._totalB,
_height,
);
}
@override
void performLayout() {
size = computeDryLayout(constraints);
}
@override
void paint(PaintingContext context, Offset offset) {
final paint = Paint()..color = lookupBackgroundColor(_level);
LevelCanvas(context.canvas)
..save()
..translate(offset.dx, offset.dy)
..scale(_height / LevelCanvas._totalB)
..drawLevelBack(paint, bolt: _flash)
..drawLevelLv(paint..color = Colors.white)
..drawLEDigit(_level, paint)
..restore();
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.label = '${_flash ? "硬核" : ""}$_level级';
}
static Color lookupBackgroundColor(int level) {
return switch (level) {
0 || 1 => const Color(0xFFC0C0C0),
2 => const Color(0xFF8BD29B),
3 => const Color(0xFF7BCDEF),
4 => const Color(0xFFFEBB8B),
5 => const Color(0xFFEE672A),
_ => const Color(0xFFF04C49),
};
}
}
extension type LevelCanvas(Canvas _) implements Canvas {
// ========== 布局常量 ==========
static const _r = Radius.circular(20);
static const double _left = 629;
static const double _right = 877;
static const double _colW = 68; // 竖段宽度
static const double _lColR = _left + _colW; // 697
static const double _rColL = _right - _colW; // 810
// 三条横线的边界
static const double _rowH = 68;
static const double _rowSp = 146;
static const double _topY = 55;
static const double _topYB = _topY + _rowH; // 123
static const double _midY = _topY + _rowSp; // 201
static const double _midYB = _midY + _rowH; // 269
static const double _botY = _midY + _rowSp; // 347
static const double _botYB = _botY + _rowH; // 415
// 竖段拼接用的中心线
static const double _midMid = (_midY + _midYB) / 2; // 235
static final _boltIcon =
(ParagraphBuilder(
ParagraphStyle(
fontSize: 460,
fontFamily: Icons.bolt_rounded.fontFamily,
height: 1,
fontWeight: FontWeight.w900,
textDirection: TextDirection.ltr,
),
)..addText(.fromCharCode(Icons.bolt_rounded.codePoint))).build()
..layout(const ParagraphConstraints(width: double.infinity));
void drawBolt() => drawParagraph(_boltIcon, const Offset(840, 5));
void _draw1(Paint paint) {
final path = Path()
..addRRect(const .fromLTRBXY(673, _botY, 833, _botYB, 20, 20))
..addRRect(
.fromLTRBAndCorners(
673,
_topY,
787,
_topYB,
topLeft: _r,
bottomLeft: _r,
topRight: _r,
),
)
..addRect(const .fromLTRB(719, _topYB, 787, _botY));
drawPath(path, paint);
}
void drawLEDigit(int digit, Paint paint) {
if (digit == 1) return _draw1(paint);
final bits = switch (digit) {
0 => 0x7E,
2 => 0x6D,
3 => 0x79,
4 => 0x33,
5 => 0x5B,
6 => 0x5F,
7 => 0x70,
8 => 0x7F,
9 => 0x7B,
// _ => throw ArgumentError('Unsupported digit: $digit'),
_ => 0x4F, // `E`
};
_drawSegments(
bits & 0x40 != 0,
bits & 0x20 != 0,
bits & 0x10 != 0,
bits & 0x08 != 0,
bits & 0x04 != 0,
bits & 0x02 != 0,
bits & 0x01 != 0,
paint,
);
}
void _drawSegments(
bool a,
bool b,
bool c,
bool d,
bool e,
bool f,
bool g,
Paint paint,
) {
// 横段
final path = Path();
if (a) {
_addRRect(
_left,
_topY,
_right,
_topYB,
_r,
_r,
f ? .zero : _r,
b ? .zero : _r,
path,
);
}
if (g) {
_addRRect(
_left,
_midY,
_right,
_midYB,
f ? .zero : _r,
b ? .zero : _r,
e ? .zero : _r,
c ? .zero : _r,
path,
);
}
if (d) {
_addRRect(
_left,
_botY,
_right,
_botYB,
e ? .zero : _r,
c ? .zero : _r,
_r,
_r,
path,
);
}
// 竖段
// 左上竖段 f
if (f) {
final top = a ? _topYB : _topY; // 有上横则齐底,否则到顶
final bottom = g ? _midY : (e ? _midMid : _midYB);
final rTop = a ? Radius.zero : _r;
final rBot = g || e ? Radius.zero : _r;
_addRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, path);
}
// 右上竖段 b
if (b) {
final top = a ? _topYB : _topY;
final bottom = g ? _midY : (c ? _midMid : _midYB);
final rTop = a ? Radius.zero : _r;
final rBot = g || c ? Radius.zero : _r;
_addRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, path);
}
// 左下竖段 e
if (e) {
final top = g ? _midYB : (f ? _midMid : _midY);
final bottom = d ? _botY : _botYB;
final rTop = g || f ? Radius.zero : _r;
final rBot = d ? Radius.zero : _r;
_addRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, path);
}
// 右下竖段 c
if (c) {
final top = g ? _midYB : (b ? _midMid : _midY);
final bottom = d ? _botY : _botYB;
final rTop = g || b ? Radius.zero : _r;
final rBot = d ? Radius.zero : _r;
_addRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, path);
}
drawPath(path, paint);
}
/// 绘制圆角矩形,四角全零时退化为矩形
void _addRRect(
double l,
double t,
double r,
double b,
Radius tl,
Radius tr,
Radius bl,
Radius br,
Path path,
) {
if (tl == .zero && tr == .zero && bl == .zero && br == .zero) {
path.addRect(.fromLTRB(l, t, r, b));
} else {
path.addRRect(
.fromLTRBAndCorners(
l,
t,
r,
b,
topLeft: tl,
topRight: tr,
bottomLeft: bl,
bottomRight: br,
),
);
}
}
static const double _vLeft = 296;
static final vV = Path()
..moveTo(_vLeft, 282)
..lineTo(_vLeft, 292)
..arcToPoint(
const Offset(300, 313),
radius: const .circular(50),
clockwise: false,
)
..lineTo(395, 408)
..arcToPoint(
const Offset(419.5, 415),
radius: const .circular(50),
clockwise: false,
)
..arcToPoint(
const Offset(444, 408),
radius: const .circular(50),
clockwise: false,
)
..lineTo(539, 313)
..arcToPoint(
const Offset(543, 292),
radius: const .circular(50),
clockwise: false,
)
..lineTo(543, 282)
..lineTo(476, 282)
..lineTo(419.5, 340)
..lineTo(363, 282)
..close();
void drawLevelLv(Paint paint) {
const double lvTop = 106;
drawPath(
Path()
..addRRect(
.fromLTRBAndCorners(
56,
lvTop,
123,
_botYB,
topLeft: _r,
topRight: _r,
bottomLeft: _r,
),
)
..addRRect(
.fromLTRBAndCorners(
123,
_botY,
256,
_botYB,
topRight: _r,
bottomRight: _r,
),
),
paint,
);
final path = Path()
..addRRect(
RRect.fromLTRBAndCorners(
_vLeft,
lvTop,
363,
282,
topLeft: _r,
topRight: _r,
),
)
..addRRect(
RRect.fromLTRBAndCorners(
476,
lvTop,
543,
282,
topLeft: _r,
topRight: _r,
),
)
..addPath(vV, .zero);
drawPath(path, paint);
}
static const double _totalR = 930;
static const double _extendR = 1250;
static const double _totalB = 466;
void drawLevelBack(Paint paint, {bool bolt = false}) {
const radius = Radius.circular(27);
final double right = bolt ? _extendR : _totalR;
final path = Path()
..addRRect(
RRect.fromLTRBAndCorners(
0,
48,
right,
_totalB,
topLeft: radius,
bottomLeft: radius,
bottomRight: radius,
),
)
..addRRect(
RRect.fromLTRBAndCorners(
576,
0,
right,
48,
topLeft: radius,
topRight: radius,
),
);
drawPath(path, paint);
if (bolt) drawBolt();
}
}