diff --git a/lib/src/content/date_events.dart b/lib/src/content/date_events.dart index 7e9dacf..1291e49 100644 --- a/lib/src/content/date_events.dart +++ b/lib/src/content/date_events.dart @@ -34,7 +34,12 @@ class DateEvents extends StatelessWidget { static const _defaultMinEventHeight = 16.0; static const _defaultEventSpacing = 1.0; static const _defaultStackedEventSpacing = 4.0; - static final Period minStackOverlap = Period(minutes: 15); + static final _defaultPartDayEventMinimumDeltaForStacking = + Period(minutes: 15); + @Deprecated('This is now configurable via ' + '[TimetableThemeData.partDayEventMinimumDeltaForStacking].') + static Period get minStackOverlap => + _defaultPartDayEventMinimumDeltaForStacking; final LocalDate date; final List events; @@ -54,6 +59,10 @@ class DateEvents extends StatelessWidget { timetableTheme?.partDayEventMinimumHeight ?? _defaultMinEventHeight, eventSpacing: timetableTheme?.partDayEventSpacing ?? _defaultEventSpacing, + enableStacking: timetableTheme?.enablePartDayEventStacking ?? true, + minimumDeltaForStacking: + timetableTheme?.partDayEventMinimumDeltaForStacking ?? + _defaultPartDayEventMinimumDeltaForStacking, stackedEventSpacing: timetableTheme?.partDayStackedEventSpacing ?? _defaultStackedEventSpacing, ), @@ -77,12 +86,16 @@ class _DayEventsLayoutDelegate @required this.minEventDuration, @required this.minEventHeight, @required this.eventSpacing, + @required this.enableStacking, + @required this.minimumDeltaForStacking, @required this.stackedEventSpacing, }) : assert(date != null), assert(events != null), assert(minEventDuration != null), assert(minEventHeight != null), assert(eventSpacing != null), + assert(enableStacking != null), + assert(minimumDeltaForStacking != null), assert(stackedEventSpacing != null); static const minWidth = 4.0; @@ -93,6 +106,8 @@ class _DayEventsLayoutDelegate final Period minEventDuration; final double minEventHeight; final double eventSpacing; + final bool enableStacking; + final Period minimumDeltaForStacking; final double stackedEventSpacing; @override @@ -184,7 +199,9 @@ class _DayEventsLayoutDelegate final other = column.last; // No space in current column - if (event.start < other.start + DateEvents.minStackOverlap) { + if (!enableStacking && event.start < _actualEnd(other, height) || + enableStacking && + event.start < other.start + minimumDeltaForStacking) { continue; } @@ -226,19 +243,22 @@ class _DayEventsLayoutDelegate continue; } - final cols = - (position.column + 1).rangeTo(columns.length - 1).where((column) { - return currentGroup - .where((e) => positions.eventPositions[e].column == column) + var columnSpan = 1; + for (var i = position.column + 1; i < columns.length; i++) { + final hasOverlapInColumn = currentGroup + .where((e) => positions.eventPositions[e].column == i) .where((e) => event.start < _actualEnd(e, height) && e.start < _actualEnd(event, height)) - .isEmpty; - }).toList(); - final maxColumnWithoutIntersections = cols.max() ?? position.column; + .isNotEmpty; + if (hasOverlapInColumn) { + break; + } + columnSpan++; + } positions.eventPositions[event] = position.copyWith( - columnSpan: maxColumnWithoutIntersections - position.column + 1, + columnSpan: columnSpan, ); } diff --git a/lib/src/theme.dart b/lib/src/theme.dart index fa71163..d0bd2cf 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -27,6 +27,8 @@ class TimetableThemeData { this.partDayEventMinimumDuration, this.partDayEventMinimumHeight, this.partDayEventSpacing, + this.enablePartDayEventStacking, + this.partDayEventMinimumDeltaForStacking, this.partDayStackedEventSpacing, }) : assert(allDayEventHeight == null || allDayEventHeight > 0), assert(minimumHourHeight == null || minimumHourHeight > 0), @@ -135,12 +137,34 @@ class TimetableThemeData { /// Horizontal space between two parallel events shown next to each other. final double partDayEventSpacing; + /// Controls whether overlapping events may be stacked on top of each other. + /// + /// If set to `true`, intersecting events may be stacked if their start values + /// differ by at least [partDayEventMinimumDeltaForStacking]. If set to + /// `false`, intersecting events will always be shown next to each other and + /// not overlap. + /// + /// Defaults to `true`. + final bool enablePartDayEventStacking; + + /// When the start values of two events differ by at least this value, they + /// may be stacked on top of each other. + /// + /// If the difference is less, they will be shown next to each other. + /// + /// Defaults to 15 min. + /// + /// See also: + /// - [enablePartDayEventStacking], which can disable the stacking behavior + /// completely. + final Period partDayEventMinimumDeltaForStacking; + /// Horizontal space between two parallel events stacked on top of each other. final double partDayStackedEventSpacing; @override int get hashCode { - return hashValues( + return hashList([ primaryColor, weekIndicatorDecoration, weekIndicatorTextStyle, @@ -160,8 +184,10 @@ class TimetableThemeData { partDayEventMinimumDuration, partDayEventMinimumHeight, partDayEventSpacing, + enablePartDayEventStacking, + partDayEventMinimumDeltaForStacking, partDayStackedEventSpacing, - ); + ]); } @override @@ -192,6 +218,9 @@ class TimetableThemeData { other.partDayEventMinimumDuration == partDayEventMinimumDuration && other.partDayEventMinimumHeight == partDayEventMinimumHeight && other.partDayEventSpacing == partDayEventSpacing && + other.enablePartDayEventStacking == enablePartDayEventStacking && + other.partDayEventMinimumDeltaForStacking == + partDayEventMinimumDeltaForStacking && other.partDayStackedEventSpacing == partDayStackedEventSpacing; } }