Calendar Interaction System
16 partial files handling pointer/touch, timing snap, drag modes (Move/Resize/Create/RangeSelect), hit testing (7 views), and interaction commitment with history integration
Table of Contents
Overview
16 partial files under Calendar/BeepCalendar.Interactions.*.cs handle all pointer/touch/mouse events. The system uses view-mode-specific hit testing, drag mode resolution driven by the hit-test result, event creation proposals during drag, and a commitment pipeline that integrates with the undo/redo history stack. The ResolveDragMode() method returns a CalendarInteractionMode enum that determines subsequent behavior. Four interaction events (InteractionStarted, InteractionUpdated, InteractionCompleted, InteractionCancelled) provide hooks for external observers.
Pointer Events (4 Files)
| File | Events | Responsibility |
|---|---|---|
| Interactions.Pointer.cs | MouseDown, MouseUp, MouseMove | Pointer dispatch, modifier key tracking (Ctrl for copy, Shift for range) |
| Interactions.Pointer.DownUp.cs | MouseDown + MouseUp | Click detection, drag threshold (minimum pixels before drag mode activates) |
| Interactions.Pointer.MoveCancel.cs | MouseMove + Cancel | Drag tracking with delta calculation, Escape key to cancel current interaction |
| Interactions.Pointer.Up.cs | MouseUp | Drag completion trigger, passes location + delta to commitment pipeline |
Drag Modes
ResolveDragMode(CalendarInteractionHitTestResult) determines the CalendarInteractionMode:
| Mode | Hit Target | Enabled Views | Behavior |
|---|---|---|---|
| MoveEvent | Event block body | Day, Week, WorkWeek, Timeline | Drag event to a new time slot. Original position cleared, new position shown as ghost |
| ResizeStart | Event block top edge | Day, Week, WorkWeek | Drag upward to move start time earlier. Bottom anchor fixed. |
| ResizeEnd | Event block bottom edge | Day, Week, WorkWeek | Drag downward to move end time later. Top anchor fixed. |
| SelectEvent | Event block in non-drag views | Month, Agenda, List | Click to select the event. Fires EventSelected. Double-click opens editor. |
| RangeSelect | Date cell | Day, Week, WorkWeek | Click-drag over time slots to select a time range |
| CreateEvent | Empty surface / date cell | Month, Agenda, List | Click-drag to define and create a new event at that time |
| SelectDate | Date cell (any view) | All views | Click to navigate to that date |
Hit Testing (8 Files, 7 Views)
private CalendarInteractionHitTestResult ResolveInteractionTarget(Point loc)
{
switch (_state.ViewMode)
{
case CalendarViewMode.Month: return ResolveMonthInteraction(loc);
case CalendarViewMode.Week:
case CalendarViewMode.WorkWeek: return ResolveWeekInteraction(loc);
case CalendarViewMode.Day: return ResolveDayInteraction(loc);
case CalendarViewMode.Agenda: return ResolveAgendaInteraction(loc);
case CalendarViewMode.Timeline: return ResolveTimelineInteraction(loc);
case CalendarViewMode.List: return ResolveListInteraction(loc);
default: return new CalendarInteractionHitTestResult
{ TargetKind = EmptySurface, RequestedMode = SelectDate };
}
}
8 specialized hit-testing files: HitTesting.cs (dispatcher above), HitTesting.Views.cs, HitTesting.MonthView.cs, HitTesting.WeekView.cs, HitTesting.DayView.cs, HitTesting.TimelineView.cs, HitTesting.TimedView.cs, HitTesting.Helpers.cs. Each resolves whether the click hit an event block (EventBlock), a date cell (DateCell), or empty space (EmptySurface). Returns a CalendarInteractionHitTestResult with TargetKind, RequestedMode, Location, and Date properties.
Timing & Snapping
InteractionSnapIntervalMinutes (default 15) causes event start/end times to snap to the nearest N-minute interval during drag operations. The snapping logic spans three files: Interactions.Timing.cs (base timing), Timing.Snap.cs (snap calculation), Timing.Snap.Location.cs (pixel-to-time conversion with snap). For ResizeStart/ResizeEnd modes, only the affected edge snaps; the opposite anchor remains fixed.
Creation Proposals
During drag-to-create operations, a visual "ghost" rectangle is rendered showing the proposed event at the snapped time interval. The ghost updates in real-time as the user drags, giving live visual feedback of the event that will be created on mouse-up. Two files: Interactions.Proposals.cs (proposal lifecycle management — create, update, clear) and Proposals.CreationRange.cs (calculates proposal start/end times from drag rectangle extents).
Commitment Pipeline (4 Files)
On mouse-up, CommitInteractionMutation(Point location, Point delta) dispatches:
- Commit.cs — Dispatch hub: routes to NewEvent, Copy, or ExistingEvent handler based on drag mode and modifier state
- Commit.NewEvent.cs — Creates event from proposal, routes through EventMutating → CRUD → EventMutated → History push
- Commit.Copy.cs — Clones the source event and creates the copy at the drop location (activated by Ctrl+drag of existing event)
- Commit.ExistingEvent.cs — Mutates an existing event's properties (start, end, date) in place. Routes through EventMutating/EventMutated with modification kind
Copy-Modifier (Ctrl+Drag)
IsCopyModifierDown() checks whether Ctrl or Ctrl+Shift is pressed. When true, dragging an existing event creates a copy at the drop location instead of moving the original. This enables quick event duplication workflows common in modern calendar applications.
CalendarViewStateHelper
Internal static helper in Calendar/Helpers/CalendarViewStateHelper.cs. Builds and returns a populated DayCellState data object:
internal static DayCellState BuildDayCellState(
DateTime cellDate,
DateTime currentMonthAnchor,
DateTime selectedDate,
DateTime? hoveredDate,
DateTime focusedDate,
bool isKeyboardFocusVisible,
int eventCount,
int maxEventsPerCell)
// Returns DayCellState with computed flags:
// IsCurrentMonth, IsToday, IsSelected, IsHovered,
// IsWeekend (Saturday/Sunday), IsFocused,
// EventCount, HasMoreEvents (eventCount > maxEventsPerCell)File Inventory
| Group | Files | Purpose |
|---|---|---|
| Core | Interactions.cs | Dispatcher, drag mode resolution, copy-modifier detection, interaction start/update/complete/cancel events |
| Hit Testing | HitTesting.cs + 7 sub-files | View-mode-specific hit test resolution returning CalendarInteractionHitTestResult |
| Pointer | Pointer.cs + 3 sub-files | Mouse down/up/move/cancel event handling with modifier key tracking |
| Timing | Timing.cs + 2 sub-files | Time snapping to configurable interval (default 15 min) during drag |
| Proposals | Proposals.cs + 1 sub-file | Visual ghost rectangle creation during drag-to-create |
| Commit | Commit.cs + 3 sub-files | Interaction completion: new event creation, copy-on-drag, existing event mutation |
Code Examples
// Interaction events
cal.InteractionStarted += (s, e) =>
Console.WriteLine($"Started: {e.Mode} at {e.Location}");
cal.InteractionUpdated += (s, e) =>
Console.WriteLine($"Updated: dragged to {e.Location}");
cal.InteractionCompleted += (s, e) =>
Console.WriteLine($"Completed: {e.Mode}");
// Snap interval
cal.InteractionSnapIntervalMinutes = 30; // Snap to :00 and :30
// Create event on drop
cal.CreateEventRequested += (s, e) =>
{
e.CreatedEvent = new CalendarEvent
{
Title = "New Event",
Start = e.RequestedStart,
End = e.RequestedStart.AddHours(1)
};
};