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:
@@ -1169,59 +1169,88 @@ fn events_overlap(event1: &VEvent, event2: &VEvent) -> bool {
|
|||||||
|
|
||||||
// Calculate layout columns for overlapping events
|
// Calculate layout columns for overlapping events
|
||||||
fn calculate_event_layout(events: &[VEvent], date: NaiveDate) -> Vec<(usize, usize)> {
|
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()
|
let mut day_events: Vec<_> = events.iter()
|
||||||
.filter(|event| {
|
.enumerate()
|
||||||
|
.filter_map(|(idx, event)| {
|
||||||
let (_, _, _) = calculate_event_position(event, date);
|
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 local_start = event.dtstart.with_timezone(&Local);
|
||||||
let event_date = local_start.date_naive();
|
let event_date = local_start.date_naive();
|
||||||
event_date == date ||
|
if event_date == date ||
|
||||||
(event_date == date - chrono::Duration::days(1) && local_start.hour() >= 20)
|
(event_date == date - chrono::Duration::days(1) && local_start.hour() >= 20) {
|
||||||
|
Some((idx, event))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by start time
|
// 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)
|
// For each event, find all events it overlaps with
|
||||||
let mut layout = Vec::with_capacity(day_events.len());
|
let mut event_columns = vec![(0, 1); events.len()]; // (column_idx, total_columns)
|
||||||
let mut columns: Vec<Vec<&VEvent>> = Vec::new();
|
|
||||||
|
|
||||||
for event in &day_events {
|
for i in 0..day_events.len() {
|
||||||
// Find the first column where this event doesn't overlap with any existing event
|
let (orig_idx_i, event_i) = day_events[i];
|
||||||
let mut placed = false;
|
|
||||||
for (col_idx, column) in columns.iter_mut().enumerate() {
|
// Find all events that overlap with this event
|
||||||
if !column.iter().any(|existing_event| events_overlap(event, existing_event)) {
|
let mut overlapping_events = vec![i];
|
||||||
column.push(event);
|
for j in 0..day_events.len() {
|
||||||
layout.push((col_idx, 0)); // total_columns will be set later
|
if i != j {
|
||||||
placed = true;
|
let (_, event_j) = day_events[j];
|
||||||
break;
|
if events_overlap(event_i, event_j) {
|
||||||
|
overlapping_events.push(j);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !placed {
|
// If this event doesn't overlap with anything, it gets full width
|
||||||
// Create new column
|
if overlapping_events.len() == 1 {
|
||||||
columns.push(vec![event]);
|
event_columns[orig_idx_i] = (0, 1);
|
||||||
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]);
|
|
||||||
} else {
|
} 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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user