Files
zendegi/packages/z-timeline/lib/src/widgets/ghost_overlay.dart

78 lines
2.2 KiB
Dart

import 'package:flutter/material.dart';
import '../constants.dart';
import '../models/entry_drag_state.dart';
import '../services/layout_coordinate_service.dart';
import '../services/time_scale_service.dart';
import '../state/timeline_viewport_notifier.dart';
/// A semi-transparent ghost overlay showing where an entry will land.
///
/// Displayed during drag operations to give visual feedback about the
/// target position.
class GhostOverlay extends StatelessWidget {
const GhostOverlay({
required this.dragState,
required this.viewport,
required this.contentWidth,
required this.laneHeight,
super.key,
});
final EntryDragState dragState;
final TimelineViewportNotifier viewport;
final double contentWidth;
final double laneHeight;
@override
Widget build(BuildContext context) {
final startX = TimeScaleService.mapTimeToPosition(
dragState.targetStart,
viewport.start,
viewport.end,
);
final endX = TimeScaleService.mapTimeToPosition(
dragState.targetEnd,
viewport.start,
viewport.end,
);
// Use centralized coordinate service to ensure ghost matches pill layout
final left = LayoutCoordinateService.normalizedToWidgetX(
normalizedX: startX,
contentWidth: contentWidth,
);
final width = LayoutCoordinateService.calculateItemWidth(
normalizedWidth: endX - startX,
contentWidth: contentWidth,
);
final top = LayoutCoordinateService.laneToY(
lane: dragState.targetLane,
laneHeight: laneHeight,
);
final scheme = Theme.of(context).colorScheme;
return Positioned(
left: left.clamp(0.0, double.infinity),
width: width.clamp(0.0, double.infinity),
top: top,
height: laneHeight,
child: IgnorePointer(
child: Container(
decoration: BoxDecoration(
color: scheme.primary.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(
ZTimelineConstants.pillBorderRadius,
),
border: Border.all(
color: scheme.primary.withValues(alpha: 0.6),
width: 2,
),
),
),
),
);
}
}