event
This commit is contained in:
@@ -19,6 +19,11 @@ class ZTimelineConstants {
|
|||||||
vertical: 6.0,
|
vertical: 6.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Point events (hasEnd == false)
|
||||||
|
static const double pointEventCircleDiameter = 12.0;
|
||||||
|
static const double pointEventCircleTextGap = 6.0;
|
||||||
|
static const double pointEventTextGap = 8.0;
|
||||||
|
|
||||||
// Resize handles
|
// Resize handles
|
||||||
static const double resizeHandleWidth = 6.0;
|
static const double resizeHandleWidth = 6.0;
|
||||||
static const Duration minResizeDuration = Duration(hours: 1);
|
static const Duration minResizeDuration = Duration(hours: 1);
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../services/layout_coordinate_service.dart';
|
||||||
|
import '../services/time_scale_service.dart';
|
||||||
|
import '../state/timeline_viewport_notifier.dart';
|
||||||
|
import 'event_pill.dart';
|
||||||
|
import 'event_point.dart';
|
||||||
|
|
||||||
|
/// A preview overlay showing where an entry will land during drag/resize.
|
||||||
|
///
|
||||||
|
/// Renders the actual [EventPill] or [EventPoint] at full opacity so the
|
||||||
|
/// preview looks identical to the real item.
|
||||||
|
class DragPreview extends StatelessWidget {
|
||||||
|
const DragPreview({
|
||||||
|
required this.targetStart,
|
||||||
|
required this.targetEnd,
|
||||||
|
required this.targetLane,
|
||||||
|
required this.viewport,
|
||||||
|
required this.contentWidth,
|
||||||
|
required this.laneHeight,
|
||||||
|
required this.color,
|
||||||
|
required this.label,
|
||||||
|
this.hasEnd = true,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime targetStart;
|
||||||
|
final DateTime targetEnd;
|
||||||
|
final int targetLane;
|
||||||
|
final TimelineViewportNotifier viewport;
|
||||||
|
final double contentWidth;
|
||||||
|
final double laneHeight;
|
||||||
|
final Color color;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// Whether this is a range event. When false, renders [EventPoint].
|
||||||
|
final bool hasEnd;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final startX = TimeScaleService.mapTimeToPosition(
|
||||||
|
targetStart,
|
||||||
|
viewport.start,
|
||||||
|
viewport.end,
|
||||||
|
);
|
||||||
|
final top = LayoutCoordinateService.laneToY(
|
||||||
|
lane: targetLane,
|
||||||
|
laneHeight: laneHeight,
|
||||||
|
);
|
||||||
|
final left = LayoutCoordinateService.normalizedToWidgetX(
|
||||||
|
normalizedX: startX,
|
||||||
|
contentWidth: contentWidth,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasEnd) {
|
||||||
|
return Positioned(
|
||||||
|
left: left.clamp(0.0, double.infinity),
|
||||||
|
top: top,
|
||||||
|
height: laneHeight,
|
||||||
|
child: IgnorePointer(child: EventPoint(color: color, label: label)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final endX = TimeScaleService.mapTimeToPosition(
|
||||||
|
targetEnd,
|
||||||
|
viewport.start,
|
||||||
|
viewport.end,
|
||||||
|
);
|
||||||
|
final width = LayoutCoordinateService.calculateItemWidth(
|
||||||
|
normalizedWidth: endX - startX,
|
||||||
|
contentWidth: contentWidth,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
left: left.clamp(0.0, double.infinity),
|
||||||
|
width: width.clamp(0.0, double.infinity),
|
||||||
|
top: top,
|
||||||
|
height: laneHeight,
|
||||||
|
child: IgnorePointer(child: EventPill(color: color, label: label)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../constants.dart';
|
||||||
|
|
||||||
|
/// A point-event widget: small circle with a text label to its right.
|
||||||
|
///
|
||||||
|
/// Used for entries with [TimelineEntry.hasEnd] == false.
|
||||||
|
class EventPoint extends StatelessWidget {
|
||||||
|
const EventPoint({
|
||||||
|
required this.color,
|
||||||
|
required this.label,
|
||||||
|
this.maxTextWidth,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Color color;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// Maximum width for the label text. When provided, text truncates with
|
||||||
|
/// ellipsis. When null the text sizes naturally.
|
||||||
|
final double? maxTextWidth;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final onColor =
|
||||||
|
ThemeData.estimateBrightnessForColor(color) == Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black87;
|
||||||
|
|
||||||
|
final textWidget = Text(
|
||||||
|
label,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||||
|
color: onColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final showText = maxTextWidth == null || maxTextWidth! > 0;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: ZTimelineConstants.pointEventCircleDiameter,
|
||||||
|
height: ZTimelineConstants.pointEventCircleDiameter,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showText) ...[
|
||||||
|
const SizedBox(width: ZTimelineConstants.pointEventCircleTextGap),
|
||||||
|
if (maxTextWidth != null)
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxTextWidth!),
|
||||||
|
child: textWidget,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
textWidget,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../constants.dart';
|
|
||||||
import '../services/layout_coordinate_service.dart';
|
|
||||||
import '../services/time_scale_service.dart';
|
|
||||||
import '../state/timeline_viewport_notifier.dart';
|
|
||||||
import 'event_pill.dart';
|
|
||||||
|
|
||||||
/// A semi-transparent ghost overlay showing where an entry will land.
|
|
||||||
///
|
|
||||||
/// Displayed during drag and resize operations to give visual feedback about
|
|
||||||
/// the target position.
|
|
||||||
class GhostOverlay extends StatelessWidget {
|
|
||||||
const GhostOverlay({
|
|
||||||
required this.targetStart,
|
|
||||||
required this.targetEnd,
|
|
||||||
required this.targetLane,
|
|
||||||
required this.viewport,
|
|
||||||
required this.contentWidth,
|
|
||||||
required this.laneHeight,
|
|
||||||
this.title,
|
|
||||||
this.color,
|
|
||||||
this.label,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final DateTime targetStart;
|
|
||||||
final DateTime targetEnd;
|
|
||||||
final int targetLane;
|
|
||||||
final TimelineViewportNotifier viewport;
|
|
||||||
final double contentWidth;
|
|
||||||
final double laneHeight;
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
/// When provided, renders an [EventPill] with slight transparency instead of
|
|
||||||
/// the generic primary-colored box. Used for resize ghosts.
|
|
||||||
final Color? color;
|
|
||||||
|
|
||||||
/// Label for the pill when [color] is provided.
|
|
||||||
final String? label;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final startX = TimeScaleService.mapTimeToPosition(
|
|
||||||
targetStart,
|
|
||||||
viewport.start,
|
|
||||||
viewport.end,
|
|
||||||
);
|
|
||||||
final endX = TimeScaleService.mapTimeToPosition(
|
|
||||||
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: targetLane,
|
|
||||||
laneHeight: laneHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Positioned(
|
|
||||||
left: left.clamp(0.0, double.infinity),
|
|
||||||
width: width.clamp(0.0, double.infinity),
|
|
||||||
top: top,
|
|
||||||
height: laneHeight,
|
|
||||||
child: IgnorePointer(
|
|
||||||
child: color != null
|
|
||||||
? Opacity(
|
|
||||||
opacity: 0.5,
|
|
||||||
child: EventPill(color: color!, label: label ?? ''),
|
|
||||||
)
|
|
||||||
: _buildGenericGhost(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGenericGhost(BuildContext context) {
|
|
||||||
final scheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: ZTimelineConstants.pillPadding,
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: title != null
|
|
||||||
? Text(
|
|
||||||
title!,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
||||||
color: scheme.primary.withValues(alpha: 0.8),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ import '../services/time_scale_service.dart';
|
|||||||
import '../services/timeline_group_registry.dart';
|
import '../services/timeline_group_registry.dart';
|
||||||
import '../state/timeline_viewport_notifier.dart';
|
import '../state/timeline_viewport_notifier.dart';
|
||||||
import 'event_pill.dart';
|
import 'event_pill.dart';
|
||||||
|
import 'event_point.dart';
|
||||||
import 'timeline_scope.dart';
|
import 'timeline_scope.dart';
|
||||||
import 'timeline_view.dart';
|
import 'timeline_view.dart';
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ class InteractiveEventPill extends StatefulWidget {
|
|||||||
this.onEntryResized,
|
this.onEntryResized,
|
||||||
this.onEntryMoved,
|
this.onEntryMoved,
|
||||||
this.groupRegistry,
|
this.groupRegistry,
|
||||||
|
this.nextEntryStartX,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,11 +48,16 @@ class InteractiveEventPill extends StatefulWidget {
|
|||||||
final OnEntryMoved? onEntryMoved;
|
final OnEntryMoved? onEntryMoved;
|
||||||
final TimelineGroupRegistry? groupRegistry;
|
final TimelineGroupRegistry? groupRegistry;
|
||||||
|
|
||||||
|
/// Normalized startX of the next entry in the same lane, used to compute
|
||||||
|
/// available width for point events.
|
||||||
|
final double? nextEntryStartX;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InteractiveEventPill> createState() => _InteractiveEventPillState();
|
State<InteractiveEventPill> createState() => _InteractiveEventPillState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
||||||
|
final _pointInteractionKey = GlobalKey();
|
||||||
_InteractionMode? _mode;
|
_InteractionMode? _mode;
|
||||||
double _cumulativeDx = 0;
|
double _cumulativeDx = 0;
|
||||||
|
|
||||||
@@ -66,6 +73,8 @@ class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_InteractionMode _determineMode(Offset localPosition) {
|
_InteractionMode _determineMode(Offset localPosition) {
|
||||||
|
if (!widget.entry.entry.hasEnd) return _InteractionMode.drag;
|
||||||
|
|
||||||
final pillWidth = _pillWidth;
|
final pillWidth = _pillWidth;
|
||||||
const handleWidth = ZTimelineConstants.resizeHandleWidth;
|
const handleWidth = ZTimelineConstants.resizeHandleWidth;
|
||||||
|
|
||||||
@@ -273,10 +282,9 @@ class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final pill = EventPill(
|
final isPoint = !widget.entry.entry.hasEnd;
|
||||||
color: widget.colorBuilder(widget.entry.entry),
|
final color = widget.colorBuilder(widget.entry.entry);
|
||||||
label: widget.labelBuilder(widget.entry.entry),
|
final label = widget.labelBuilder(widget.entry.entry);
|
||||||
);
|
|
||||||
|
|
||||||
final top = LayoutCoordinateService.laneToY(
|
final top = LayoutCoordinateService.laneToY(
|
||||||
lane: widget.entry.entry.lane,
|
lane: widget.entry.entry.lane,
|
||||||
@@ -286,6 +294,68 @@ class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
|||||||
normalizedX: widget.entry.startX,
|
normalizedX: widget.entry.startX,
|
||||||
contentWidth: widget.contentWidth,
|
contentWidth: widget.contentWidth,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isPoint) {
|
||||||
|
// Available width from this entry to the next in the same lane
|
||||||
|
// (or to the right edge of the content area).
|
||||||
|
final availableWidth = widget.nextEntryStartX != null
|
||||||
|
? (widget.nextEntryStartX! - widget.entry.startX) *
|
||||||
|
widget.contentWidth
|
||||||
|
: (1.0 - widget.entry.startX) * widget.contentWidth;
|
||||||
|
|
||||||
|
final maxTextWidth = availableWidth -
|
||||||
|
ZTimelineConstants.pointEventCircleDiameter -
|
||||||
|
ZTimelineConstants.pointEventCircleTextGap -
|
||||||
|
ZTimelineConstants.pointEventTextGap;
|
||||||
|
|
||||||
|
final pointWidget = EventPoint(
|
||||||
|
color: color,
|
||||||
|
label: label,
|
||||||
|
maxTextWidth: maxTextWidth > 0 ? maxTextWidth : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!widget.enableDrag) {
|
||||||
|
return Positioned(
|
||||||
|
top: top,
|
||||||
|
left: left.clamp(0.0, double.infinity),
|
||||||
|
width: availableWidth.clamp(
|
||||||
|
ZTimelineConstants.pointEventCircleDiameter,
|
||||||
|
double.infinity,
|
||||||
|
),
|
||||||
|
height: widget.laneHeight,
|
||||||
|
child: Align(alignment: Alignment.centerLeft, child: pointWidget),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final scope = ZTimelineScope.of(context);
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
top: top,
|
||||||
|
left: left.clamp(0.0, double.infinity),
|
||||||
|
width: availableWidth.clamp(
|
||||||
|
ZTimelineConstants.pointEventCircleDiameter,
|
||||||
|
double.infinity,
|
||||||
|
),
|
||||||
|
height: widget.laneHeight,
|
||||||
|
child: ListenableBuilder(
|
||||||
|
listenable: scope.interaction,
|
||||||
|
builder: (context, child) {
|
||||||
|
final entryId = widget.entry.entry.id;
|
||||||
|
final isDragging = scope.interaction.isDraggingEntry &&
|
||||||
|
scope.interaction.dragState?.entryId == entryId;
|
||||||
|
|
||||||
|
return Opacity(
|
||||||
|
opacity: isDragging ? 0.3 : 1.0,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _buildInteractivePoint(pointWidget),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range event (hasEnd == true)
|
||||||
|
final pill = EventPill(color: color, label: label);
|
||||||
final width = _pillWidth;
|
final width = _pillWidth;
|
||||||
|
|
||||||
if (!widget.enableDrag) {
|
if (!widget.enableDrag) {
|
||||||
@@ -299,7 +369,8 @@ class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final scope = ZTimelineScope.of(context);
|
final scope = ZTimelineScope.of(context);
|
||||||
final hasResizeHandles = widget.onEntryResized != null && width >= 16;
|
final hasResizeHandles =
|
||||||
|
widget.entry.entry.hasEnd && widget.onEntryResized != null && width >= 16;
|
||||||
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
top: top,
|
top: top,
|
||||||
@@ -341,6 +412,39 @@ class _InteractiveEventPillState extends State<InteractiveEventPill> {
|
|||||||
scope.interaction.clearHoveredEntry();
|
scope.interaction.clearHoveredEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onPointHoverEnter() {
|
||||||
|
final box =
|
||||||
|
_pointInteractionKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
if (box == null || !box.attached) return;
|
||||||
|
final topLeft = box.localToGlobal(Offset.zero);
|
||||||
|
final rect = topLeft & box.size;
|
||||||
|
final scope = ZTimelineScope.of(context);
|
||||||
|
scope.interaction.setHoveredEntry(widget.entry.entry.id, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInteractivePoint(Widget pointWidget) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: MouseRegion(
|
||||||
|
key: _pointInteractionKey,
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
opaque: false,
|
||||||
|
hitTestBehavior: HitTestBehavior.deferToChild,
|
||||||
|
onEnter: (_) => _onPointHoverEnter(),
|
||||||
|
onExit: (_) => _onHoverExit(),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onPanDown: _onPanDown,
|
||||||
|
onPanStart: _onPanStart,
|
||||||
|
onPanUpdate: _onPanUpdate,
|
||||||
|
onPanEnd: _onPanEnd,
|
||||||
|
onPanCancel: _onPanCancel,
|
||||||
|
child: pointWidget,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildInteractivePill(Widget pill, bool hasResizeHandles) {
|
Widget _buildInteractivePill(Widget pill, bool hasResizeHandles) {
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import '../models/timeline_entry.dart';
|
|||||||
import '../models/timeline_group.dart';
|
import '../models/timeline_group.dart';
|
||||||
import '../services/timeline_projection_service.dart';
|
import '../services/timeline_projection_service.dart';
|
||||||
import '../state/timeline_viewport_notifier.dart';
|
import '../state/timeline_viewport_notifier.dart';
|
||||||
import 'ghost_overlay.dart';
|
import 'drag_preview.dart';
|
||||||
import 'interactive_event_pill.dart';
|
import 'interactive_event_pill.dart';
|
||||||
import 'timeline_scope.dart';
|
import 'timeline_scope.dart';
|
||||||
|
|
||||||
@@ -285,6 +285,24 @@ class _GroupLanesState extends State<_GroupLanes> {
|
|||||||
return effective;
|
return effective;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds the startX of the next entry in the same lane, or null if none.
|
||||||
|
double? _findNextEntryStartX(List<ProjectedEntry> entries, int index) {
|
||||||
|
final current = entries[index];
|
||||||
|
final lane = current.entry.lane;
|
||||||
|
final startX = current.startX;
|
||||||
|
double? closest;
|
||||||
|
for (var j = 0; j < entries.length; j++) {
|
||||||
|
if (j == index) continue;
|
||||||
|
final other = entries[j];
|
||||||
|
if (other.entry.lane != lane) continue;
|
||||||
|
if (other.startX <= startX) continue;
|
||||||
|
if (closest == null || other.startX < closest) {
|
||||||
|
closest = other.startX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context, int effectiveLanesCount) {
|
Widget _buildContent(BuildContext context, int effectiveLanesCount) {
|
||||||
final totalHeight =
|
final totalHeight =
|
||||||
effectiveLanesCount * widget.laneHeight +
|
effectiveLanesCount * widget.laneHeight +
|
||||||
@@ -300,9 +318,9 @@ class _GroupLanesState extends State<_GroupLanes> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Event pills
|
// Event pills
|
||||||
for (final e in widget.entries)
|
for (var i = 0; i < widget.entries.length; i++)
|
||||||
InteractiveEventPill(
|
InteractiveEventPill(
|
||||||
entry: e,
|
entry: widget.entries[i],
|
||||||
laneHeight: widget.laneHeight,
|
laneHeight: widget.laneHeight,
|
||||||
labelBuilder: widget.labelBuilder,
|
labelBuilder: widget.labelBuilder,
|
||||||
colorBuilder: widget.colorBuilder,
|
colorBuilder: widget.colorBuilder,
|
||||||
@@ -313,6 +331,10 @@ class _GroupLanesState extends State<_GroupLanes> {
|
|||||||
onEntryResized: widget.onEntryResized,
|
onEntryResized: widget.onEntryResized,
|
||||||
onEntryMoved: widget.onEntryMoved,
|
onEntryMoved: widget.onEntryMoved,
|
||||||
groupRegistry: scope?.groupRegistry,
|
groupRegistry: scope?.groupRegistry,
|
||||||
|
nextEntryStartX: _findNextEntryStartX(
|
||||||
|
widget.entries,
|
||||||
|
i,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Ghost overlay for drag operations
|
// Ghost overlay for drag operations
|
||||||
@@ -326,7 +348,7 @@ class _GroupLanesState extends State<_GroupLanes> {
|
|||||||
// Show ghost for resize (entry stays in its own group)
|
// Show ghost for resize (entry stays in its own group)
|
||||||
if (resizeState != null &&
|
if (resizeState != null &&
|
||||||
resizeState.originalEntry.groupId == widget.group.id) {
|
resizeState.originalEntry.groupId == widget.group.id) {
|
||||||
return GhostOverlay(
|
return DragPreview(
|
||||||
targetStart: resizeState.targetStart,
|
targetStart: resizeState.targetStart,
|
||||||
targetEnd: resizeState.targetEnd,
|
targetEnd: resizeState.targetEnd,
|
||||||
targetLane: resizeState.targetLane,
|
targetLane: resizeState.targetLane,
|
||||||
@@ -335,19 +357,23 @@ class _GroupLanesState extends State<_GroupLanes> {
|
|||||||
laneHeight: widget.laneHeight,
|
laneHeight: widget.laneHeight,
|
||||||
color: widget.colorBuilder(resizeState.originalEntry),
|
color: widget.colorBuilder(resizeState.originalEntry),
|
||||||
label: widget.labelBuilder(resizeState.originalEntry),
|
label: widget.labelBuilder(resizeState.originalEntry),
|
||||||
|
hasEnd: resizeState.originalEntry.hasEnd,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show ghost for drag-move
|
// Show ghost for drag-move
|
||||||
if (dragState != null &&
|
if (dragState != null &&
|
||||||
dragState.targetGroupId == widget.group.id) {
|
dragState.targetGroupId == widget.group.id) {
|
||||||
return GhostOverlay(
|
return DragPreview(
|
||||||
targetStart: dragState.targetStart,
|
targetStart: dragState.targetStart,
|
||||||
targetEnd: dragState.targetEnd,
|
targetEnd: dragState.targetEnd,
|
||||||
targetLane: dragState.targetLane,
|
targetLane: dragState.targetLane,
|
||||||
viewport: widget.viewport,
|
viewport: widget.viewport,
|
||||||
contentWidth: widget.contentWidth,
|
contentWidth: widget.contentWidth,
|
||||||
laneHeight: widget.laneHeight,
|
laneHeight: widget.laneHeight,
|
||||||
|
color: widget.colorBuilder(dragState.originalEntry),
|
||||||
|
label: widget.labelBuilder(dragState.originalEntry),
|
||||||
|
hasEnd: dragState.originalEntry.hasEnd,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export 'src/state/timeline_viewport_notifier.dart';
|
|||||||
export 'src/widgets/breadcrumb_segment_chip.dart';
|
export 'src/widgets/breadcrumb_segment_chip.dart';
|
||||||
export 'src/widgets/entry_popover_overlay.dart';
|
export 'src/widgets/entry_popover_overlay.dart';
|
||||||
export 'src/widgets/event_pill.dart';
|
export 'src/widgets/event_pill.dart';
|
||||||
export 'src/widgets/ghost_overlay.dart';
|
export 'src/widgets/event_point.dart';
|
||||||
|
export 'src/widgets/drag_preview.dart';
|
||||||
export 'src/widgets/interactive_event_pill.dart';
|
export 'src/widgets/interactive_event_pill.dart';
|
||||||
export 'src/widgets/timeline_breadcrumb.dart';
|
export 'src/widgets/timeline_breadcrumb.dart';
|
||||||
export 'src/widgets/timeline_interactor.dart';
|
export 'src/widgets/timeline_interactor.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user