diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index e90406f95..ba5794a52 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -608,6 +608,7 @@ class Conductor else { var resultMeasureTime:Float = 0; + ms = ms < 0 ? 0 : ms; var lastTimeChange:SongTimeChange = timeChanges[0]; var i:Int = -1; @@ -654,6 +655,7 @@ class Conductor else { var resultMs:Float = 0; + measureTime = measureTime < 0 ? 0 : measureTime; var lastTimeChange:SongTimeChange = timeChanges[0]; var i:Int = -1; @@ -701,6 +703,7 @@ class Conductor else { var resultStep:Float = 0; + ms = ms < 0 ? 0 : ms; var lastTimeChange:SongTimeChange = timeChanges[0]; var i:Int = -1; @@ -744,6 +747,7 @@ class Conductor else { var resultMs:Float = 0; + stepTime = stepTime < 0 ? 0 : stepTime; var lastTimeChange:SongTimeChange = timeChanges[0]; var i:Int = -1; @@ -809,6 +813,30 @@ class Conductor } } + /** + * Given a time in milliseconds, return the time change that time is inside of. + * @param ms The time in milliseconds. + * @return The resulting time change. + */ + public function getTimeChange(ms:Float):SongTimeChange + { + if (timeChanges.length == 0) + { + return new SongTimeChange(0, 100); + } + var i:Int = 0; + ms = ms < 0 ? 0 : ms; + for (timeChange in timeChanges) + { + i++; + if ((i == timeChanges.length && ms >= timeChange.timeStamp) || (ms >= timeChange.timeStamp && ms < timeChanges[i].timeStamp)) + { + return timeChange; + } + } + return new SongTimeChange(0, 100); + } + /** * Adds Conductor fields to the Flixel debugger variable display. * @param conductorToUse The conductor to use. Defaults to `Conductor.instance`. diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index f64a4f230..61e84cc05 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -179,12 +179,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * The height of the note selection buttons above the grid. */ - public static final NOTE_SELECT_BUTTON_HEIGHT:Int = 24; + public static final NOTE_SELECT_BUTTON_HEIGHT:Int = 32; /** * The amount of padding between the menu bar and the chart grid when fully scrolled up. */ - public static final GRID_TOP_PAD:Int = NOTE_SELECT_BUTTON_HEIGHT + 12; + public static final GRID_TOP_PAD:Int = NOTE_SELECT_BUTTON_HEIGHT + 4; /** * The initial vertical position of the chart grid. @@ -199,7 +199,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * The Y position of the note preview area. */ - public static final NOTE_PREVIEW_Y_POS:Int = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 4; + public static final NOTE_PREVIEW_Y_POS:Int = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT + 4; /** * The X position of the note grid. @@ -410,8 +410,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState setNotePreviewViewportBounds(calculateNotePreviewViewportBounds()); refreshNotePreviewPlayheadPosition(); - // Update the measure tick display. - if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0; + if (measureTicks != null) handleMeasureTickPosition(); return this.scrollPositionInPixels; } @@ -2064,6 +2063,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var playbarEnd:Button; + /** + * The button above the grid that does nothing currently. Used for covering up the measure ticks being cut off :) + * Constructed manually and added to the layout so we can control its position. + */ + var buttonSelectDummy:Button; + /** * The button above the grid that selects all notes on the opponent's side. * Constructed manually and added to the layout so we can control its position. @@ -2889,7 +2894,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState buttonSelectOpponent.allowFocus = false; buttonSelectOpponent.text = "Opponent"; // Default text. buttonSelectOpponent.x = GRID_X_POS; - buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 8; + buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT; buttonSelectOpponent.width = GRID_SIZE * 4; buttonSelectOpponent.height = NOTE_SELECT_BUTTON_HEIGHT; buttonSelectOpponent.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection."; @@ -2955,6 +2960,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState performCommand(new SetItemSelectionCommand([], currentSongChartEventData)); } } + + buttonSelectDummy = new Button(); + buttonSelectDummy.allowFocus = false; + buttonSelectDummy.x = buttonSelectOpponent.x - GRID_SIZE; + buttonSelectDummy.y = buttonSelectEvent.y; + buttonSelectDummy.width = GRID_SIZE; + buttonSelectDummy.height = NOTE_SELECT_BUTTON_HEIGHT; + buttonSelectDummy.zIndex = 110; + add(buttonSelectDummy); } /** @@ -4373,13 +4387,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + // An odd way to fix this since measureTicks contains more than just the gray sidebar now. + var gridPlayheadScrollArea:FlxRect = FlxRect.weak(measureTicks?.x ?? -100, MENU_BAR_HEIGHT + GRID_TOP_PAD, GRID_SIZE, 597); + if (FlxG.mouse.justPressed) { if (scrollAnchorScreenPos != null) { scrollAnchorScreenPos = null; } - else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks) && !isCursorOverHaxeUI) + else if (measureTicks != null && gridPlayheadScrollArea.containsXY(FlxG.mouse.viewX, FlxG.mouse.viewY) && !isCursorOverHaxeUI) { gridPlayheadScrollAreaPressed = true; // Stop audio playback while dragging on the grid playhead. @@ -5671,7 +5688,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var xOffset = 45 - (healthIconBF.width / 2); healthIconBF.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS + gridTiledSprite.width + xOffset); var yOffset = 30 - (healthIconBF.height / 2); - healthIconBF.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; + healthIconBF.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT + 8) + yOffset; } // Visibly center the Dad health icon. @@ -5680,7 +5697,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var xOffset = 75 + (healthIconDad.width / 2); healthIconDad.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS - xOffset); var yOffset = 30 - (healthIconDad.height / 2); - healthIconDad.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset; + healthIconDad.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT + 8) + yOffset; } } @@ -6295,10 +6312,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { gridTiledSprite.height = songLengthInPixels; } - if (measureTicks != null) - { - measureTicks.setHeight(songLengthInPixels); - } // Remove any notes past the end of the song. var songCutoffPointSteps:Float = songLengthInSteps - 0.1; @@ -6586,11 +6599,50 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + /** + * Updates the Conductor instance and checkes for time signature changes. + */ + function updateSongTime():Void + { + var oldTimeSignatureNum:Int = Conductor.instance.timeSignatureNumerator; + var oldTimeSignatureDen:Int = Conductor.instance.timeSignatureDenominator; + Conductor.instance.update(audioInstTrack.time); + if (Conductor.instance.timeSignatureNumerator != oldTimeSignatureNum + || Conductor.instance.timeSignatureDenominator != oldTimeSignatureDen) + { + updateTimeSignature(); + } + } + + /** + * Updates the measure tick bitmap forcibly to make sure it's correct. + */ function updateTimeSignature():Void { - this.updateTheme(); + this.updateMeasureTicks(true); gridTiledSprite.loadGraphic(gridBitmap); - measureTicks.reloadTickBitmap(); + } + + /** + * Handle positioning the measure ticks sprite. + */ + function handleMeasureTickPosition():Void + { + this.updateMeasureTicks(); + var currentMeasureTime = Conductor.instance.getMeasureTimeInMs(Math.floor(Conductor.instance.getTimeInMeasures(scrollPositionInMs))); + var currentMeasurePos = currentMeasureTime < 0 ? 0 : Conductor.instance.getTimeInSteps(currentMeasureTime) * GRID_SIZE; + measureTicks.y = gridTiledSprite?.y + currentMeasurePos; + // Make sure the measure tick bitmap does not past the grid itself. + var totalMeasureTicksHeight:Float = currentMeasurePos + measureTickBitmap.height; + if (totalMeasureTicksHeight >= songLengthInPixels) + { + var spillOver:Float = totalMeasureTicksHeight - songLengthInPixels; + measureTicks.setClipRect(new FlxRect(0, 0, measureTickBitmap.width, measureTickBitmap.height - spillOver)); + } + else + { + measureTicks.setClipRect(null); + } } /** @@ -6706,18 +6758,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } - function updateSongTime():Void - { - var oldTimeSignatureNum:Int = Conductor.instance.timeSignatureNumerator; - var oldTimeSignatureDen:Int = Conductor.instance.timeSignatureDenominator; - Conductor.instance.update(audioInstTrack.time); - if (Conductor.instance.timeSignatureNumerator != oldTimeSignatureNum - || Conductor.instance.timeSignatureDenominator != oldTimeSignatureDen) - { - updateTimeSignature(); - } - } - function stopAudioPlayback():Void { if (audioInstTrack != null) audioInstTrack.pause(); diff --git a/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx b/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx index 6a3356c78..bfbf10b63 100644 --- a/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx +++ b/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx @@ -55,6 +55,7 @@ class AddNewTimeChangeCommand implements ChartEditorCommand state.updateSongTime(); // basically will update the time signature for the editor if necessary. state.updateGridHeight(); + state.updateTimeSignature(); } public function undo(state:ChartEditorState):Void @@ -79,6 +80,7 @@ class AddNewTimeChangeCommand implements ChartEditorCommand state.updateSongTime(); state.updateGridHeight(); + state.updateTimeSignature(); } public function shouldAddToHistory(state:ChartEditorState):Bool diff --git a/source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx b/source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx index 01894237b..33a18cefd 100644 --- a/source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx @@ -73,6 +73,7 @@ class ModifyTimeChangeCommand implements ChartEditorCommand state.updateSongTime(); state.updateGridHeight(); + state.updateTimeSignature(); } public function undo(state:ChartEditorState):Void @@ -105,6 +106,7 @@ class ModifyTimeChangeCommand implements ChartEditorCommand state.updateSongTime(); state.updateGridHeight(); + state.updateTimeSignature(); } public function shouldAddToHistory(state:ChartEditorState):Bool @@ -118,7 +120,6 @@ class ModifyTimeChangeCommand implements ChartEditorCommand public function toString():String { - return - 'TimeChange ${timeChangeIndex}: ${targetTimeStamp} : BPM: ${targetBPM} in ${targetNumerator}/${targetDenominator}'; + return 'TimeChange ${timeChangeIndex}: ${targetTimeStamp} : BPM: ${targetBPM} in ${targetNumerator}/${targetDenominator}'; } } diff --git a/source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx index 85aa28010..174039a23 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx @@ -31,7 +31,7 @@ class RemoveTimeChangeCommand implements ChartEditorCommand } else { - removedTimeChange = timeChanges.splice(timeChangeIndex , 1); + removedTimeChange = timeChanges.splice(timeChangeIndex, 1); } state.currentSongMetadata.timeChanges = timeChanges; @@ -48,6 +48,7 @@ class RemoveTimeChangeCommand implements ChartEditorCommand state.updateSongTime(); state.updateGridHeight(); + state.updateTimeSignature(); } public function undo(state:ChartEditorState):Void @@ -71,6 +72,7 @@ class RemoveTimeChangeCommand implements ChartEditorCommand state.updateSongTime(); state.updateGridHeight(); + state.updateTimeSignature(); } public function shouldAddToHistory(state:ChartEditorState):Bool @@ -81,7 +83,8 @@ class RemoveTimeChangeCommand implements ChartEditorCommand public function toString():String { if (removedTimeChange != null && removedTimeChange.length > 0) return - 'TimeChange ${timeChangeIndex} : ${removedTimeChange[0].timeStamp} : BPM: ${removedTimeChange[0].bpm} in ${removedTimeChange[0].timeSignatureNum}/${removedTimeChange[0].timeSignatureDen} removed' - else return 'huh?'; + 'TimeChange ${timeChangeIndex} : ${removedTimeChange[0].timeStamp} : BPM: ${removedTimeChange[0].bpm} in ${removedTimeChange[0].timeSignatureNum}/${removedTimeChange[0].timeSignatureDen} removed' + else + return 'huh?'; } } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx index e61329ba3..f8ce01df6 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorMeasureTicks.hx @@ -1,8 +1,8 @@ package funkin.ui.debug.charting.components; import flixel.FlxSprite; -import flixel.addons.display.FlxTiledSprite; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; +import flixel.math.FlxRect; import flixel.text.FlxText; import flixel.util.FlxColor; @@ -11,9 +11,11 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup { var chartEditorState:ChartEditorState; - var tickTiledSprite:FlxTiledSprite; + var measureTicksSprite:FlxSprite; var measureNumbers:Array = []; + public var measureLengthsInPixels:Array = []; + override function set_y(value:Float):Float { var result = super.set_y(value); @@ -29,8 +31,8 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup this.chartEditorState = chartEditorState; - tickTiledSprite = new FlxTiledSprite(chartEditorState.measureTickBitmap, chartEditorState.measureTickBitmap.width, 1000, false, true); - add(tickTiledSprite); + measureTicksSprite = new FlxSprite(0, 0); + add(measureTicksSprite); for (i in 0...5) { @@ -45,40 +47,39 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup public function reloadTickBitmap():Void { - tickTiledSprite.loadGraphic(chartEditorState.measureTickBitmap); + measureTicksSprite.loadGraphic(chartEditorState.measureTickBitmap); + } + + public function setClipRect(rect:Null):Void + { + measureTicksSprite.clipRect = rect; } /** * Update all 5 measure numbers, since that's the most we can really see at a time, even if barely. * Please excuse the horror you're about to witness. + * Welp, you gotta go back in commits to see it now. */ function updateMeasureNumber() { - var currentPixelScrollPositionInMs = Conductor.instance?.getStepTimeInMs((this.chartEditorState?.scrollPositionInPixels + 1) / ChartEditorState.GRID_SIZE); - var measureNumberInViewport = Math.floor(Conductor.instance?.getTimeInMeasures(currentPixelScrollPositionInMs)); + if (measureLengthsInPixels.length == 0 || measureLengthsInPixels == null) return; + var currentMeasure:Int = Math.floor(Conductor.instance?.getTimeInMeasures(chartEditorState.scrollPositionInMs)); for (i in 0...measureNumbers.length) { var measureNumber:FlxText = measureNumbers[i]; if (measureNumber == null) continue; - var measureNumberPosition = Math.floor(Conductor.instance?.getTimeInSteps(Conductor.instance?.getMeasureTimeInMs(measureNumberInViewport + - i)) * ChartEditorState.GRID_SIZE); - - measureNumber.y = measureNumberPosition + this.y; + var measureNumberPosition = measureLengthsInPixels[i]; + measureNumber.y = this.y + measureNumberPosition; // Show the measure number only if it isn't beneath the end of the note grid. // Using measureNumber + 1 because the cut-off bar at the bottom is technically a bar, but it looks bad if a measure number shows up there. - var fixedMeasureNumberValue = measureNumberInViewport + i + 1; + var fixedMeasureNumberValue = currentMeasure + i + 1; if (fixedMeasureNumberValue < Math.ceil(Conductor.instance?.getTimeInMeasures(chartEditorState.songLengthInMs))) measureNumber.text = '${fixedMeasureNumberValue}'; else measureNumber.text = ''; } } - - public function setHeight(songLengthInPixels:Float):Void - { - tickTiledSprite.height = songLengthInPixels; - } } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index ac8985dc1..5f28ad62e 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -5,6 +5,7 @@ import flixel.addons.display.FlxSliceSprite; import flixel.FlxSprite; import flixel.math.FlxRect; import flixel.util.FlxColor; +import funkin.data.song.SongData.SongTimeChange; import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme; import openfl.display.BitmapData; import openfl.geom.Rectangle; @@ -73,6 +74,9 @@ class ChartEditorThemeHandler static final TOTAL_COLUMN_COUNT:Int = ChartEditorState.STRUMLINE_SIZE * 2 + 1; + // Used for checking in updateMeasureTicks() so we don't have to redraw it everytime if the current measure is the same as before. + static var previousMeasure:Int = -69; + /** * When the theme is changed, this function updates all of the UI elements to match the new theme. * @param state The ChartEditorState to update. @@ -174,58 +178,6 @@ class ChartEditorThemeHandler ChartEditorState.GRID_SELECTION_BORDER_WIDTH, state.gridBitmap.height), selectionBorderColor); - // Draw horizontal dividers between the measures. - - var gridMeasureDividerColor:FlxColor = switch (state.currentTheme) - { - case Light: GRID_MEASURE_DIVIDER_COLOR_LIGHT; - case Dark: GRID_MEASURE_DIVIDER_COLOR_DARK; - default: GRID_MEASURE_DIVIDER_COLOR_LIGHT; - }; - - // Divider at top - state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor); - // Divider at bottom - var dividerLineBY:Float = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2); - state.gridBitmap.fillRect(new Rectangle(0, dividerLineBY, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor); - - // Draw horizontal dividers between the beats. - - var gridBeatDividerColor:FlxColor = switch (state.currentTheme) - { - case Light: GRID_BEAT_DIVIDER_COLOR_LIGHT; - case Dark: GRID_BEAT_DIVIDER_COLOR_DARK; - default: GRID_BEAT_DIVIDER_COLOR_LIGHT; - }; - - // Selection borders horizontally in the middle. - for (i in 1...(Conductor.instance.stepsPerMeasure)) - { - // There may be a different number of beats per measure, but there's always 4 steps per beat. - if ((i % Constants.STEPS_PER_BEAT) == 0) - { - state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width, - GRID_BEAT_DIVIDER_WIDTH), - gridBeatDividerColor); - } - } - - // Draw vertical dividers between the strumlines. - - var gridStrumlineDividerColor:FlxColor = switch (state.currentTheme) - { - case Light: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT; - case Dark: GRID_STRUMLINE_DIVIDER_COLOR_DARK; - default: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT; - }; - - // Divider at 1 * (Strumline Size) - var dividerLineAX:Float = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); - state.gridBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor); - // Divider at 2 * (Strumline Size) - var dividerLineBX:Float = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); - state.gridBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor); - if (state.gridTiledSprite != null) { state.gridTiledSprite.loadGraphic(state.gridBitmap); @@ -234,41 +186,111 @@ class ChartEditorThemeHandler } /** - * Vertical measure ticks. + * Draw the measure ticks left of the grid. This also includes the horizontal beat and measure dividers as well as the vertical strumline dividers. */ - static function updateMeasureTicks(state:ChartEditorState):Void + public static function updateMeasureTicks(state:ChartEditorState, force:Bool = false):Void { var measureTickWidth:Int = 6; var beatTickWidth:Int = 4; var stepTickWidth:Int = 2; + var measuresToCheck:Int = 5; - // Draw the measure ticks. - var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 grid squares wide. - var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure); // 1 measure tall. - state.measureTickBitmap = new BitmapData(ticksWidth, ticksHeight, true); - state.measureTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK); + var currentMeasure:Int = Math.floor(Conductor.instance.getTimeInMeasures(state.scrollPositionInMs)); + if (previousMeasure == currentMeasure && !force) return; + if (currentMeasure < 0) currentMeasure = previousMeasure = 0; - // Draw the measure ticks. - state.measureTickBitmap.fillRect(new Rectangle(0, 0, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); - var bottomTickY:Float = state.measureTickBitmap.height - (measureTickWidth / 2); - state.measureTickBitmap.fillRect(new Rectangle(0, bottomTickY, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + // Make the bitmap. + var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE); + var linesWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT); + state.measureTickBitmap = new BitmapData(ticksWidth + linesWidth, 4096, true, 0x00FFFFFF); - // Draw the beat and step ticks. No need for two seperate loops thankfully. - // This'll be fun to update when beat tuplets become functional. - for (i in 1...(Conductor.instance.stepsPerMeasure)) + // Draw the vertical grey sidebar. + state.measureTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, 4096), GRID_BEAT_DIVIDER_COLOR_DARK); + + // Then draw the ticks and dividers themselves. + var totalTicksHeight:Int = 0; + var measureLengthsInPixels:Array = []; + for (measure in 0...measuresToCheck) { - if ((i % Constants.STEPS_PER_BEAT) == 0) // If we're on a beat, draw a beat tick. + var currentTimeInMs:Float = Conductor.instance.getMeasureTimeInMs(currentMeasure + measure) + + 10; // Manual adjustment as it can get the wrong time change without this. + var currentTimeChange:SongTimeChange = Conductor.instance.getTimeChange(currentTimeInMs); + var stepsPerMeasure:Int = Constants.STEPS_PER_BEAT * currentTimeChange.timeSignatureNum; + var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * stepsPerMeasure); + + // Draw horizontal dividers between the measures. + var gridMeasureDividerColor:FlxColor = switch (state.currentTheme) { - var beatTickY:Float = state.measureTickBitmap.height * i / Conductor.instance.stepsPerMeasure - (beatTickWidth / 2); - var beatTickLength:Float = state.measureTickBitmap.width * 2 / 3; - state.measureTickBitmap.fillRect(new Rectangle(0, beatTickY, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); - } - else // Else, draw a step tick. + case Light: GRID_MEASURE_DIVIDER_COLOR_LIGHT; + case Dark: GRID_MEASURE_DIVIDER_COLOR_DARK; + default: GRID_MEASURE_DIVIDER_COLOR_LIGHT; + }; + + // Divider at top + state.measureTickBitmap.fillRect(new Rectangle(ticksWidth, totalTicksHeight, linesWidth, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor); + // Divider at bottom + var dividerLineBY:Float = ticksHeight - (GRID_MEASURE_DIVIDER_WIDTH / 2); + state.measureTickBitmap.fillRect(new Rectangle(ticksWidth, totalTicksHeight + dividerLineBY, linesWidth, GRID_MEASURE_DIVIDER_WIDTH / 2), + gridMeasureDividerColor); + + // Measure ticks. + state.measureTickBitmap.fillRect(new Rectangle(0, totalTicksHeight, ticksWidth, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + var bottomTickY:Float = ticksHeight - (measureTickWidth / 2); + state.measureTickBitmap.fillRect(new Rectangle(0, totalTicksHeight + bottomTickY, ticksWidth, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + + // Draw horizontal dividers between the beats, this is done inside the following loop. + var gridBeatDividerColor:FlxColor = switch (state.currentTheme) { - var stepTickY:Float = state.measureTickBitmap.height * i / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2); - var stepTickLength:Float = state.measureTickBitmap.width * 1 / 3; - state.measureTickBitmap.fillRect(new Rectangle(0, stepTickY, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + case Light: GRID_BEAT_DIVIDER_COLOR_LIGHT; + case Dark: GRID_BEAT_DIVIDER_COLOR_DARK; + default: GRID_BEAT_DIVIDER_COLOR_LIGHT; + }; + + // Draw the beat ticks and dividers, and step ticks. No need for two seperate loops thankfully. + for (i in 1...stepsPerMeasure) + { + if ((i % Constants.STEPS_PER_BEAT) == 0) // If we're on a beat, draw a beat tick and divider. + { + var beatTickY:Float = totalTicksHeight + ChartEditorState.GRID_SIZE * i - (beatTickWidth / 2); + var beatTickLength:Float = ticksWidth * 2 / 3; + state.measureTickBitmap.fillRect(new Rectangle(0, beatTickY, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + + // Horizontal divider. + state.measureTickBitmap.fillRect(new Rectangle(ticksWidth, totalTicksHeight + (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), + linesWidth, GRID_BEAT_DIVIDER_WIDTH), + gridBeatDividerColor); + } + else // Else, draw a step tick. + { + var stepTickY:Float = totalTicksHeight + ChartEditorState.GRID_SIZE * i - (stepTickWidth / 2); + var stepTickLength:Float = ticksWidth * 1 / 3; + state.measureTickBitmap.fillRect(new Rectangle(0, stepTickY, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT); + } } + measureLengthsInPixels.push(totalTicksHeight); + totalTicksHeight += ticksHeight; + } + previousMeasure = currentMeasure; + + // Finally, draw vertical dividers between the strumlines. + var gridStrumlineDividerColor:FlxColor = switch (state.currentTheme) + { + case Light: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT; + case Dark: GRID_STRUMLINE_DIVIDER_COLOR_DARK; + default: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT; + }; + + // Divider at 1 * (Strumline Size) + var dividerLineAX:Float = ticksWidth + ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); + state.measureTickBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.measureTickBitmap.height), gridStrumlineDividerColor); + // Divider at 2 * (Strumline Size) + var dividerLineBX:Float = ticksWidth + ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); + state.measureTickBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.measureTickBitmap.height), gridStrumlineDividerColor); + + if (state.measureTicks != null) + { + state.measureTicks.measureLengthsInPixels = measureLengthsInPixels; + state.measureTicks.reloadTickBitmap(); } }