private void MainGrid_MouseUp(object sender, MouseButtonEventArgs e) { Mouse.Capture(null); MainGrid.MouseMove -= MainGrid_MouseMoveWhenDown; MainGrid.MouseMove += MainGrid_MouseMoveWhenUp; if (pmm != null) { pmm = null; // End of visual feed-back, align on grid, and update ViewModel // Round position to closest square on the grid List <PositionOrientation> topLeftList = new List <PositionOrientation>(); foreach (WordAndCanvas wac in m_Sel.WordAndCanvasList) { WordCanvas wc = wac.WordCanvas; int top = (int)Math.Floor((double)wc.GetValue(Canvas.TopProperty) / UnitSize + 0.5); int left = (int)Math.Floor((double)wc.GetValue(Canvas.LeftProperty) / UnitSize + 0.5); topLeftList.Add(new PositionOrientation(top, left, wac.WordPosition.IsVertical)); } // Do not accept Illegal placements, adjust to only valid placements AdjustToSuitableLocationInLayout(m_FixedLayout, m_Sel.WordAndCanvasList, topLeftList, true); // Move to final, rounded position viewModel.UpdateWordPositionLocation(m_Sel.WordAndCanvasList, topLeftList, true); // Update WordPosition with new location MoveWordAndCanvasList(m_Sel.WordAndCanvasList); // Visual animation FinalRefreshAfterUpdate(); } }
// Initial drawing of for current model layout internal IList <WordAndCanvas> AddCanvasForWordPositionList(IEnumerable <WordPosition> wordPositionList) { List <WordAndCanvas> wordAndCanvasList = new List <WordAndCanvas>(); // Draw letters foreach (WordPosition wp in wordPositionList) { WordCanvas wc = new WordCanvas(wp); WordAndCanvas wac = new WordAndCanvas(wp, wc); DrawingCanvas.Children.Add(wc); m_WordAndCanvasList.Add(wac); wordAndCanvasList.Add(wac); } return(wordAndCanvasList); }
// Helper, this was originally in MainGrid_MouseDown handler, but when a right-click occurs, // it is assumed than it will select automatically a non-selected word, so code got promoted to its own function... private void UpdateSelectionAfterClick(MouseButtonEventArgs e) { if (DrawingCanvas.InputHitTest(e.GetPosition(DrawingCanvas)) is TextBlock hitTextBlock) { WordCanvas hitC = hitTextBlock.Parent as WordCanvas; WordAndCanvas hit = m_WordAndCanvasList.FirstOrDefault(wac => ReferenceEquals(wac.WordCanvas, hitC)); Debug.Assert(hit != null); //Debug.WriteLine("Hit " + hit.WordPosition.ToString()); // If Ctrl key is NOT pressed, clear previous selection // But if we click again in something already selected, do not clear selection! if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl)) { if (m_Sel.WordAndCanvasList != null && !m_Sel.WordAndCanvasList.Contains(hit)) { m_Sel.Clear(); } } // Add current word to selection m_Sel.Add(hit); // If Shift key is pressed, selection is extended to connected words if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { //viewModel.Layout.GetConnectedWordPositions(hitWP).ForEach(connected => AddWordPositionToSelection(connected)); foreach (WordPosition connected in viewModel.Layout.GetConnectedWordPositions(hit.WordPosition)) { m_Sel.Add(m_WordAndCanvasList.FirstOrDefault(wac => wac.WordPosition == connected)); } } // Remove and add again elements to move so they're displayed above non-moved elements //foreach (WordCanvas wc in m_Sel.WordPositionList.Select(wp => Map.GetWordCanvasFromWordPosition(wp))) foreach (WordCanvas wc in m_Sel.WordAndCanvasList.Select(wac => wac.WordCanvas)) { DrawingCanvas.Children.Remove(wc); DrawingCanvas.Children.Add(wc); wc.SetColor(Brushes.White, Brushes.DarkBlue); } } }
// Separate from MainGrid_MouseDown to reduce complexity private Action <Point> GetMouseDownMoveAction() { // Need a layout without moved word to validate placement m_FixedLayout = viewModel.GetLayoutExcludingWordPositionList(m_Sel.WordAndCanvasList.Select(wac => wac.WordPosition)); // Reverse-transform mouse Grid coordinates into DrawingCanvas coordinates Matrix m = MainMatrixTransform.Matrix; m.Invert(); // To convert from screen transformed coordinates into ideal grid // coordinates starting at (0,0) with a square side of UnitSize List <Vector> clickOffsetList = new List <Vector>(m_Sel.WordAndCanvasList.Count); clickOffsetList.AddRange(m_Sel.WordAndCanvasList .Select(wac => new Point((double)wac.WordCanvas.GetValue(Canvas.LeftProperty), (double)wac.WordCanvas.GetValue(Canvas.TopProperty))) .Select(canvasTopLeft => canvasTopLeft - m.Transform(previousMousePosition))); // When moving, point is current mouse in ideal grid coordinates return(point => { // Just move selected WordCanvas for (int i = 0; i < m_Sel.WordAndCanvasList.Count; i++) { double preciseTop = point.Y + clickOffsetList[i].Y; double preciseLeft = point.X + clickOffsetList[i].X; WordCanvas wc = m_Sel.WordAndCanvasList[i].WordCanvas; wc.SetValue(Canvas.TopProperty, preciseTop); wc.SetValue(Canvas.LeftProperty, preciseLeft); // Round position to closest square on the grid int top = (int)Math.Floor(preciseTop / UnitSize + 0.5); int left = (int)Math.Floor(preciseLeft / UnitSize + 0.5); PlaceWordStatus status = EditorViewModel.CanPlaceWordAtPositionInLayout(m_FixedLayout, m_Sel.WordAndCanvasList[i].WordPosition, new PositionOrientation(top, left, m_Sel.WordAndCanvasList[i].WordPosition.IsVertical)); RecolorizeWord(m_Sel.WordAndCanvasList[i], status); } }); }
internal void MoveWordAndCanvasList(IList <WordAndCanvas> wordAndCanvasList) { if (wordAndCanvasList == null) { throw new ArgumentNullException(nameof(wordAndCanvasList)); } if (wordAndCanvasList.Count == 0) { throw new ArgumentException("Zero words in list", nameof(wordAndCanvasList)); } // If bounding rectangle is updated, need to redraw background grid BoundingRectangle r = viewModel.Layout.Bounds; if (!r.Equals(gridBounding)) { UpdateBackgroundGrid(); } // Compute distance moved on 1st element to choose animation speed (duration) WordPosition wp1 = wordAndCanvasList.First().WordPosition; WordCanvas wc1 = wordAndCanvasList.First().WordCanvas; double deltaX = (double)wc1.GetValue(Canvas.LeftProperty) - wp1.StartColumn * UnitSize; double deltaY = (double)wc1.GetValue(Canvas.TopProperty) - wp1.StartRow * UnitSize; double distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY); // If distance is null, for instance after a selection click, we're done if (distance <= 0.0001) { return; } // Group animations in a storyboard to simplify premature ending var sb = new Storyboard(); var duration = new Duration(TimeSpan.FromSeconds(distance >= UnitSize ? 0.35 : 0.1)); finalMoveWordAnimationData = new List <(Canvas, DependencyProperty, double)>(); foreach (WordAndCanvas wac in wordAndCanvasList) { WordCanvas wc = wac.WordCanvas; double finalLeftValue = wac.WordPosition.StartColumn * UnitSize; DoubleAnimation daLeft = new DoubleAnimation { Duration = duration, From = (double)wc.GetValue(Canvas.LeftProperty), To = finalLeftValue }; Storyboard.SetTarget(daLeft, wc); Storyboard.SetTargetProperty(daLeft, new PropertyPath("Left")); sb.Children.Add(daLeft); finalMoveWordAnimationData.Add((wc, Canvas.LeftProperty, finalLeftValue)); double finalTopValue = wac.WordPosition.StartRow * UnitSize; DoubleAnimation daTop = new DoubleAnimation { Duration = duration, From = (double)wc.GetValue(Canvas.TopProperty), To = finalTopValue }; Storyboard.SetTarget(daTop, wc); Storyboard.SetTargetProperty(daTop, new PropertyPath("Top")); sb.Children.Add(daTop); finalMoveWordAnimationData.Add((wc, Canvas.TopProperty, finalTopValue)); } IsMoveWordAnimationInProgress = true; sb.Completed += Sb_Completed; sb.Begin(); }