focus-flow/apps/focus_flow_flutter/lib/widgets/timeline/timeline_axis.dart

119 lines
3.5 KiB
Dart

/// Renders Timeline Axis pieces for the FocusFlow Flutter timeline.
library;
import 'package:flutter/material.dart';
import '../../theme/focus_flow_tokens.dart';
import 'timeline_geometry.dart';
/// Time labels and rail shown beside the compact timeline.
class TimelineAxis extends StatelessWidget {
/// Creates a timeline axis for [geometry].
const TimelineAxis({required this.geometry, this.topInset = 12, super.key});
/// Geometry used to position labels and tick marks.
final TimelineGeometry geometry;
/// Vertical inset before the first timeline tick.
final double topInset;
@override
Widget build(BuildContext context) {
final ticks = geometry.ticks();
return SizedBox(
width: FocusFlowTokens.timelineRailWidth,
height: geometry.totalHeight + topInset + 24,
child: Stack(
children: [
Positioned(
left: 92,
top: topInset,
bottom: 0,
child: Container(width: 2, color: FocusFlowTokens.rail),
),
for (final tick in ticks)
Positioned(
top: tick.y + topInset - 10,
left: 0,
width: 86,
child: Text(
tick.label,
maxLines: 1,
softWrap: false,
overflow: TextOverflow.visible,
textAlign: TextAlign.right,
style: TextStyle(
color: tick.major
? FocusFlowTokens.textPrimary
: FocusFlowTokens.textFaint,
fontSize: tick.major ? 16 : 14,
fontWeight: tick.major ? FontWeight.w600 : FontWeight.w400,
),
),
),
for (final tick in ticks.where((tick) => tick.major))
Positioned(
top: tick.y + topInset - 6,
left: 87,
child: const DecoratedBox(
decoration: BoxDecoration(
color: FocusFlowTokens.rail,
shape: BoxShape.circle,
),
child: SizedBox(width: 12, height: 12),
),
),
],
),
);
}
}
/// Background grid aligned to timeline tick marks.
class TimelineGrid extends StatelessWidget {
/// Creates a timeline grid for [geometry].
const TimelineGrid({required this.geometry, this.topInset = 12, super.key});
/// Geometry used to position grid lines.
final TimelineGeometry geometry;
/// Vertical inset before the first timeline tick.
final double topInset;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _TimelineGridPainter(geometry: geometry, topInset: topInset),
size: Size.infinite,
);
}
}
class _TimelineGridPainter extends CustomPainter {
const _TimelineGridPainter({required this.geometry, required this.topInset});
final TimelineGeometry geometry;
final double topInset;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = FocusFlowTokens.gridLine
..strokeWidth = 1;
for (final tick in geometry.ticks()) {
canvas.drawLine(
Offset(0, tick.y + topInset),
Offset(size.width, tick.y + topInset),
paint
..color = tick.major
? FocusFlowTokens.gridLine.withValues(alpha: 0.72)
: FocusFlowTokens.gridLine,
);
}
}
@override
bool shouldRepaint(covariant _TimelineGridPainter oldDelegate) {
return oldDelegate.geometry != geometry || oldDelegate.topInset != topInset;
}
}