private void EditNoteModeTap(ScoreEditorHitTestResult hit, MouseEventArgs e) { var editor = _visualizer.Editor; if (hit.HitAnyNote) { var note = hit.Note; if (note == _lastMouseDownNote) { // If we hit any note, its behavior is like 'select' mode, except that this one doesn't accept // modifier keys. if (editor.HasSelectedBars) { editor.ClearSelectedBars(); } else { EditorToggleNoteSelection(note); } editor.Invalidate(); } } else if (hit.HitBarGridIntersection) { // Add a note EditorAddNote(hit); editor.Invalidate(); } else { ClearNoteAndBarSelection(); } }
private Note EditorAddNote(ScoreEditorHitTestResult hit) { var editor = _visualizer.Editor; var note = editor.AddNoteAt(hit.Bar, hit.Row, hit.Column); _visualizer.InformProjectModified(); return(note); }
private void SpecialNoteAreaOnMouseUp(ScoreEditorHitTestResult hit, MouseEventArgs e) { switch (e.Button) { case MouseButtons.Left: ClearNoteAndBarSelection(); break; case MouseButtons.Right: EditorPopupContextMenu(hit, e.Location.ToXna()); break; } }
private void InfoAreaOnMouseUp(ScoreEditorHitTestResult hit, MouseEventArgs e) { var editor = _visualizer.Editor; switch (e.Button) { case MouseButtons.Left: { // Click info area to select measures. if (editor.HasSelectedNotes) { // Clear note selection first. editor.ClearSelectedNotes(); editor.Invalidate(); } else { // Select/unselect bar(s). var bar = hit.Bar; if (bar != null) { switch (Control.ModifierKeys) { case Keys.Control: EditorToggleBarSelectionMultipleMode(bar); break; case Keys.Shift: // TODO: select a series of measures. //break; case Keys.None: EditorToggleBarSelection(bar); break; } editor.Invalidate(); } else { ClearNoteAndBarSelection(); } } break; } case MouseButtons.Right: EditorPopupContextMenu(hit, e.Location.ToXna()); break; } }
private void EditorPopupContextMenu(ScoreEditorHitTestResult hit, Point location) { VisualizerContextMenu menu; if (hit.HitRegion == ScoreEditorHitRegion.SpecialNoteArea) { menu = hit.HitAnyNote ? VisualizerContextMenu.SpecialNoteModify : VisualizerContextMenu.SpecialNoteAdd; } else { menu = hit.HitAnyNote ? VisualizerContextMenu.Note : VisualizerContextMenu.Bar; } var ea = new ContextMenuRequestedEventArgs(menu, hit); _visualizer.RequestContextMenu(ea); }
private void EditNoteModeSelect(ScoreEditorHitTestResult hit, MouseEventArgs e) { var editor = _visualizer.Editor; if (hit.HitAnyNote) { if (editor.HasSelectedBars) { editor.ClearSelectedBars(); editor.Invalidate(); } else { var note = hit.Note; if (note == _lastMouseDownNote) { switch (Control.ModifierKeys) { case Keys.Control: EditorToggleNoteSelectionMultipleMode(note); break; case Keys.Shift: // TODO: select a series of notes. Debug.Print("TODO: select a series of notes."); EditorToggleNoteSelection(note); break; case Keys.None: EditorToggleNoteSelection(note); break; } editor.Invalidate(); } } } else { ClearNoteAndBarSelection(); } }
/// <summary> /// Performs a hit test and returns the result. /// </summary> /// <param name="x">The X coordinate of the hit test point, relative to this control.</param> /// <param name="y">The Y coordinate of the hit test point, relative to this control.</param> /// <returns>The result of this hit test.</returns> public ScoreEditorHitTestResult HitTest(int x, int y) { var score = Project?.Project.GetScore(Difficulty); if (score == null || !score.HasAnyBar) { return(ScoreEditorHitTestResult.GetInvalidResult(x, y)); } var barArea = ScoreEditorLayout.GetBarArea(Config, ClientSize); if (!barArea.Contains(x, y)) { return(ScoreEditorHitTestResult.GetInvalidResult(x, y)); } var config = Config; ScoreEditorHitRegion hitRegion; var relativeX = x - barArea.Left; if (relativeX < config.InfoAreaWidth) { hitRegion = ScoreEditorHitRegion.InfoArea; } else if (relativeX < config.InfoAreaWidth + config.GridNumberAreaWidth - config.NoteRadius) { hitRegion = ScoreEditorHitRegion.GridNumberArea; } else if (relativeX < config.InfoAreaWidth + config.GridNumberAreaWidth + config.GridAreaWidth + config.NoteRadius) { hitRegion = ScoreEditorHitRegion.GridArea; } else { hitRegion = ScoreEditorHitRegion.SpecialNoteArea; } var gridArea = ScoreEditorLayout.GetGridArea(config, ClientSize); var columnWidth = gridArea.Width / (config.NumberOfColumns - 1); var barStartY = (float)ScrollOffsetY; if (y > barStartY + config.NoteRadius) { return(ScoreEditorHitTestResult.GetInvalidResult(x, y)); } var unit = Look.BarLineSpaceUnit; var spaceUnitRatio = ScoreEditorLayout.SpaceUnitRadiusRatio; foreach (var bar in score.Bars) { var numGrids = bar.GetNumberOfGrids(); var barHeight = numGrids * unit; var hitInThisBar = barStartY + config.NoteRadius >= y && y > barStartY - (barHeight - config.NoteRadius); if (!hitInThisBar) { // Continue to the next bar. barStartY -= barHeight; continue; } // Calculate zooming compensation. var firstClearDrawnRatio = ScoreEditorLayout.BarZoomRatio.FirstOrDefault(i => unit * i >= config.NoteRadius * spaceUnitRatio); if (firstClearDrawnRatio == 0) { firstClearDrawnRatio = numGrids; } var newUnit = unit * firstClearDrawnRatio; // Locate the column. // Remember, gridArea is already adjusted. var relativeGridX = x - gridArea.Left; var testCol = (int)((relativeGridX + config.NoteRadius) / columnWidth); if (testCol < 0) { return(new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, null, -1, NotePosition.Default)); } var testX = testCol * columnWidth; int col; if (Math.Abs(relativeGridX - testX) < config.NoteRadius) { col = testCol; } else if (Math.Abs(relativeGridX - (testX + columnWidth)) < config.NoteRadius) { col = testCol + 1; } else { if (hitRegion == ScoreEditorHitRegion.SpecialNoteArea) { col = -1; } else { return(new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, null, -1, NotePosition.Default)); } } // Y position of the hit, relative to start of this bar. var relativeY = -(y - barStartY); // List the gaming note in current column. var possibleNotesInColumn = bar.Notes.Where(n => (int)n.Basic.FinishPosition == col + 1).ToList(); // Variables for row locating. int testRow, row; float testY; // If no note is hit, follow the traditional algorithm to find if there is any hit on special notes. if (possibleNotesInColumn.Count == 0) { // Locate the row. testRow = (int)((relativeY + config.NoteRadius) / newUnit); if (testRow < 0) { break; } testY = testRow * newUnit; if (Math.Abs(relativeY - testY) < config.NoteRadius) { row = testRow; } else if (Math.Abs(relativeY - (testY + newUnit)) < config.NoteRadius) { row = testRow + 1; } else { return(new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, null, -1, NotePosition.Default)); } row *= firstClearDrawnRatio; // Hit any gaming note? var note = bar.Notes.FirstOrDefault(n => n.Basic.IndexInGrid == row && (int)n.Basic.FinishPosition == col + 1); // Hit any special note? if (note == null && hitRegion == ScoreEditorHitRegion.SpecialNoteArea) { note = bar.Notes.FirstOrDefault(n => n.Helper.IsSpecial && n.Basic.IndexInGrid == row); } var result = new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, note, row, col + 1); return(result); } // Otherwise, use the new algorithm to find a possible note in current column. possibleNotesInColumn.Sort(Note.TimingComparison); var noteRadius = config.NoteRadius; for (var i = 0; i < possibleNotesInColumn.Count; ++i) { var currentNote = possibleNotesInColumn[i]; var currentY = unit * currentNote.Basic.IndexInGrid; // Is the Y coordinate inside current note's region? if (currentY - noteRadius <= relativeY && relativeY <= currentY + noteRadius) { // We got a possible match! Note nextNote; float nextY; if (i < possibleNotesInColumn.Count - 1) { // The next note is in the same bar. nextNote = possibleNotesInColumn[i + 1]; nextY = unit * nextNote.Basic.IndexInGrid; } else { // The next note is in the next bar, or... var nextBar = bar.GetNextBar(); // it is too far away (then ignore it, we found a match). if (nextBar == null || nextBar.Notes.Count == 0) { var result = new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, currentNote, currentNote.Basic.IndexInGrid, col + 1); return(result); } var notesOnSameColumnInNextBar = nextBar.Notes.Where(n => (int)n.Basic.FinishPosition == col + 1).ToList(); notesOnSameColumnInNextBar.Sort(Note.TimingComparison); nextNote = notesOnSameColumnInNextBar.FirstOrDefault(); if (nextNote == null) { // Also too far away. We've found a match. var result = new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, currentNote, currentNote.Basic.IndexInGrid, col + 1); return(result); } nextY = barHeight + unit * nextNote.Basic.IndexInGrid; } // Is the Y coordinate not overlapped by the next note (because the notes are drawn from bottom to top)? if (relativeY < nextY - noteRadius) { var result = new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, currentNote, currentNote.Basic.IndexInGrid, col + 1); return(result); } } } // Locate the row. Again. testRow = (int)((relativeY + config.NoteRadius) / newUnit); if (testRow < 0) { break; } testY = testRow * newUnit; if (Math.Abs(relativeY - testY) < config.NoteRadius) { row = testRow; } else if (Math.Abs(relativeY - (testY + newUnit)) < config.NoteRadius) { row = testRow + 1; } else { return(new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, null, -1, NotePosition.Default)); } row *= firstClearDrawnRatio; // Sorry, no can do. Maybe you hit the empty area. return(new ScoreEditorHitTestResult(new Point(x, y), hitRegion, bar, null, row, col + 1)); } return(new ScoreEditorHitTestResult(new Point(x, y), hitRegion, null, null, -1, NotePosition.Default)); }
private void EditNoteModeHoldFlick(ScoreEditorHitTestResult hit, MouseEventArgs e) { var editor = _visualizer.Editor; Note lastNote = null; if (editor.HasOneSelectedNote) { lastNote = editor.GetSelectedNote(); } else if (hit.HitAnyNote && editor.HasSelectedNotes) { Debug.Print("You can only select one note to create a hold pair."); return; } Note thisNote = null; var isNoteAdded = false; if (hit.HitAnyNote) { // The clicked note is always selected. thisNote = hit.Note == _lastMouseDownNote ? hit.Note : null; } else if (hit.HitBarGridIntersection) { // If not selected, add a note and select it. thisNote = EditorAddNote(hit); isNoteAdded = true; } // If the user clicked on nothing, then clear all selections. if (thisNote == null) { ClearNoteAndBarSelection(); editor.Invalidate(); return; } // If the user clicked on the same note, just perform the standard note selection. if (lastNote == null || lastNote == thisNote) { thisNote.EditorToggleSelected(); editor.Invalidate(); return; } var relationCreated = false; if (thisNote.Basic.FinishPosition == lastNote.Basic.FinishPosition) { do { // If the selected note is already a hold note (start/end) and there is a hold relation between the // two notes, then switch selection. if (NoteHelper.AreNotesInHoldChain(thisNote, lastNote)) { // Yep just switch selection. Do nothing in this branch. } else { var errStr = EnsureHoldValid(thisNote, lastNote); if (errStr != null) { if (isNoteAdded) { thisNote.Basic.Bar.RemoveNote(thisNote); } Debug.Print(errStr); break; } // Make hold. var(first, second) = NoteHelper.Split(thisNote, lastNote); NoteHelper.MakeHold(first, second); _visualizer.InformProjectModified(); relationCreated = true; } } while (false); } else { do { // If the selected note is already a flick note and there is a flick relation between the // two notes, then switch selection. if (NoteHelper.AreNotesInFlickChain(thisNote, lastNote)) { // Yep just switch selection. Do nothing in this branch. } else { var errStr = EnsureFlickValid(thisNote, lastNote); if (errStr != null) { if (isNoteAdded) { thisNote.Basic.Bar.RemoveNote(thisNote); } Debug.Print(errStr); break; } // Make flick. var(first, second) = NoteHelper.Split(thisNote, lastNote); NoteHelper.MakeFlick(first, second); _visualizer.InformProjectModified(); relationCreated = true; } } while (false); } // Now handle a special case: link flick after a slide. if (!relationCreated) { var(first, second) = NoteHelper.Split(thisNote, lastNote); if (first.Basic.FinishPosition != second.Basic.FinishPosition) { if (first.Helper.IsSlideEnd && second.Helper.IsFlickStart) { NoteHelper.MakeSlideToFlick(first, second); _visualizer.InformProjectModified(); relationCreated = true; } } } if (relationCreated) { thisNote.EditorUnselect(); lastNote.EditorUnselect(); } else { lastNote.EditorUnselect(); thisNote.EditorSelect(); } // Did we created a hold pair? // If so, ensure the pair's start position being the same. if (relationCreated) { if (thisNote.Helper.IsHoldStart) { thisNote.Editor.HoldPair.Basic.StartPosition = thisNote.Basic.StartPosition; } else if (thisNote.Helper.IsHoldEnd) { thisNote.Basic.StartPosition = thisNote.Editor.HoldPair.Basic.StartPosition; } } editor.Invalidate(); }
private void SpecialNoteAreaOnMouseDown(ScoreEditorHitTestResult hit, MouseEventArgs e) { }
private void GridAreaOnMouseUp(ScoreEditorHitTestResult hit, MouseEventArgs e) { var editor = _visualizer.Editor; switch (e.Button) { case MouseButtons.Left: if (!_selectionRectangle.IsEmpty) { // We are handling selection here. break; } // Whatever note you hit, change its start up position. // If no note is hit and we are hitting a grid crossing, the EditorAddNote method is invoked // and the newly added note's StartPosition is automatically set there. if (hit.HitAnyNote && editor.NoteStartPosition != NotePosition.Default) { var note = hit.Note; note.Basic.StartPosition = editor.NoteStartPosition; if (note.Helper.IsHoldStart) { note.Editor.HoldPair.Basic.StartPosition = note.Basic.StartPosition; } else if (note.Helper.IsHoldEnd) { note.Basic.StartPosition = note.Editor.HoldPair.Basic.StartPosition; } _visualizer.InformProjectModified(); } if (hit.HitAnyNote) { var modifiers = Control.ModifierKeys; if (modifiers == Keys.Alt) { if (hit.Note.Helper.IsHoldEnd || hit.Note.Helper.IsSlideEnd) { // Alt+Click on end hold or end slide to change its flick type. var flickType = hit.Note.Basic.FlickType; switch (flickType) { case NoteFlickType.None: flickType = NoteFlickType.Left; break; case NoteFlickType.Left: flickType = NoteFlickType.Right; break; case NoteFlickType.Right: flickType = NoteFlickType.None; break; default: throw new ArgumentOutOfRangeException(nameof(flickType), flickType, null); } hit.Note.Basic.FlickType = flickType; _visualizer.InformProjectModified(); } else if (hit.Note.Helper.IsFlickEnd) { // Alt+Click on end flick to change its flick type. Left / right only. // This can be used to make reversed flick. (Star!!, Master+) var flickType = hit.Note.Basic.FlickType; switch (flickType) { case NoteFlickType.Left: flickType = NoteFlickType.Right; break; case NoteFlickType.Right: flickType = NoteFlickType.Left; break; default: throw new ArgumentOutOfRangeException(nameof(flickType), flickType, null); } hit.Note.Basic.FlickType = flickType; _visualizer.InformProjectModified(); } } } // Then handle the mode-specific actions. switch (editor.EditMode) { case ScoreEditMode.Select: EditNoteModeSelect(hit, e); break; case ScoreEditMode.Tap: EditNoteModeTap(hit, e); break; case ScoreEditMode.HoldFlick: EditNoteModeHoldFlick(hit, e); break; case ScoreEditMode.Slide: EditNoteModeSlide(hit, e); break; default: throw new ArgumentOutOfRangeException(nameof(editor.EditMode), editor.EditMode, null); } break; case MouseButtons.Right: EditorPopupContextMenu(hit, e.Location.ToXna()); break; } }
private void GridAreaOnMouseDown(ScoreEditorHitTestResult hit, MouseEventArgs e) { }
private void GridNumberAreaOnMouseUp(ScoreEditorHitTestResult hit, MouseEventArgs e) { ClearNoteAndBarSelection(); }