사용자 화면 문구·표본 데이터 · 퀘스트 참여 흐름 · HOME 진입 구획 · 미니맵 · 공통 확인 창 배치 수정
This commit is contained in:
@@ -19,6 +19,8 @@ import '../../../core/i18n/app_locale.dart';
|
||||
/// - **게이지 조절 8개는 넣지 않는다(C-51).**
|
||||
/// - 색은 테마 결정 전까지 프로토타입 값을 쓴다(C-49).
|
||||
/// - 데이터는 받은 [GalaxySnapshot] 만 쓴다. 프로토타입의 표본 5행성은 쓰지 않는다.
|
||||
/// - 궤도는 FPI 순위, 첫 화면 시점은 내 행성 중앙(C-103). 140pt 에서는 내 행성 간략 카드
|
||||
/// 한 장만, 다른 행성 HUD 없음(C-105). 드래그 회전은 관성 없음(C-105).
|
||||
///
|
||||
/// 반응: 스냅샷이 바뀌면(순위·FPI) 행성이 새 궤도로 미끄러지고 HUD 숫자가 바뀐다(프로토타입
|
||||
/// `updateFPI`). 가로로 끌면 장면이 돈다(±180°, 프로토타입 View Rotation 범위). 누르면 [onOpen].
|
||||
@@ -33,7 +35,7 @@ class GalaxyMiniMap extends StatefulWidget {
|
||||
final GalaxySnapshot data;
|
||||
final VoidCallback onOpen;
|
||||
|
||||
/// 정사각 한 변. HOME 행성 영역 우측 상단(Q-DS-06 권고안 A · 회신 전 임시).
|
||||
/// 정사각 한 변. HOME 은 약 140pt(C-105 · 자리는 C-113 → `HomeHero.miniMapAlignment`).
|
||||
final double size;
|
||||
|
||||
@override
|
||||
@@ -172,6 +174,10 @@ final _tanHalfFov = math.tan(35 * math.pi / 180);
|
||||
const _hudW = 512.0;
|
||||
const _hudH = 256.0;
|
||||
|
||||
/// 별·위성 점 크기(논리 px). 프로토타입 점은 원근으로 1px 아래로 줄어 GL 최소 크기(버퍼
|
||||
/// 1px, 픽셀비 2 → 0.5pt)로 찍힌다. 0 이면 1 물리 px 이라 3x 기기에서 1/3 로 옅어진다.
|
||||
const _dot = 0.4;
|
||||
|
||||
/// 1 논리 픽셀보다 가는 선은 머리카락 선(1 물리 픽셀)으로.
|
||||
double _px(double w) => w < 1 ? 0 : w;
|
||||
|
||||
@@ -293,18 +299,22 @@ ui.Picture _hudPicture(PlanetNode p, Color color, double share) {
|
||||
return rec.endRecording();
|
||||
}
|
||||
|
||||
/// 반지름 1 단위의 광륜(6R, 가산 합성) + 발광 구체.
|
||||
/// 프로토타입 광륜 반지름(행성 반지름 배수). `aura.scale = R × curScale × 6` 이 다시
|
||||
/// curScale(Mass x1.5)로 커지는 group 안에 있어 지름은 `6 × 1.5 R` 이다.
|
||||
const _auraR = 3 * _massVolume;
|
||||
|
||||
/// 반지름 1 단위의 광륜(가산 합성) + 발광 구체.
|
||||
ui.Picture _spherePicture(Color c) {
|
||||
final rec = ui.PictureRecorder();
|
||||
final canvas = Canvas(rec);
|
||||
canvas.drawCircle(
|
||||
Offset.zero,
|
||||
3,
|
||||
_auraR,
|
||||
Paint()
|
||||
..blendMode = BlendMode.plus
|
||||
..shader = ui.Gradient.radial(
|
||||
Offset.zero,
|
||||
3,
|
||||
_auraR,
|
||||
[
|
||||
c.withValues(alpha: 0.4),
|
||||
c.withValues(alpha: 0.15),
|
||||
@@ -360,19 +370,22 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
/// 그리는 행성 수(내 행성은 순위 밖이어도 더한다).
|
||||
static const maxPlanets = 10;
|
||||
|
||||
/// 간략 카드(내 행성)의 최대 폭(미니맵 폭 대비).
|
||||
static const hudFloor = 0.7;
|
||||
/// 간략 카드(내 행성)의 최대 폭(미니맵 폭 대비). 넘치면 카드를 통째로 줄인다(숫자는 자르지 않는다).
|
||||
static const hudFloor = 0.55;
|
||||
|
||||
/// 이 폭 이상이면 프로토타입 HUD 카드를 모든 행성에 그대로 그린다.
|
||||
static const fullHudMin = 280.0;
|
||||
|
||||
/// 간략 카드 글자 크기(pt) — 이름 · 둘째 줄.
|
||||
static const compactNamePt = 11.0;
|
||||
static const compactLinePt = 9.0;
|
||||
/// 간략 카드 글자 크기(pt) — 이름 · 숫자 줄. 프로토타입 텍스처의 줄 구성(이름 / RANK · FPI /
|
||||
/// LIVE)을 세 줄로 줄인 것이다. 140pt 에서 카드가 장면을 덮지 않게 한 줄을 짧게 둔다.
|
||||
static const compactNamePt = 9.0;
|
||||
static const compactLinePt = 8.0;
|
||||
|
||||
/// 간략 카드 둘째 줄 — `#순위 · FPI n · LIVE n`.
|
||||
static String compactLine(PlanetNode p) =>
|
||||
'#${p.rank} · FPI ${formatPop(p.fpi)} · LIVE ${formatPop(p.online)}';
|
||||
/// 간략 카드 숫자 줄 — `#순위 · FPI n` / `LIVE n`.
|
||||
static List<String> compactLines(PlanetNode p) => [
|
||||
'#${p.rank} · FPI ${formatPop(p.fpi)}',
|
||||
'LIVE ${formatPop(p.online)}',
|
||||
];
|
||||
|
||||
ui.Picture? _compact;
|
||||
Object? _compactKey;
|
||||
@@ -388,42 +401,49 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
if (key == _compactKey && _compact != null) return _compact!;
|
||||
_compact?.dispose();
|
||||
_compactKey = key;
|
||||
const pad = 6.0, barW = 3.0, gap = 4.0;
|
||||
final textMax = maxW - pad * 2 - barW - gap;
|
||||
TextPainter line(String text, double pt, FontWeight weight, Color c) =>
|
||||
TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
fontFamily: AppFonts.body,
|
||||
fontSize: pt,
|
||||
fontWeight: weight,
|
||||
height: 1.2,
|
||||
color: c,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: textMax);
|
||||
final name = line(
|
||||
b.node.name.toUpperCase(),
|
||||
compactNamePt,
|
||||
FontWeight.w800,
|
||||
b.color,
|
||||
);
|
||||
// 좁으면 LIVE 앞에서 둘째 줄이 한 번 꺾인다(글자 크기는 줄이지 않는다).
|
||||
final stats = line(
|
||||
compactLine(b.node),
|
||||
compactLinePt,
|
||||
FontWeight.w700,
|
||||
const Color(0xE6FFFFFF),
|
||||
);
|
||||
final textW = math.max(name.width, stats.width);
|
||||
const pad = 4.0, barW = 2.0, gap = 3.0;
|
||||
TextPainter line(
|
||||
String text,
|
||||
double pt,
|
||||
FontWeight weight,
|
||||
Color c, [
|
||||
double maxWidth = double.infinity,
|
||||
]) => TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
fontFamily: AppFonts.body,
|
||||
fontSize: pt,
|
||||
fontWeight: weight,
|
||||
height: 1.15,
|
||||
color: c,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
maxLines: 1,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth);
|
||||
final texts = [
|
||||
(b.node.name.toUpperCase(), compactNamePt, FontWeight.w800, b.color),
|
||||
for (final t in compactLines(b.node))
|
||||
(t, compactLinePt, FontWeight.w700, const Color(0xE6FFFFFF)),
|
||||
];
|
||||
final stats = [
|
||||
for (final (t, pt, w, c) in texts.skip(1)) line(t, pt, w, c),
|
||||
];
|
||||
final statsW = stats.fold<double>(0, (m, l) => math.max(m, l.width));
|
||||
// 긴 이름은 말줄임 — 카드 폭은 숫자 줄(또는 최대 폭)이 정한다.
|
||||
final (nt, npt, nw, nc) = texts.first;
|
||||
final nameMax = math.max(statsW, maxW - pad * 2 - gap - barW);
|
||||
final lines = [line(nt, npt, nw, nc, nameMax), ...stats];
|
||||
final textW = lines.fold<double>(0, (m, l) => math.max(m, l.width));
|
||||
final textH = lines.fold<double>(0, (m, l) => m + l.height);
|
||||
final w = pad * 2 + textW + gap + barW;
|
||||
final h = pad * 2 + name.height + 2 + stats.height;
|
||||
final h = pad * 2 + textH;
|
||||
// 숫자 줄이 최대 폭을 넘을 때만 카드를 줄인다 — 줄을 자르면 순위·FPI 숫자가 사라진다.
|
||||
final k = math.min(1.0, maxW / w);
|
||||
final rec = ui.PictureRecorder();
|
||||
final canvas = Canvas(rec);
|
||||
final canvas = Canvas(rec)..scale(k);
|
||||
final box = Rect.fromLTWH(0, 0, w, h);
|
||||
canvas.drawRect(box, Paint()..color = const Color(0xE6000514));
|
||||
canvas.drawRect(
|
||||
@@ -433,8 +453,13 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
..strokeWidth = 1
|
||||
..color = b.color.withValues(alpha: 0.8),
|
||||
);
|
||||
name.paint(canvas, const Offset(pad, pad));
|
||||
stats.paint(canvas, Offset(pad, pad + name.height + 2));
|
||||
var y = pad;
|
||||
for (final l in lines) {
|
||||
l.paint(canvas, Offset(pad, y));
|
||||
y += l.height;
|
||||
l.dispose();
|
||||
}
|
||||
// 프로토타입 막대 `160 * fpi / 5000` — 1위 대비 비율.
|
||||
final barH = h - pad * 2;
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
@@ -445,13 +470,8 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
),
|
||||
Paint()..color = _emerald,
|
||||
);
|
||||
compactText = [
|
||||
(b.node.name.toUpperCase(), compactNamePt),
|
||||
(compactLine(b.node), compactLinePt),
|
||||
];
|
||||
name.dispose();
|
||||
stats.dispose();
|
||||
_compactSize = Size(w, h);
|
||||
compactText = [for (final (t, pt, _, _) in texts) (t, pt * k)];
|
||||
_compactSize = Size(w * k, h * k);
|
||||
return _compact = rec.endRecording();
|
||||
}
|
||||
|
||||
@@ -466,6 +486,15 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
@visibleForTesting
|
||||
Color? colorOf(String id) => _bodies[id]?.color;
|
||||
|
||||
/// 행성의 현재 월드 좌표(x, z). 장면 원점 = 화면 중앙. 없으면 null.
|
||||
@visibleForTesting
|
||||
Offset? worldOf(String id) =>
|
||||
_bodies[id] == null ? null : Offset(_bodies[id]!.wx, _bodies[id]!.wz);
|
||||
|
||||
/// 마지막으로 그린 간략 카드 크기(논리 px).
|
||||
@visibleForTesting
|
||||
Size get compactSize => _compactSize;
|
||||
|
||||
/// 행성의 현재 궤도 반지름(월드 단위). 없으면 null.
|
||||
@visibleForTesting
|
||||
double? orbitOf(String id) => _bodies[id]?.rad;
|
||||
@@ -607,13 +636,14 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
..shader = ui.Gradient.radial(
|
||||
center,
|
||||
r,
|
||||
const [Color(0x0A10B981), Color(0x033B82F6), Color(0x00000000)],
|
||||
const [Color(0x0410B981), Color(0x013B82F6), Color(0x00000000)],
|
||||
const [0, 0.4, 0.8],
|
||||
),
|
||||
);
|
||||
canvas.restore();
|
||||
}
|
||||
// 프로토타입 별 8000개. 화면 넓이당 밀도는 390×844 화면 기준으로 맞춘다.
|
||||
// 프로토타입 별 8000개. 미니맵은 휴대폰 전체 화면 장면의 축소판으로 보이게, 화면 넓이당
|
||||
// 밀도를 390×844 기준으로 맞춘다(덜어내지 않으면 140pt 에 별이 5배 빽빽하다).
|
||||
final keep = (size.width * size.height / (390 * 844)).clamp(0.0, 1.0);
|
||||
final pts = <double>[];
|
||||
for (var i = 0; i < 8000; i++) {
|
||||
@@ -632,7 +662,7 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
ui.PointMode.points,
|
||||
Float32List.fromList(pts),
|
||||
Paint()
|
||||
..strokeWidth = 0
|
||||
..strokeWidth = _dot
|
||||
..color = const Color(0xCCBFBFFF),
|
||||
);
|
||||
_bg = rec.endRecording();
|
||||
@@ -708,11 +738,9 @@ class GalaxyMiniMapPainter extends CustomPainter {
|
||||
}
|
||||
if (out.isNotEmpty) {
|
||||
_fill
|
||||
..strokeWidth = 0
|
||||
..strokeWidth = _dot
|
||||
..blendMode = BlendMode.plus
|
||||
..color = b.color.withValues(
|
||||
alpha: 0.9 * (2 * _scale * focal / d).clamp(0.2, 1.0),
|
||||
);
|
||||
..color = b.color.withValues(alpha: 0.9);
|
||||
canvas.drawRawPoints(ui.PointMode.points, out, _fill);
|
||||
_fill.blendMode = BlendMode.srcOver;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user