Fix overlapping events to only split columns for overlapping event groups

Implemented clustering algorithm in calculate_event_layout that:
- Only creates column splits for events that actually overlap
- Non-overlapping events maintain full width display
- Uses greedy column assignment for overlapping groups
- Preserves proper column indices for each event

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-09-02 11:20:37 -04:00
parent 0899a84b42
commit 3662f117f5

View File

@@ -1169,59 +1169,88 @@ fn events_overlap(event1: &VEvent, event2: &VEvent) -> bool {
// Calculate layout columns for overlapping events
fn calculate_event_layout(events: &[VEvent], date: NaiveDate) -> Vec<(usize, usize)> {
// Filter events that should appear on this date and sort by start time
// Filter and sort events that should appear on this date
let mut day_events: Vec<_> = events.iter()
.filter(|event| {
.enumerate()
.filter_map(|(idx, event)| {
let (_, _, _) = calculate_event_position(event, date);
// Only include events that would be positioned (non-zero dimensions or all-day)
let local_start = event.dtstart.with_timezone(&Local);
let event_date = local_start.date_naive();
event_date == date ||
(event_date == date - chrono::Duration::days(1) && local_start.hour() >= 20)
if event_date == date ||
(event_date == date - chrono::Duration::days(1) && local_start.hour() >= 20) {
Some((idx, event))
} else {
None
}
})
.collect();
// Sort by start time
day_events.sort_by_key(|event| event.dtstart.with_timezone(&Local).naive_local());
day_events.sort_by_key(|(_, event)| event.dtstart.with_timezone(&Local).naive_local());
// Calculate layout: (column_index, total_columns)
let mut layout = Vec::with_capacity(day_events.len());
let mut columns: Vec<Vec<&VEvent>> = Vec::new();
// For each event, find all events it overlaps with
let mut event_columns = vec![(0, 1); events.len()]; // (column_idx, total_columns)
for event in &day_events {
// Find the first column where this event doesn't overlap with any existing event
let mut placed = false;
for (col_idx, column) in columns.iter_mut().enumerate() {
if !column.iter().any(|existing_event| events_overlap(event, existing_event)) {
column.push(event);
layout.push((col_idx, 0)); // total_columns will be set later
placed = true;
break;
for i in 0..day_events.len() {
let (orig_idx_i, event_i) = day_events[i];
// Find all events that overlap with this event
let mut overlapping_events = vec![i];
for j in 0..day_events.len() {
if i != j {
let (_, event_j) = day_events[j];
if events_overlap(event_i, event_j) {
overlapping_events.push(j);
}
}
}
if !placed {
// Create new column
columns.push(vec![event]);
layout.push((columns.len() - 1, 0)); // total_columns will be set later
}
}
// Update total_columns for all events
let total_columns = columns.len();
for (_, total_cols) in layout.iter_mut() {
*total_cols = total_columns;
}
// Create result mapping original events to their layout
let mut result = Vec::with_capacity(events.len());
for event in events {
if let Some(pos) = day_events.iter().position(|e| e.uid == event.uid) {
result.push(layout[pos]);
// If this event doesn't overlap with anything, it gets full width
if overlapping_events.len() == 1 {
event_columns[orig_idx_i] = (0, 1);
} else {
result.push((0, 1)); // Default: single column
// This event overlaps - we need to calculate column layout
// Sort the overlapping group by start time
overlapping_events.sort_by_key(|&idx| day_events[idx].1.dtstart.with_timezone(&Local).naive_local());
// Assign columns using a greedy algorithm
let mut columns: Vec<Vec<usize>> = Vec::new();
for &event_idx in &overlapping_events {
let (orig_idx, event) = day_events[event_idx];
// Find the first column where this event doesn't overlap with existing events
let mut placed = false;
for (col_idx, column) in columns.iter_mut().enumerate() {
let can_place = column.iter().all(|&existing_idx| {
let (_, existing_event) = day_events[existing_idx];
!events_overlap(event, existing_event)
});
if can_place {
column.push(event_idx);
event_columns[orig_idx] = (col_idx, columns.len());
placed = true;
break;
}
}
if !placed {
// Create new column
columns.push(vec![event_idx]);
event_columns[orig_idx] = (columns.len() - 1, columns.len());
}
}
// Update total_columns for all events in this overlapping group
let total_columns = columns.len();
for &event_idx in &overlapping_events {
let (orig_idx, _) = day_events[event_idx];
event_columns[orig_idx].1 = total_columns;
}
}
}
result
event_columns
}