/// <summary> /// Returns how many days into an event the specified label starts /// (if it's not the first label of the event). /// </summary> private int GetLabelDayOffset(DraggableView label) { if (label == null) { return 0; } var ev = label.BindingContext as Event; if (ev == null) { return 0; } var labelDate = GetLabelStartDate(label); if (!labelDate.HasValue) { return 0; } return (labelDate.Value - ev.Start.Date).Days; }
/// <summary> /// Get the start date for a specified label (may be later than the start date of the event) /// </summary> /// <param name="label">Label to get start date for</param> /// <returns>Start date, or null if not found</returns> private DateTime? GetLabelStartDate(DraggableView label) { if (label == null) { return null; } var column = AutoStackGrid.GetColumn(label); var row = Grid.GetRow(label.ParentView); var calendarCell = _grid.Children.OfType<CalendarCellView>() .FirstOrDefault(cell => Grid.GetRow(cell) == row && Grid.GetColumn(cell) == column); return calendarCell?.BindingContext as DateTime?; }
/// <summary> /// Takes the center point from a drag event and calculates the point to use /// for hit testing (e.g., we want to use the center of the first day of the /// label, not the center of the whole label) relative to the calendar grid. /// </summary> /// <param name="center">Center point of the dragged label, relative to its immediate parent</param> /// <param name="label">Label being dragged</param> /// <returns>Calculated destination point</returns> private Point GetDragDestinationPoint(Point center, DraggableView label) { var newCenterPoint = center; var colSpan = AutoStackGrid.GetColumnSpan(label); newCenterPoint = label.ParentView.TranslatePointToAncestor(newCenterPoint, _grid); // Adjust point for ColumnSpan (i.e., it's the starting day we care about) // if (colSpan > 1) { var labelWidth = label.Bounds.Width; var dayWidth = labelWidth / colSpan; var firstDayCenterX = newCenterPoint.X - (colSpan - 1) * dayWidth / 2d; newCenterPoint.X = firstDayCenterX; } return newCenterPoint; }
/// <summary> /// Remove resize mode views /// </summary> /// <param name="overlay">The resize mode overlay that triggered this, so we can remove it</param> private void EndResizeMode(View overlay) { _grid.Children.Remove(overlay); _grid.Children.Remove(_rightResizeHandle); _grid.Children.Remove(_rightResizeHandleDragger); _grid.Children.Remove(_leftResizeHandle); _grid.Children.Remove(_leftResizeHandleDragger); ((TappableView)overlay).Tapped -= HandleOverlayTapped; _rightResizeHandleDragger.Dragging -= HandleResizeDragging; _rightResizeHandleDragger.Dragged -= HandleResizeDragged; _rightResizeHandleDragger.DragCanceled -= HandleResizeCanceled; _leftResizeHandleDragger.Dragging -= HandleResizeDragging; _leftResizeHandleDragger.Dragged -= HandleResizeDragged; _leftResizeHandleDragger.DragCanceled -= HandleResizeCanceled; // Reset opacity foreach (var label in _labels) { label.Opacity = 1.0; } UnhighlightCells(); _resizingEvent = null; _rightResizeHandle = null; _rightResizeHandleDragger = null; _leftResizeHandle = null; _leftResizeHandleDragger = null; _resizeRange = null; }
/// <summary> /// Creates an invisible element directly on top of the provided drag handle visual /// which will actually process drag gestures. /// </summary> /// <remarks> /// The elements are set up as siblings with matching properties because a parental /// relationship would cause moving the visual to break the drag. /// This element is invisible because we don't want anything to actually follow the user's /// finger... we just want the drag to be rendered in controlled increments. /// </remarks> /// <param name="handle">A resize handle visual for which to create a drag element</param> /// <returns>The drag element</returns> private DraggableView CreateResizeHandleDraggerForVisual(View handle) { var dragger = new DraggableView(); dragger.BackgroundColor = Color.Transparent; dragger.Dragging += HandleResizeDragging; dragger.Dragged += HandleResizeDragged; dragger.DragCanceled += HandleResizeCanceled; Grid.SetColumn(dragger, Grid.GetColumn(handle)); Grid.SetRow(dragger, Grid.GetRow(handle)); dragger.HeightRequest = handle.HeightRequest; dragger.WidthRequest = handle.WidthRequest; dragger.VerticalOptions = handle.VerticalOptions; dragger.HorizontalOptions = handle.HorizontalOptions; return dragger; }
/// <summary> /// Creates overlay and tracking handles, highlights cells, and dims other events. /// </summary> /// <param name="ev">Event to resize</param> private void BeginResizeMode(Event ev) { _resizingEvent = ev; _resizeRange = new TimeRange(ev.Start, ev.End); // Create overlay to capture taps for dismissing resize mode // var overlay = new TappableView(); Grid.SetRowSpan(overlay, _rows + 1); Grid.SetColumnSpan(overlay, _cols); overlay.BackgroundColor = Color.Transparent; overlay.Tapped += HandleOverlayTapped; _grid.Children.Add(overlay); // Dim everything but this event // foreach (var label in _labels.Where(l => l.BindingContext != _resizingEvent)) { label.Opacity = 0.5; } HighlightCells(_resizingEvent.StartDate, _resizingEvent.EndDate); var cells = GetCellsForRange(_resizingEvent.StartDate, _resizingEvent.EndDate); var firstCell = cells.First(); var lastCell = cells.Last(); _leftResizeHandle = CreateResizeHandleVisual(Grid.GetColumn(firstCell), Grid.GetRow(firstCell), false); _grid.Children.Add(_leftResizeHandle); _rightResizeHandle = CreateResizeHandleVisual(Grid.GetColumn(lastCell), Grid.GetRow(lastCell), true); _grid.Children.Add(_rightResizeHandle); _leftResizeHandleDragger = CreateResizeHandleDraggerForVisual(_leftResizeHandle); _grid.Children.Add(_leftResizeHandleDragger); _rightResizeHandleDragger = CreateResizeHandleDraggerForVisual(_rightResizeHandle); _grid.Children.Add(_rightResizeHandleDragger); }
/// <summary> /// Create an individual label corresponding to an event (possibly only part of the /// event) and place it on the grid. /// </summary> private DraggableView PlaceEventLabel(Event ev, int row, int col, int colSpan, Thickness margin = default(Thickness)) { System.Diagnostics.Debug.WriteLine($"Placing {ev.Name} with a span of {colSpan}"); var view = new DraggableView { BackgroundColor = Color.Blue, VerticalOptions = LayoutOptions.FillAndExpand }; view.BindingContext = ev; var label = new Label(); label.SetBinding<Event>(Label.TextProperty, e => e.Name); label.VerticalOptions = LayoutOptions.Fill; label.YAlign = TextAlignment.Center; label.FontSize = Device.GetNamedSize(NamedSize.Small, view); label.HorizontalOptions = LayoutOptions.FillAndExpand; view.Content = label; AutoStackGrid.SetMargin(view, margin); var container = GetEventContainerForRow(row + 1); container.AddChild(view, col, colSpan); var tapGesture = new TapGestureRecognizer(); // First binding is to the "parent" context (i.e., ours, as opposed to the label's) //tapGesture.SetBinding(TapGestureRecognizer.CommandProperty, new Binding("EditEventCommand", BindingMode.Default, null, null, null, _vm)); //tapGesture.SetBinding(TapGestureRecognizer.CommandParameterProperty, new Binding(".")); // Activates resize mode instead of opening editor // tapGesture.Tapped += HandleEventTapped; view.GestureRecognizers.Add(tapGesture); view.Dragged += HandleDragged; view.DragStart += HandleDragStart; view.Dragging += HandleDragging; view.DragCanceled += HandleDragCanceled; _labels.Add(view); return view; }