From 3a7c3d144d7f73bbee408051b47bbb28cf0f8ffa Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Sun, 2 Jan 2022 12:14:15 -0500 Subject: [PATCH 01/25] initial work in InteractiveViewer refactor --- lib/music_view/music_scroll_container.dart | 313 +++++---------- lib/music_view/music_view.dart | 431 ++++++++++----------- lib/ui_models.dart | 6 +- pubspec.lock | 37 +- pubspec.yaml | 5 +- 5 files changed, 343 insertions(+), 449 deletions(-) diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index bdf259b0..39c0d6d9 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -4,6 +4,7 @@ import 'package:beatscratch_flutter_redux/settings/settings.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:matrix4_transform/matrix4_transform.dart'; import '../beatscratch_plugin.dart'; import '../generated/protos/music.pb.dart'; @@ -14,13 +15,6 @@ import '../util/music_theory.dart'; import '../util/util.dart'; import 'music_system_painter.dart'; -class ScaleUpdate { - final double oldScale; - final double newScale; - - ScaleUpdate(this.oldScale, this.newScale); -} - class MusicScrollContainer extends StatefulWidget { static const double minScale = 0.09; static const double maxScale = 1.0; @@ -31,7 +25,6 @@ class MusicScrollContainer extends StatefulWidget { final Color sectionColor; final Melody focusedMelody; final RenderingMode renderingMode; - final double xScale, yScale; final List staves; final Part focusedPart, keyboardPart, colorboardPart; final double height, width; @@ -45,12 +38,8 @@ class MusicScrollContainer extends StatefulWidget { final ValueNotifier highlightedBeat, focusedBeat, tappedBeat; final ValueNotifier tappedPart; final ValueNotifier requestedScrollOffsetForScale; - final ValueNotifier notifyXScaleUpdate, notifyYScaleUpdate; - final ValueNotifier xScaleNotifier, - yScaleNotifier, - targetXScaleNotifier, - targetYScaleNotifier; - final ValueNotifier verticalScrollNotifier; + final TransformationController transformationController; + final ValueNotifier targetScaleNotifier; final BSMethod scrollToFocusedBeat; final bool showingSectionList; @@ -60,8 +49,6 @@ class MusicScrollContainer extends StatefulWidget { this.scrollToFocusedBeat, this.currentSection, this.sectionColor, - this.xScale, - this.yScale, this.focusedMelody, this.renderingMode, this.colorboardNotesNotifier, @@ -80,17 +67,12 @@ class MusicScrollContainer extends StatefulWidget { this.tappedBeat, this.tappedPart, this.requestedScrollOffsetForScale, - this.targetXScaleNotifier, - this.targetYScaleNotifier, + this.targetScaleNotifier, this.isTwoFingerScaling, this.scrollToCurrentBeat, this.centerCurrentSection, this.appSettings, - this.notifyXScaleUpdate, - this.notifyYScaleUpdate, - this.xScaleNotifier, - this.yScaleNotifier, - this.verticalScrollNotifier, + this.transformationController, this.scrollToPart, this.showingSectionList}) : super(key: key); @@ -104,8 +86,6 @@ Rect verticallyVisibleRect = Rect.zero; class _MusicScrollContainerState extends State with TickerProviderStateMixin { - ScrollController verticalController; - AnimationController animationController; ValueNotifier colorGuideOpacityNotifier, colorblockOpacityNotifier, @@ -122,7 +102,6 @@ class _MusicScrollContainerState extends State ValueNotifier focusedPart; ValueNotifier sectionColor; - ScrollController timeScrollController; double _prevBeat; String _prevSectionOrder; String _prevSectionId; @@ -136,33 +115,34 @@ class _MusicScrollContainerState extends State int get numberOfBeats => /*isViewingSection ? widget.currentSection.harmony.beatCount :*/ widget.score.beatCount; - double get xScale => widget.xScale; - - double get yScale => widget.yScale; + double get dx => + MatrixUtils.getAsTranslation(transformationController.value).dx; + double get dy => + MatrixUtils.getAsTranslation(transformationController.value).dy; + double get scale => transformationController.value.getMaxScaleOnAxis(); - double get standardBeatWidth => unscaledStandardBeatWidth * xScale; + double get standardBeatWidth => unscaledStandardBeatWidth; + double get scaledStandardBeatWidth => unscaledStandardBeatWidth * scale; - double get targetXScale => widget.targetXScaleNotifier.value; - double get targetYScale => widget.targetYScaleNotifier.value; + double get targetScale => widget.targetScaleNotifier.value; - double get targetBeatWidth => unscaledStandardBeatWidth * targetXScale; + double get targetBeatWidth => unscaledStandardBeatWidth; double get canvasHeightMagic => 1.3 - 0.3 * (widget.staves.length) / 5; double get sectionsHeight => widget.musicViewMode == MusicViewMode.score || - xScale < 2 * MusicScrollContainer.minScale || + scale < 2 * MusicScrollContainer.minScale || !widget.showingSectionList ? 30 : 0; - // TODO: Eliminate magic "1.2" constant double get systemHeight => (((widget.staves.length + 0.5) * (MusicSystemPainter.staffHeight) * - yScale) + + scale) + sectionsHeight * 2); double get systemRenderAreaWidth => - widget.width - MusicSystemPainter.calculateClefWidth(xScale); + widget.width - MusicSystemPainter.calculateClefWidth(scale); double get beatsOnScreenPerSystem => systemRenderAreaWidth / standardBeatWidth; int get maxSystemsNeeded => @@ -186,8 +166,8 @@ class _MusicScrollContainerState extends State currentBeatTargetSystemIndex * (systemRenderAreaWidth); double get _systemHeightForScrolling => MusicSystemPainter.calculateSystemHeight( - yScale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(yScale); + scale, widget.score.parts.length) + + MusicSystemPainter.calculateSystemPadding(scale); double get _extraHeightForScrolling => _systemHeightForScrolling < widget.height ? max(0, (widget.height - _systemHeightForScrolling) / 3) @@ -236,8 +216,6 @@ class _MusicScrollContainerState extends State @override void initState() { super.initState(); - timeScrollController = ScrollController(); - verticalController = ScrollController(); widget.scrollToFocusedBeat.addListener(() { scrollToFocusedBeat(); }); @@ -256,72 +234,6 @@ class _MusicScrollContainerState extends State sectionColor = ValueNotifier(widget.sectionColor); widget.scrollToCurrentBeat.addListener(scrollToCurrentBeat); widget.centerCurrentSection.addListener(_constrainToSectionBounds); - // scrollToCurrentBeat - timeScrollController.addListener(_timeScrollListener); - verticalController.addListener(_verticalScrollListener); - - VoidCallback _scaleUpdateListener( - ValueNotifier notifier, Function(ScaleUpdate) handler) { - return () { - final value = notifier.value; - if (value != null && - value.oldScale - .notRoughlyEquals(value.newScale, precision: 0.0005)) { - // print("ScaleUpdate: ${value.oldScale.toStringAsFixed(12)} -> " - // "${value.newScale.toStringAsFixed(12)}"); - handler(value); - notifier.value = null; - } - }; - } - - xScaleUpdateListener = - _scaleUpdateListener(widget.notifyXScaleUpdate, (update) { - widget.xScaleNotifier.value = update.newScale; - - if (widget.isTwoFingerScaling) { - scrollToFocusedBeat(instant: true); - } else { - // s1, s2: before/after scale. p1: before offset - final double p1 = timeScrollController.offset, - s1 = update.oldScale, - s2 = update.newScale, - w1 = widget.width, // * s1 / s2, - w2 = widget.width * s2 / s1, - bw2 = unscaledStandardBeatWidth * s2, - bw1 = unscaledStandardBeatWidth * s1; - // p2: the target offset - double p2; - if (autoScroll) { - p2 = p1 * s2 / s1; - double cb1 = currentBeat * bw1; - // if cb1 (the current beat) was on screen - if (cb1 + bw1 >= p1 && cb1 <= p1 + w1) { - // if cb1 was on the right/left third of the view, - // shift focus that way - if (cb1 >= p1 + w1 * 0.66667) { - p2 += (w2 - w1) / 2; - } else if (cb1 <= p1 + w1 * 0.33333) { - p2 -= (w2 - w1) / 2; - } - } else { - print('cb1 out of bounds'); - } - } else { - p2 = p1 * s2 / s1; - } - p2 += (w2 - w1) / 2; - // print("For scale, jumping from $p1 to $p2"); - timeScrollController.jumpTo(p2); - // scrollToFocusedBeat(); - } - }); - yScaleUpdateListener = - _scaleUpdateListener(widget.notifyYScaleUpdate, (update) { - widget.yScaleNotifier.value = update.newScale; - }); - widget.notifyXScaleUpdate.addListener(xScaleUpdateListener); - widget.notifyYScaleUpdate.addListener(yScaleUpdateListener); widget.scrollToPart.addListener(scrollToPart); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -336,6 +248,8 @@ class _MusicScrollContainerState extends State static const int _autoScrollDelayDuration = 2500; + _transformationListener() {} + _timeScrollListener() { // _lastScrollEventSeen = DateTime.now(); // if ((_lastTimeScrollValue - timeScrollController.offset).abs() > 25) { @@ -370,7 +284,7 @@ class _MusicScrollContainerState extends State } _verticalScrollListener() { - widget.verticalScrollNotifier.value = verticalController.offset; + // widget.verticalScrollNotifier.value = verticalController.offset; // double maxScrollExtent = widget.focusedBeat.value != null // ? maxCanvasHeight // : max(10, overallCanvasHeight - widget.height); @@ -396,6 +310,7 @@ class _MusicScrollContainerState extends State @override void dispose() { animationController.dispose(); + transformationController.removeListener(_transformationListener); colorblockOpacityNotifier.dispose(); notationOpacityNotifier.dispose(); sectionScaleNotifier.dispose(); @@ -406,13 +321,9 @@ class _MusicScrollContainerState extends State colorboardPart.dispose(); focusedPart.dispose(); sectionColor.dispose(); - timeScrollController.removeListener(_timeScrollListener); - verticalController.removeListener(_verticalScrollListener); widget.scrollToCurrentBeat.removeListener(scrollToCurrentBeat); widget.scrollToPart.removeListener(scrollToPart); widget.centerCurrentSection.removeListener(_constrainToSectionBounds); - widget.notifyXScaleUpdate.removeListener(xScaleUpdateListener); - widget.notifyYScaleUpdate.removeListener(_constrainToSectionBounds); super.dispose(); } @@ -420,6 +331,8 @@ class _MusicScrollContainerState extends State bool _hasBuilt = false; DateTime _lastAutoScrollTime = DateTime(0); double prevWidth; + TransformationController get transformationController => + widget.transformationController; @override Widget build(BuildContext context) { @@ -489,80 +402,53 @@ class _MusicScrollContainerState extends State _prevPartId = widget.focusedPart?.id; _hasBuilt = true; - return CustomScrollView( - controller: verticalController, - scrollDirection: Axis.vertical, - slivers: [ - CustomSliverToBoxAdapter( - setVisibleRect: (rect) { - verticallyVisibleRect = rect; - }, - child: AnimatedContainer( - duration: animationDuration, - height: overallCanvasHeight, - child: CustomScrollView( - controller: timeScrollController, - scrollDirection: Axis.horizontal, - slivers: [ - CustomSliverToBoxAdapter( - setVisibleRect: (rect) { - horizontallyVisibleRect = rect; - }, - child: CustomPaint( -// key: Key("$overallCanvasWidth-$overallCanvasHeight"), - size: Size(overallCanvasWidth, overallCanvasHeight), - painter: MusicSystemPainter( - sectionScaleNotifier: sectionScaleNotifier, - score: widget.score, - section: widget.currentSection, - musicViewMode: widget.musicViewMode, - xScaleNotifier: widget.xScaleNotifier, - yScaleNotifier: widget.yScaleNotifier, - focusedMelodyId: widget.focusedMelody?.id, - staves: stavesNotifier, - partTopOffsets: partTopOffsets, - staffOffsets: staffOffsets, - colorGuideOpacityNotifier: - colorGuideOpacityNotifier, - colorblockOpacityNotifier: - colorblockOpacityNotifier, - notationOpacityNotifier: - notationOpacityNotifier, - colorboardNotesNotifier: - widget.colorboardNotesNotifier, - keyboardNotesNotifier: - widget.keyboardNotesNotifier, - bluetoothControllerPressedNotes: - widget.bluetoothControllerPressedNotes, - visibleRect: () => horizontallyVisibleRect, - verticallyVisibleRect: () => Rect.fromLTRB( - horizontallyVisibleRect.left, - horizontallyVisibleRect.top + - verticalController.offset, - horizontallyVisibleRect.right, - horizontallyVisibleRect.top + - verticalController.offset + - widget.height), - keyboardPart: keyboardPart, - colorboardPart: colorboardPart, - focusedPart: focusedPart, - tappedPart: widget.tappedPart, - sectionColor: sectionColor, - isCurrentScore: widget.isCurrentScore, - highlightedBeat: widget.highlightedBeat, - focusedBeat: widget.focusedBeat, - tappedBeat: widget.tappedBeat, - firstBeatOfSection: firstBeatOfSection, - renderPartNames: true, - isPreview: false, - systemsToRender: systemsToRender, - otherListenables: [verticalController])), - ) - ], - ))) - ]); + return InteractiveViewer( + // minScale: MusicScrollContainer.minScale, + // maxScale: MusicScrollContainer.maxScale, + child: CustomPaint( + // key: Key("$overallCanvasWidth-$overallCanvasHeight"), + size: Size(overallCanvasWidth, overallCanvasHeight), + painter: MusicSystemPainter( + sectionScaleNotifier: sectionScaleNotifier, + score: widget.score, + section: widget.currentSection, + musicViewMode: widget.musicViewMode, + xScaleNotifier: ValueNotifier(scale), + yScaleNotifier: ValueNotifier(scale), + focusedMelodyId: widget.focusedMelody?.id, + staves: stavesNotifier, + partTopOffsets: partTopOffsets, + staffOffsets: staffOffsets, + colorGuideOpacityNotifier: colorGuideOpacityNotifier, + colorblockOpacityNotifier: colorblockOpacityNotifier, + notationOpacityNotifier: notationOpacityNotifier, + colorboardNotesNotifier: widget.colorboardNotesNotifier, + keyboardNotesNotifier: widget.keyboardNotesNotifier, + bluetoothControllerPressedNotes: + widget.bluetoothControllerPressedNotes, + visibleRect: () => transformedRect, + verticallyVisibleRect: () => transformedRect, + keyboardPart: keyboardPart, + colorboardPart: colorboardPart, + focusedPart: focusedPart, + tappedPart: widget.tappedPart, + sectionColor: sectionColor, + isCurrentScore: widget.isCurrentScore, + highlightedBeat: widget.highlightedBeat, + focusedBeat: widget.focusedBeat, + tappedBeat: widget.tappedBeat, + firstBeatOfSection: firstBeatOfSection, + renderPartNames: true, + isPreview: false, + systemsToRender: systemsToRender, + otherListenables: [])), + ); } + Rect get transformedRect => MatrixUtils.transformRect( + transformationController.value, + Rect.fromLTRB(0, 0, widget.width / scale, widget.height / scale)); + scrollToFocusedBeat({ bool instant = false, }) { @@ -576,7 +462,7 @@ class _MusicScrollContainerState extends State } // double get secondSystemOffset => - // widget.width - MusicSystemPainter.calculateClefWidth(xScale); + // widget.width - MusicSystemPainter.calculateClefWidth(scale); // bool showOnSecondSystem(double animationPos) => // systemsToRender > 1 && animationPos > secondSystemOffset; @@ -608,35 +494,38 @@ class _MusicScrollContainerState extends State } bool isBeatOnScreen(double beat) { - double animationPos = _animationPos(currentBeat); - double currentPos = timeScrollController.position.pixels; - return animationPos >= currentPos && - animationPos < currentPos + visibleRect.width - targetClefWidth; + return true; + // double animationPos = _animationPos(currentBeat); + // double currentPos = timeScrollController.position.pixels; + // return animationPos >= currentPos && + // animationPos < currentPos + visibleRect.width - targetClefWidth; } void scrollToBeat(double currentBeat, {Duration duration = animationDuration, Curve curve = Curves.linear}) { + //TODO reimplement this! + return; // _lastBeatScrolledTo = currentBeat; double animationPos = _animationPos(currentBeat); // print( - // "scrollToBeat $currentBeat : $animationPos; s/t: ${widget.xScale}/$targetXScale"); + // "scrollToBeat $currentBeat : $animationPos; s/t: ${widget.scale}/$targetScale"); // final pixels = timeScrollController.position.pixels; // final offset = timeScrollController.offset; - if (_hasBuilt && - animationPos.notRoughlyEquals(timeScrollController.offset)) { - try { - animate() { - if (duration.inMilliseconds > 0) { - timeScrollController.animateTo(animationPos, - duration: duration, curve: curve); - } else { - timeScrollController.jumpTo(animationPos); - } - } - - animate(); - } catch (e) {} - } + // if (_hasBuilt && + // animationPos.notRoughlyEquals(timeScrollController.offset)) { + // try { + // animate() { + // if (duration.inMilliseconds > 0) { + // timeScrollController.animateTo(animationPos, + // duration: duration, curve: curve); + // } else { + // timeScrollController.jumpTo(animationPos); + // } + // } + + // animate(); + // } catch (e) {} + // } } void scrollToPart() { @@ -655,8 +544,8 @@ class _MusicScrollContainerState extends State // scrollToBottomMostPart(); // } // } else { - verticalController.animateTo(0 + currentBeatTargetSystemYOffset, - duration: animationDuration, curve: Curves.ease); + // verticalController.animateTo(0 + currentBeatTargetSystemYOffset, + // duration: animationDuration, curve: Curves.ease); // } } @@ -670,7 +559,7 @@ class _MusicScrollContainerState extends State int get staffCount => stavesNotifier.value.length; double get maxSystemVerticalPosition => max( 0, - (staffCount) * MusicSystemPainter.staffHeight * yScale - + (staffCount) * MusicSystemPainter.staffHeight * scale - widget.height + 100); void scrollToCurrentBeat() { @@ -699,7 +588,7 @@ class _MusicScrollContainerState extends State if (widget.isTwoFingerScaling) return; // print("_constrainToSectionBounds"); try { - double position = timeScrollController.position.pixels; + MatrixUtils.getAsTranslation(transformationController.value).dx; double sectionWidth = widget.currentSection.beatCount * targetBeatWidth; double visibleWidth = horizontallyVisibleRect.width; if (sectionCanBeCentered) { @@ -709,9 +598,9 @@ class _MusicScrollContainerState extends State (firstBeatOfSection + MusicSystemPainter.extraBeatsSpaceForClefs) * targetBeatWidth; final allowedMargin = visibleWidth * 0.2; - if (position < sectionStart - allowedMargin) { + if (dx < sectionStart - allowedMargin) { scrollToBeat(firstBeatOfSection); - } else if (position + visibleWidth > + } else if (dx + visibleWidth > sectionStart + sectionWidth + allowedMargin) { scrollToBeat(rightMostBeatConstrainedToSection); } @@ -745,7 +634,7 @@ class _MusicScrollContainerState extends State }); widget.staves.asMap().forEach((staffIndex, staff) { double staffPosition = - staffIndex * MusicSystemPainter.staffHeight * yScale; + staffIndex * MusicSystemPainter.staffHeight * scale; double initialStaffPosition = staffOffsets.value.putIfAbsent(staff.id, () => overallCanvasHeight); Animation staffAnimation; diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index 43cf7a2d..6cdeeced 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -140,6 +140,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { bool isBrowsingPartMelodies; bool isEditingSection; bool _isTwoFingerScaling = false; + TransformationController transformationController = + TransformationController(); // Offset _previousOffset = Offset.zero; bool _ignoreNextScale = false; @@ -169,33 +171,27 @@ class _MusicViewState extends State with TickerProviderStateMixin { bool get showViewOptions => widget.showViewOptions || forceShowViewOptions; - ValueNotifier xScaleNotifier, - yScaleNotifier, - verticalScrollingPosition; + // ValueNotifier xScaleNotifier, + // yScaleNotifier, + // verticalScrollingPosition; BSMethod scrollToFocusedBeat; - /// Always immediately updated; the return values of [xScale] and [yScale]. - ValueNotifier _targetedXScale, _targetedYScale; - double get targetedXScale => _targetedXScale.value; - set targetedXScale(double v) { - _targetedXScale.value = v; + /// Always immediately updated; the return values of [targetedScale] and [scale]. + ValueNotifier _targetedScale; + double get targetedScale => _targetedScale.value; + set targetedScale(double v) { + _targetedScale.value = v; widget.appSettings.musicScale = v; } - double get targetedYScale => _targetedYScale.value; - set targetedYScale(double v) { - _targetedYScale.value = v; - widget.appSettings.musicScale = v; - } - - /// Used to maintain a locking mechanism as we animate from [_xScale] to [targetedXScale] in the setter for [xScale]. + /// Used to maintain a locking mechanism as we animate from [_xScale] to [targetedScale] in the setter for [targetedScale]. DateTime _xScaleLock, _yScaleLock; List _xScaleAnimationControllers, _yScaleAnimationControllers; - /// Used to notify the [MusicScrollContainer] as we animate [_xScale] to [targetedXScale] in the setter for [xScale]. - BSValueMethod _xScaleUpdate, _yScaleUpdate; + /// Used to notify the [MusicScrollContainer] as we animate [_xScale] to [targetedScale] in the setter for [targetedScale]. + // BSValueMethod _xScaleUpdate, _yScaleUpdate; Map> _swipeTutorialsSeen; SwipeTutorial _currentSwipeTutorial; @@ -268,7 +264,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { controllers.add(scaleAnimationController); Animation animation; - // print("animating xScale to $value"); + // print("animating targetedScale to $value"); // print("Tween params: begin: $currentValue, end: $value"); animation = Tween(begin: currentValue(), end: value()) .animate(scaleAnimationController) @@ -343,7 +339,6 @@ class _MusicViewState extends State with TickerProviderStateMixin { @required double Function() currentValue, @required Function(double) applyAnimatedValue, @required List controllers, - @required BSValueMethod notifyUpdate, }) { if (value() == currentValue()) { return; @@ -363,7 +358,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { currentValue: currentValue, applyAnimatedValue: (v) { applyAnimatedValue(v); - notifyUpdate(ScaleUpdate(prevValue, v)); + // notifyUpdate(ScaleUpdate(prevValue, v)); prevValue = v; }, controllers: controllers, @@ -372,7 +367,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { _animateScaleAtomically( getLockTime: getLockTime, setLockTime: setLockTime, - notifyUpdate: notifyUpdate, + // notifyUpdate: notifyUpdate, value: value, currentValue: currentValue, applyAnimatedValue: applyAnimatedValue, @@ -405,31 +400,26 @@ class _MusicViewState extends State with TickerProviderStateMixin { } } - double get _xScale => xScaleNotifier.value; - - set _xScale(value) => xScaleNotifier.value = value; - - double get _yScale => yScaleNotifier.value; - - set _yScale(value) => yScaleNotifier.value = value; + double get dx => + MatrixUtils.getAsTranslation(transformationController.value).dx; + double get dy => + MatrixUtils.getAsTranslation(transformationController.value).dy; + double get scale => transformationController.value.getMaxScaleOnAxis(); + set scale(value) => transformationController.value.scale(value / scale); - double get xScale => targetedXScale; - - double get yScale => targetedYScale; - - set xScale(double value) { + _targetedScaleAnimationListener(double value) { value = max(0, min(maxScale, value)); - targetedXScale = value; + targetedScale = value; _animateScaleAtomically( - value: () => xScale, - currentValue: () => _xScale, - applyAnimatedValue: (value) => _xScale = value, + value: () => targetedScale, + currentValue: () => scale, + applyAnimatedValue: (value) => scale = value, controllers: _xScaleAnimationControllers, setLockTime: (it) { _xScaleLock = it; }, getLockTime: () => _xScaleLock, - notifyUpdate: _xScaleUpdate, + // notifyUpdate: _xScaleUpdate, ); // if (autoScroll) { // widget.scrollToCurrentBeat(); @@ -438,37 +428,37 @@ class _MusicViewState extends State with TickerProviderStateMixin { // } } - set rawXScale(double value) { - value = max(minScale, min(maxScale, value)); - final oldValue = _xScale; - targetedXScale = value; - _xScale = value; - _xScaleUpdate(ScaleUpdate(oldValue, value)); - } - - set yScale(double value) { - value = max(minScale, min(maxScale, value)); - targetedYScale = value; - _animateScaleAtomically( - value: () => yScale, - currentValue: () => _yScale, - applyAnimatedValue: (value) => _yScale = value, - controllers: _yScaleAnimationControllers, - setLockTime: (it) { - _yScaleLock = it; - }, - getLockTime: () => _yScaleLock, - notifyUpdate: _yScaleUpdate, - ); - } - - set rawYScale(double value) { - value = max(minScale, min(maxScale, value)); - final oldValue = _yScale; - targetedYScale = value; - _yScale = value; - _yScaleUpdate(ScaleUpdate(oldValue, value)); - } + // set rawXScale(double value) { + // value = max(minScale, min(maxScale, value)); + // final oldValue = _xScale; + // targetedScale = value; + // _xScale = value; + // // _xScaleUpdate(ScaleUpdate(oldValue, value)); + // } + + // set scale(double value) { + // value = max(minScale, min(maxScale, value)); + // targetedScale = value; + // _animateScaleAtomically( + // value: () => scale, + // currentValue: () => _yScale, + // applyAnimatedValue: (value) => _yScale = value, + // controllers: _yScaleAnimationControllers, + // setLockTime: (it) { + // _yScaleLock = it; + // }, + // getLockTime: () => _yScaleLock, + // // notifyUpdate: _yScaleUpdate, + // ); + // } + + // set rawYScale(double value) { + // value = max(minScale, min(maxScale, value)); + // final oldValue = _yScale; + // targetedScale = value; + // _yScale = value; + // _yScaleUpdate(ScaleUpdate(oldValue, value)); + // } @override void initState() { @@ -479,9 +469,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { tappedBeat = new ValueNotifier(null); tappedPart = new ValueNotifier(null); scrollToFocusedBeat = BSMethod(); - _targetedXScale = ValueNotifier(null); - _targetedYScale = ValueNotifier(null); - verticalScrollingPosition = new ValueNotifier(0); + _targetedScale = ValueNotifier(null); requestedScrollOffsetForScale = ValueNotifier(null); _swipeTutorialsSeen = { MusicViewMode.melody: [], @@ -491,8 +479,6 @@ class _MusicViewState extends State with TickerProviderStateMixin { _xScaleAnimationControllers = []; _yScaleAnimationControllers = []; centerCurrentSection = BSMethod(); - _xScaleUpdate = BSValueMethod(null); - _yScaleUpdate = BSValueMethod(null); centerCurrentSection = BSMethod(); scrollToPart = BSMethod(); @@ -501,8 +487,6 @@ class _MusicViewState extends State with TickerProviderStateMixin { isEditingSection = true; _aligned = true; _partAligned = false; - xScaleNotifier = ValueNotifier(null); - yScaleNotifier = ValueNotifier(null); widget.scrollToCurrentBeat.addListener(handleZoomAlign); } @@ -525,6 +509,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { highlightedBeat.dispose(); centerCurrentSection.dispose(); widget.scrollToCurrentBeat.removeListener(handleZoomAlign); + transformationController.dispose(); super.dispose(); } @@ -553,35 +538,33 @@ class _MusicViewState extends State with TickerProviderStateMixin { @override Widget build(context) { - if (_xScale == null) { + if (scale == null || targetedScale == null) { final musicScale = widget.appSettings.musicScale; if (musicScale == null) { if (context.isTablet) { - _xScale = 0.33; + scale = 0.33; } else { - _xScale = 0.22; + scale = 0.22; } } else { - _xScale = musicScale; + scale = musicScale; } - _yScale = _xScale; - targetedXScale = _xScale; - targetedYScale = _yScale; - } - if (targetedXScale != null && - (_xScale.notRoughlyEquals(targetedXScale) || - _yScale.notRoughlyEquals(targetedYScale))) { - xScale = xScale; - yScale = yScale; + targetedScale = scale; } + // if (targetedScale != null && + // (_xScale.notRoughlyEquals(targetedScale) || + // _yScale.notRoughlyEquals(targetedScale))) { + // targetedScale = targetedScale; + // scale = scale; + // } if (_partAligned && (widget.musicViewMode == MusicViewMode.part || widget.musicViewMode == MusicViewMode.melody || widget.musicViewMode == MusicViewMode.score)) { - if (xScale.notRoughlyEquals(partAlignedScale)) { + if (targetedScale.notRoughlyEquals(partAlignedScale)) { partAlignVertically(); } - } else if (_aligned && xScale.notRoughlyEquals(alignedScale)) { + } else if (_aligned && targetedScale.notRoughlyEquals(alignedScale)) { alignVertically(); } _previousSplitMode = widget.splitMode; @@ -694,6 +677,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { )), Expanded( child: GestureDetector( + behavior: HitTestBehavior.translucent, onVerticalDragUpdate: context.isPortrait ? (details) { if (ignoreDragEvents) return; @@ -976,7 +960,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { if (MyPlatform.isDebug && true) IgnorePointer( child: Container( - width: 2 * xScale * unscaledStandardBeatWidth, + width: 2 * targetedScale * unscaledStandardBeatWidth, height: widget.height, decoration: BoxDecoration( color: Colors.black12, @@ -1025,17 +1009,18 @@ class _MusicViewState extends State with TickerProviderStateMixin { getBeat(Offset position, {bool targeted = true}) { double systemHeight = MusicSystemPainter.calculateSystemHeight( - yScale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(yScale); + scale, widget.score.parts.length) + + MusicSystemPainter.calculateSystemPadding(scale); double systemXOffset = - ((position.dy + verticalScrollingPosition.value) / systemHeight) + ((position.dy /*+ verticalScrollingPosition.value*/) / systemHeight) .floor() * - (widget.width - MusicSystemPainter.calculateClefWidth(xScale)); + (widget.width - + MusicSystemPainter.calculateClefWidth(targetedScale)); int beat = ((position.dx + systemXOffset + horizontallyVisibleRect.left - - 2 * unscaledStandardBeatWidth * xScale) / - (unscaledStandardBeatWidth * (targeted ? xScale : _xScale))) + 2 * unscaledStandardBeatWidth * targetedScale) / + (unscaledStandardBeatWidth * (targeted ? targetedScale : scale))) .floor(); // print("beat=$beat"); beat = max(0, min(beat, widget.score.maxBeat)); @@ -1118,10 +1103,10 @@ class _MusicViewState extends State with TickerProviderStateMixin { widget.musicViewMode == MusicViewMode.score) && (focusedPartIsNotFirst || focusedMelodyIsNotFirst) && showViewOptions; - bool showMinimizeButton = xScale != minScale; - bool showExpandButton = xScale != alignedScale; + bool showMinimizeButton = targetedScale != minScale; + bool showExpandButton = targetedScale != alignedScale; bool showExpandPartButton = (!showExpandButton || !showMinimizeButton) && - xScale != partAlignedScale && + targetedScale != partAlignedScale && (widget.musicViewMode == MusicViewMode.part || widget.musicViewMode == MusicViewMode.score || widget.musicViewMode == MusicViewMode.melody); @@ -1154,8 +1139,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { pointerDown(Offset localPosition) { double systemHeight = MusicSystemPainter.calculateSystemHeight( - yScale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(yScale); + scale, widget.score.parts.length) + + MusicSystemPainter.calculateSystemPadding(scale); if (systemHeight < localPosition.dy) { if (autoScroll) { setState(() { @@ -1188,20 +1173,21 @@ class _MusicViewState extends State with TickerProviderStateMixin { int beat = getBeat(localPosition); print( - "pointerDown: ${localPosition} -> beat: $beat; x/t: $_xScale/$targetedXScale"); + "pointerDown: ${localPosition} -> beat: $beat; x/t: $scale/$targetedScale"); tappedBeat.value = beat; - double partIndexY = verticalScrollingPosition.value + localPosition.dy; - partIndexY -= MusicSystemPainter.calculateHarmonyHeight(yScale); + double partIndexY = /*verticalScrollingPosition.value +*/ localPosition + .dy; + partIndexY -= MusicSystemPainter.calculateHarmonyHeight(scale); if (widget.musicViewMode == MusicViewMode.score || - xScale < minScale * 2) { - partIndexY -= MusicSystemPainter.calculateSectionHeight(yScale); + targetedScale < minScale * 2) { + partIndexY -= MusicSystemPainter.calculateSectionHeight(scale); } while (partIndexY > systemHeight) { partIndexY -= systemHeight; } int partIndex = - (partIndexY / (yScale * MusicSystemPainter.staffHeight)).floor(); + (partIndexY / (scale * MusicSystemPainter.staffHeight)).floor(); print("partIndex=$partIndex"); print("mainPart=${mainPart?.midiName}"); if (!autoSort || widget.musicViewMode == MusicViewMode.section) { @@ -1242,7 +1228,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { } int beat = tappedBeat.value; print( - "onTapUp: ${details.localPosition} -> beat: $beat; x/t: $_xScale/$targetedXScale"); + "onTapUp: ${details.localPosition} -> beat: $beat; x/t: $scale/$targetedScale"); if (BeatScratchPlugin.playing && recordingMelody && highlightedBeat.value != beat) { @@ -1264,51 +1250,51 @@ class _MusicViewState extends State with TickerProviderStateMixin { pointerDown(details.localPosition); onLongPress(); }, - onScaleStart: (details) => setState(() { - if (_ignoreNextScale) { - return; - } - _aligned = false; - _partAligned = false; - _isTwoFingerScaling = true; - int beat = getBeat(details.focalPoint); - focusedBeat.value = beat; - tappedBeat.value = null; - tappedPart.value = null; - _startScale = xScale; - // _startVerticalScale = yScale; - }), - onScaleUpdate: (ScaleUpdateDetails details) { - if (_ignoreNextScale) { - return; - } - setState(() { - if (focusedBeat.value == null) { - int beat = getBeat(details.focalPoint); - focusedBeat.value = beat; - } - if (details.scale > 0) { - final target = _startScale * details.scale; - rawXScale = target; - _updateFocusedBeatValue(withDelayClear: false); - rawYScale = target; - } - // if (details.horizontalScale > 0) { - // final target = _startHorizontalScale * details.horizontalScale; - // rawXScale = target; - // } - // if (details.verticalScale > 0) { - // final target = _startVerticalScale * details.verticalScale; - // rawYScale = target; - // } - }); - }, - onScaleEnd: (ScaleEndDetails details) { - _ignoreNextScale = false; - _isTwoFingerScaling = false; - focusedBeat.value = null; - handleZoomAlign(); - }, + // onScaleStart: (details) => setState(() { + // if (_ignoreNextScale) { + // return; + // } + // _aligned = false; + // _partAligned = false; + // _isTwoFingerScaling = true; + // int beat = getBeat(details.focalPoint); + // focusedBeat.value = beat; + // tappedBeat.value = null; + // tappedPart.value = null; + // _startScale = targetedScale; + // // _startVerticalScale = scale; + // }), + // onScaleUpdate: (ScaleUpdateDetails details) { + // if (_ignoreNextScale) { + // return; + // } + // setState(() { + // if (focusedBeat.value == null) { + // int beat = getBeat(details.focalPoint); + // focusedBeat.value = beat; + // } + // if (details.scale > 0) { + // final target = _startScale * details.scale; + // rawXScale = target; + // _updateFocusedBeatValue(withDelayClear: false); + // rawYScale = target; + // } + // // if (details.horizontalScale > 0) { + // // final target = _startHorizontalScale * details.horizontalScale; + // // rawXScale = target; + // // } + // // if (details.verticalScale > 0) { + // // final target = _startVerticalScale * details.verticalScale; + // // rawYScale = target; + // // } + // }); + // }, + // onScaleEnd: (ScaleEndDetails details) { + // _ignoreNextScale = false; + // _isTwoFingerScaling = false; + // focusedBeat.value = null; + // handleZoomAlign(); + // }, child: Stack(children: [ MusicScrollContainer( musicViewMode: widget.musicViewMode, @@ -1321,8 +1307,6 @@ class _MusicViewState extends State with TickerProviderStateMixin { widget.bluetoothControllerPressedNotes, focusedMelody: widget.melody, renderingMode: widget.renderingMode, - xScale: _xScale, - yScale: _xScale, staves: staves, focusedPart: mainPart, keyboardPart: widget.keyboardPart, @@ -1334,20 +1318,15 @@ class _MusicViewState extends State with TickerProviderStateMixin { focusedBeat: focusedBeat, tappedBeat: tappedBeat, tappedPart: tappedPart, - verticalScrollNotifier: verticalScrollingPosition, requestedScrollOffsetForScale: requestedScrollOffsetForScale, - targetXScaleNotifier: _targetedXScale, - targetYScaleNotifier: _targetedYScale, + targetScaleNotifier: _targetedScale, scrollToFocusedBeat: scrollToFocusedBeat, isTwoFingerScaling: _isTwoFingerScaling, scrollToCurrentBeat: widget.scrollToCurrentBeat, scrollToPart: scrollToPart, centerCurrentSection: centerCurrentSection, appSettings: widget.appSettings, - notifyXScaleUpdate: _xScaleUpdate, - notifyYScaleUpdate: _yScaleUpdate, - xScaleNotifier: xScaleNotifier, - yScaleNotifier: yScaleNotifier, + transformationController: transformationController, showingSectionList: widget.showingSectionList), Row(children: [ Expanded(child: SizedBox()), @@ -1395,16 +1374,17 @@ class _MusicViewState extends State with TickerProviderStateMixin { } handleZoomAlign() { + return; if (autoZoomAlign) { - final double x = targetedXScale, - // y = targetedYScale, + final double x = targetedScale, + // y = targetedScale, w = widget.width, numberOfBeatsOnScreen = w / (x * unscaledStandardBeatWidth), targetNumberOfBeatsOnScreen = numberOfBeatsOnScreen.roundToDouble(), newXScale = w / (targetNumberOfBeatsOnScreen * unscaledStandardBeatWidth); - xScale = newXScale; - yScale = newXScale; + targetedScale = newXScale; + // scale = newXScale; } } @@ -1532,57 +1512,61 @@ class _MusicViewState extends State with TickerProviderStateMixin { Widget zoomButton() { final zoomIncrement = 1.03; - final bigDecrementIcon = (xScale > alignedScale || yScale > alignedScale) - ? Icons.expand - : FontAwesomeIcons.compressArrowsAlt; - final bigDecrementAction = (xScale > alignedScale || yScale > alignedScale) - ? () { - setState(() { - _aligned = true; - _partAligned = false; - _preButtonScale(); - alignVertically(); - Future.delayed(animationDuration, () { - widget.scrollToCurrentBeat(); - }); - }); - } - : (xScale > minScale || yScale > minScale) + final bigDecrementIcon = + (targetedScale > alignedScale || scale > alignedScale) + ? Icons.expand + : FontAwesomeIcons.compressArrowsAlt; + final bigDecrementAction = + (targetedScale > alignedScale || scale > alignedScale) ? () { setState(() { - minimize(); + _aligned = true; + _partAligned = false; + _preButtonScale(); + alignVertically(); + Future.delayed(animationDuration, () { + widget.scrollToCurrentBeat(); + }); }); } - : null; - final bigIncrementIcon = (xScale >= alignedScale || yScale >= alignedScale) - ? FontAwesomeIcons.expandArrowsAlt - : Icons.expand; - final bigIncrementAction = (xScale < alignedScale || yScale < alignedScale) - ? () { - setState(() { - _aligned = true; - _partAligned = false; - _preButtonScale(); - alignVertically(); - _lineUpAfterSizeChange(); - Future.delayed(animationDuration, () { - widget.scrollToCurrentBeat(); - }); - }); - } - : (xScale < partAlignedScale || yScale < partAlignedScale) && - !xScale.roughlyEquals(partAlignedScale) && - widget.musicViewMode != MusicViewMode.section + : (targetedScale > minScale || scale > minScale) + ? () { + setState(() { + minimize(); + }); + } + : null; + final bigIncrementIcon = + (targetedScale >= alignedScale || scale >= alignedScale) + ? FontAwesomeIcons.expandArrowsAlt + : Icons.expand; + final bigIncrementAction = + (targetedScale < alignedScale || scale < alignedScale) ? () { setState(() { _aligned = true; - _partAligned = true; + _partAligned = false; _preButtonScale(); - partAlignVertically(); + alignVertically(); _lineUpAfterSizeChange(); + Future.delayed(animationDuration, () { + widget.scrollToCurrentBeat(); + }); }); } - : null; + : (targetedScale < partAlignedScale || scale < partAlignedScale) && + !targetedScale.roughlyEquals(partAlignedScale) && + widget.musicViewMode != MusicViewMode.section + ? () { + setState(() { + _aligned = true; + _partAligned = true; + _preButtonScale(); + partAlignVertically(); + _lineUpAfterSizeChange(); + }); + } + : null; return Container( color: Colors.black12, padding: EdgeInsets.zero, @@ -1612,7 +1596,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { musicForegroundColor.withOpacity(0.54)))), Transform.translate( offset: Offset(2, 20), - child: Text("${(xScale * 100).toStringAsFixed(0)}%", + child: Text( + "${(targetedScale * 100).toStringAsFixed(0)}%", style: TextStyle( fontWeight: FontWeight.w800, fontSize: 12, @@ -1634,27 +1619,25 @@ class _MusicViewState extends State with TickerProviderStateMixin { incrementIcon: Icons.zoom_in, decrementIcon: Icons.zoom_out, incrementDistance: 1, - onIncrement: (xScale < maxScale || yScale < maxScale) + onIncrement: (targetedScale < maxScale || scale < maxScale) ? () { _ignoreNextScale = true; _aligned = false; _partAligned = false; setState(() { - rawXScale = min(maxScale, (xScale) * zoomIncrement); - rawYScale = min(maxScale, (yScale) * zoomIncrement); - // print("zoomIn done; xScale=$targetXScale, yScale=$targetYScale"); + scale = min(maxScale, (targetedScale) * zoomIncrement); + // print("zoomIn done; targetedScale=$targetXScale, scale=$targetYScale"); }); } : null, - onDecrement: (xScale > minScale || yScale > minScale) + onDecrement: (targetedScale > minScale || scale > minScale) ? () { _ignoreNextScale = true; _aligned = false; _partAligned = false; setState(() { - rawXScale = max(minScale, xScale / zoomIncrement); - rawYScale = max(minScale, yScale / zoomIncrement); - // print("zoomOut done; xScale=$xScale, yScale=$yScale"); + scale = max(minScale, targetedScale / zoomIncrement); + // print("zoomOut done; targetedScale=$targetedScale, scale=$scale"); }); } : null, @@ -1683,8 +1666,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { _aligned = false; _partAligned = false; _preButtonScale(); - xScale = minScale; - yScale = minScale; + targetedScale = minScale; + // scale = minScale; _lineUpAfterSizeChange(); } @@ -1803,18 +1786,18 @@ class _MusicViewState extends State with TickerProviderStateMixin { alignVertically() { final scale = alignedScale; - xScale = scale; - yScale = scale; + targetedScale = scale; + // scale = scale; } partAlignVertically() { final scale = partAlignedScale; - xScale = scale; - yScale = scale; + targetedScale = scale; + // scale = scale; } double get sectionsHeight => widget.musicViewMode == MusicViewMode.score || - xScale < 2 * MusicScrollContainer.minScale + targetedScale < 2 * MusicScrollContainer.minScale ? 30 : 0; diff --git a/lib/ui_models.dart b/lib/ui_models.dart index a358769e..a0b273d3 100644 --- a/lib/ui_models.dart +++ b/lib/ui_models.dart @@ -17,7 +17,5 @@ enum ScrollingMode { sideScroll, pitch, roll } enum RenderingMode { notation, colorblock } const int animationMultiplier = 1; -const Duration animationDuration = - Duration(milliseconds: (kIsWeb || true ? 500 : 300) * animationMultiplier); -const Duration slowAnimationDuration = - Duration(milliseconds: (kIsWeb || true ? 800 : 400) * animationMultiplier); +const Duration animationDuration = Duration(milliseconds: 300); +const Duration slowAnimationDuration = Duration(milliseconds: 400); diff --git a/pubspec.lock b/pubspec.lock index 996e7025..4ed9e3ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" base_x: dependency: "direct main" description: @@ -42,7 +42,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -170,9 +170,9 @@ packages: flutter_reorderable_list: dependency: "direct main" description: - name: flutter_reorderable_list - url: "https://pub.dartlang.org" - source: hosted + path: "../flutter_reorderable_list" + relative: true + source: path version: "1.1.0" flutter_test: dependency: "direct dev" @@ -225,7 +225,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + matrix4_transform: + dependency: "direct main" + description: + name: matrix4_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" meta: dependency: transitive description: @@ -247,6 +254,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.3" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" package_info_plus: dependency: "direct main" description: @@ -380,6 +394,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2" quiver: dependency: "direct main" description: @@ -496,7 +517,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.3" typed_data: dependency: transitive description: @@ -559,7 +580,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" webview_flutter: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2ee00dab..cba02d74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,8 @@ dependencies: recase: ^3.0.0 # vibration: ^1.7.2 share: ^0.6.5+4 - flutter_reorderable_list: ^1.1.0 + flutter_reorderable_list: # ^1.1.0 + path: ../flutter_reorderable_list url_launcher: ^6.0.3 implicitly_animated_reorderable_list: ^0.4.1 flutter_keyboard_visibility: ^2.0.0 @@ -44,6 +45,8 @@ dependencies: flutter_midi_command: #^0.3.0 path: ../FlutterMidiCommand dcache: ^0.4.0 + provider: ^6.0.2 + matrix4_transform: ^2.0.1 dev_dependencies: flutter_test: From 1f576e885137f33587bea4475beec54560e51225 Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Sun, 2 Jan 2022 21:24:54 -0500 Subject: [PATCH 02/25] progress. need to update to use minScale=1 to use InteractiveViewer more as intended, and base the scale from 1-100% zoom directly --- lib/music_view/music_scroll_container.dart | 51 ++++++++++++++-------- lib/music_view/music_view.dart | 28 +++++++++--- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index 39c0d6d9..236e7e5d 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -16,7 +16,7 @@ import '../util/util.dart'; import 'music_system_painter.dart'; class MusicScrollContainer extends StatefulWidget { - static const double minScale = 0.09; + static const double minScale = 0.04; static const double maxScale = 1.0; final MusicViewMode musicViewMode; @@ -39,6 +39,7 @@ class MusicScrollContainer extends StatefulWidget { final ValueNotifier tappedPart; final ValueNotifier requestedScrollOffsetForScale; final TransformationController transformationController; + final ValueNotifier scaleUpdateNotifier; final ValueNotifier targetScaleNotifier; final BSMethod scrollToFocusedBeat; final bool showingSectionList; @@ -73,6 +74,7 @@ class MusicScrollContainer extends StatefulWidget { this.centerCurrentSection, this.appSettings, this.transformationController, + this.scaleUpdateNotifier, this.scrollToPart, this.showingSectionList}) : super(key: key); @@ -115,6 +117,10 @@ class _MusicScrollContainerState extends State int get numberOfBeats => /*isViewingSection ? widget.currentSection.harmony.beatCount :*/ widget.score.beatCount; + TransformationController get transformationController => + widget.transformationController; + ValueNotifier get scaleUpdateNotifier => + widget.scaleUpdateNotifier; double get dx => MatrixUtils.getAsTranslation(transformationController.value).dx; double get dy => @@ -130,16 +136,15 @@ class _MusicScrollContainerState extends State double get canvasHeightMagic => 1.3 - 0.3 * (widget.staves.length) / 5; - double get sectionsHeight => widget.musicViewMode == MusicViewMode.score || - scale < 2 * MusicScrollContainer.minScale || - !widget.showingSectionList - ? 30 - : 0; + bool get showSectionNames => + widget.musicViewMode == MusicViewMode.score || + scale < 2 * MusicScrollContainer.minScale || + !widget.showingSectionList; + double get sectionsHeight => showSectionNames ? 30 : 0; - double get systemHeight => (((widget.staves.length + 0.5) * - (MusicSystemPainter.staffHeight) * - scale) + - sectionsHeight * 2); + double get systemHeight => + (((widget.staves.length + 0.5) * (MusicSystemPainter.staffHeight)) + + sectionsHeight * 2); double get systemRenderAreaWidth => widget.width - MusicSystemPainter.calculateClefWidth(scale); @@ -331,8 +336,6 @@ class _MusicScrollContainerState extends State bool _hasBuilt = false; DateTime _lastAutoScrollTime = DateTime(0); double prevWidth; - TransformationController get transformationController => - widget.transformationController; @override Widget build(BuildContext context) { @@ -401,13 +404,23 @@ class _MusicScrollContainerState extends State _prevSectionId = widget.currentSection.id; _prevPartId = widget.focusedPart?.id; _hasBuilt = true; - + print("InteractiveViewer overallCanvasHeight=$overallCanvasHeight"); return InteractiveViewer( - // minScale: MusicScrollContainer.minScale, - // maxScale: MusicScrollContainer.maxScale, + minScale: MusicScrollContainer.minScale, + maxScale: MusicScrollContainer.maxScale, + boundaryMargin: EdgeInsets.symmetric( + horizontal: widget.width / scale, vertical: widget.height / scale), + onInteractionUpdate: (ScaleUpdateDetails details) { + // scaleUpdateNotifier.value = details; + print( + "onInteractionUpdate: total scale = ${scale}, focal=${details.focalPoint}"); // print the scale here + // print("matrix=${transformationController.value}"); + }, + transformationController: transformationController, child: CustomPaint( // key: Key("$overallCanvasWidth-$overallCanvasHeight"), - size: Size(overallCanvasWidth, overallCanvasHeight), + size: Size( + overallCanvasWidth / scale, 100 * overallCanvasHeight / scale), painter: MusicSystemPainter( sectionScaleNotifier: sectionScaleNotifier, score: widget.score, @@ -441,13 +454,13 @@ class _MusicScrollContainerState extends State renderPartNames: true, isPreview: false, systemsToRender: systemsToRender, - otherListenables: [])), + otherListenables: [transformationController])), ); } - Rect get transformedRect => MatrixUtils.transformRect( + Rect get transformedRect => MatrixUtils.inverseTransformRect( transformationController.value, - Rect.fromLTRB(0, 0, widget.width / scale, widget.height / scale)); + Rect.fromLTRB(0, 0, widget.width, widget.height)); scrollToFocusedBeat({ bool instant = false, diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index 6cdeeced..a6a5cf62 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -140,8 +140,9 @@ class _MusicViewState extends State with TickerProviderStateMixin { bool isBrowsingPartMelodies; bool isEditingSection; bool _isTwoFingerScaling = false; - TransformationController transformationController = - TransformationController(); + TransformationController transformationController; + ValueNotifier scaleUpdateNotifier = + ValueNotifier(ScaleUpdateDetails()); // Offset _previousOffset = Offset.zero; bool _ignoreNextScale = false; @@ -405,7 +406,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { double get dy => MatrixUtils.getAsTranslation(transformationController.value).dy; double get scale => transformationController.value.getMaxScaleOnAxis(); - set scale(value) => transformationController.value.scale(value / scale); + set scale(value) => + null; //transformationController.value.scale(value / scale); _targetedScaleAnimationListener(double value) { value = max(0, min(maxScale, value)); @@ -464,6 +466,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { void initState() { super.initState(); _disposed = false; + transformationController = TransformationController() + ..addListener(_onTransformationChange); highlightedBeat = new ValueNotifier(null); focusedBeat = new ValueNotifier(null); tappedBeat = new ValueNotifier(null); @@ -509,10 +513,22 @@ class _MusicViewState extends State with TickerProviderStateMixin { highlightedBeat.dispose(); centerCurrentSection.dispose(); widget.scrollToCurrentBeat.removeListener(handleZoomAlign); - transformationController.dispose(); + transformationController + ..removeListener(_onTransformationChange) + ..dispose(); super.dispose(); } + scaleText(double scale) => "${(scale * 100).toStringAsFixed(0)}%"; + + double _preTransformationScale = 0; + _onTransformationChange() { + if (scaleText(scale) != scaleText(_preTransformationScale)) { + setState(() {}); + } + _preTransformationScale = scale; + } + makeFullSize() { if (widget.splitMode == SplitMode.half) { widget.toggleSplitMode(); @@ -1327,6 +1343,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { centerCurrentSection: centerCurrentSection, appSettings: widget.appSettings, transformationController: transformationController, + scaleUpdateNotifier: scaleUpdateNotifier, showingSectionList: widget.showingSectionList), Row(children: [ Expanded(child: SizedBox()), @@ -1596,8 +1613,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { musicForegroundColor.withOpacity(0.54)))), Transform.translate( offset: Offset(2, 20), - child: Text( - "${(targetedScale * 100).toStringAsFixed(0)}%", + child: Text(scaleText(scale), style: TextStyle( fontWeight: FontWeight.w800, fontSize: 12, From a1be43bcf9d590de069c3d4496fb24649540aaff Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Sun, 9 Jan 2022 18:23:13 -0500 Subject: [PATCH 03/25] big progress --- lib/music_preview/preview_renderer.dart | 18 ++-- lib/music_preview/score_preview.dart | 4 +- lib/music_view/music_scroll_container.dart | 70 ++++++------- lib/music_view/music_system_painter.dart | 114 ++++++++++----------- lib/music_view/music_view.dart | 23 ++--- lib/storage/score_picker_preview.dart | 4 +- lib/util/music_notation_theory.dart | 7 +- 7 files changed, 113 insertions(+), 127 deletions(-) diff --git a/lib/music_preview/preview_renderer.dart b/lib/music_preview/preview_renderer.dart index 62b67456..3732b17d 100644 --- a/lib/music_preview/preview_renderer.dart +++ b/lib/music_preview/preview_renderer.dart @@ -38,18 +38,17 @@ class MusicPreviewRenderer { final parts = score.parts; final staves = parts.map((part) => PartStaff(part)).toList(growable: false); final partTopOffsets = Map.fromIterable(parts.asMap().keys, - key: (index) => parts[index].id, - value: (index) => index * scale * MusicSystemPainter.staffHeight); + key: (index) => parts[index].id, value: (index) => index * staffHeight); final staffOffsets = Map.fromIterable(staves.asMap().keys, key: (index) => staves[index].id, - value: (index) => index * scale * MusicSystemPainter.staffHeight); + value: (index) => index * staffHeight); return MusicSystemPainter( sectionScaleNotifier: ValueNotifier(renderSections ? 1 : 0), score: score, section: score.sections.first, musicViewMode: musicViewMode, - xScaleNotifier: ValueNotifier(scale), - yScaleNotifier: ValueNotifier(scale), + transformationController: TransformationController()..value.scale(scale), + rescale: true, staves: ValueNotifier(staves), partTopOffsets: ValueNotifier(partTopOffsets), staffOffsets: ValueNotifier(staffOffsets), @@ -60,8 +59,9 @@ class MusicPreviewRenderer { AppSettings.globalRenderingMode == RenderingMode.notation ? 1 : 0), colorboardNotesNotifier: ValueNotifier([]), keyboardNotesNotifier: ValueNotifier([]), - visibleRect: () => Rect.fromLTRB(0, 0, width, height), - verticallyVisibleRect: () => Rect.fromLTRB(0, 0, width, height), + visibleRect: () => Rect.fromLTRB(0, 0, width / scale, height / scale), + verticallyVisibleRect: () => + Rect.fromLTRB(0, 0, width / scale, height / scale), keyboardPart: ValueNotifier(null), colorboardPart: ValueNotifier(null), focusedPart: ValueNotifier(null), @@ -76,9 +76,7 @@ class MusicPreviewRenderer { } double get maxWidth => - (MusicSystemPainter.extraBeatsSpaceForClefs + score.beatCount) * - unscaledStandardBeatWidth * - scale; + (extraBeatsSpaceForClefs + score.beatCount) * beatWidth * scale; double get actualWidth => min(maxWidth, width); Future get renderedScoreImage async { if (height < 1 || width < 1) { diff --git a/lib/music_preview/score_preview.dart b/lib/music_preview/score_preview.dart index b0bf4306..c3d1c5e2 100644 --- a/lib/music_preview/score_preview.dart +++ b/lib/music_preview/score_preview.dart @@ -68,8 +68,8 @@ class _ScorePreviewState extends State { : thumbnailB = value; double get maxWidth => - (MusicSystemPainter.extraBeatsSpaceForClefs + widget.score.beatCount) * - unscaledStandardBeatWidth * + (extraBeatsSpaceForClefs + widget.score.beatCount) * + beatWidth * widget.scale; double get actualWidth => min(maxWidth, widget.width); diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index 236e7e5d..f1435f94 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:beatscratch_flutter_redux/settings/settings.dart'; +import 'package:beatscratch_flutter_redux/widget/my_platform.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -17,7 +18,7 @@ import 'music_system_painter.dart'; class MusicScrollContainer extends StatefulWidget { static const double minScale = 0.04; - static const double maxScale = 1.0; + static const double maxScale = 1; final MusicViewMode musicViewMode; final Score score; @@ -127,12 +128,11 @@ class _MusicScrollContainerState extends State MatrixUtils.getAsTranslation(transformationController.value).dy; double get scale => transformationController.value.getMaxScaleOnAxis(); - double get standardBeatWidth => unscaledStandardBeatWidth; - double get scaledStandardBeatWidth => unscaledStandardBeatWidth * scale; + double get scaledStandardBeatWidth => beatWidth * scale; double get targetScale => widget.targetScaleNotifier.value; - double get targetBeatWidth => unscaledStandardBeatWidth; + double get targetBeatWidth => beatWidth; double get canvasHeightMagic => 1.3 - 0.3 * (widget.staves.length) / 5; @@ -143,18 +143,15 @@ class _MusicScrollContainerState extends State double get sectionsHeight => showSectionNames ? 30 : 0; double get systemHeight => - (((widget.staves.length + 0.5) * (MusicSystemPainter.staffHeight)) + - sectionsHeight * 2); + (((widget.staves.length + 0.5) * (staffHeight)) + sectionsHeight * 2); - double get systemRenderAreaWidth => - widget.width - MusicSystemPainter.calculateClefWidth(scale); - double get beatsOnScreenPerSystem => - systemRenderAreaWidth / standardBeatWidth; + double get systemRenderAreaWidth => max(0, widget.width - clefWidth); + double get beatsOnScreenPerSystem => systemRenderAreaWidth / beatWidth; int get maxSystemsNeeded => - (widget.score.beatCount / beatsOnScreenPerSystem).ceil(); + (widget.score.beatCount / max(0.1, beatsOnScreenPerSystem)).ceil(); int get maxSupportedSystems => widget.appSettings.systemsToRender < 1 - ? 999999999999 + ? 9999999 : widget.appSettings.systemsToRender; int get systemsToRender => min(maxSystemsNeeded, maxSupportedSystems); @@ -185,11 +182,9 @@ class _MusicScrollContainerState extends State _extraHeightForScrolling)); double get overallCanvasWidth => - (numberOfBeats + MusicSystemPainter.extraBeatsSpaceForClefs) * - targetBeatWidth; + (numberOfBeats + extraBeatsSpaceForClefs) * targetBeatWidth; double get overallCanvasHeight => systemsToRender * systemHeight; - double get targetClefWidth => - MusicSystemPainter.extraBeatsSpaceForClefs * targetBeatWidth; + double get targetClefWidth => extraBeatsSpaceForClefs * targetBeatWidth; double get sectionWidth => widget.currentSection.beatCount * targetBeatWidth; @@ -337,6 +332,9 @@ class _MusicScrollContainerState extends State DateTime _lastAutoScrollTime = DateTime(0); double prevWidth; + get minScale => MusicScrollContainer.minScale; + get maxScale => MusicScrollContainer.maxScale; + @override Widget build(BuildContext context) { _animateOpacitiesAndScale(); @@ -404,30 +402,35 @@ class _MusicScrollContainerState extends State _prevSectionId = widget.currentSection.id; _prevPartId = widget.focusedPart?.id; _hasBuilt = true; - print("InteractiveViewer overallCanvasHeight=$overallCanvasHeight"); + print( + "InteractiveViewer overallCanvasHeight=$overallCanvasHeight, systemsToRender=$systemsToRender, systemHeight=$systemHeight"); return InteractiveViewer( - minScale: MusicScrollContainer.minScale, - maxScale: MusicScrollContainer.maxScale, - boundaryMargin: EdgeInsets.symmetric( - horizontal: widget.width / scale, vertical: widget.height / scale), + minScale: minScale, + maxScale: maxScale, + boundaryMargin: EdgeInsets.only( + bottom: widget.width / minScale, right: widget.height / scale), + // boundaryMargin: EdgeInsets.symmetric( + // horizontal: widget.width / scale, vertical: widget.height / scale), onInteractionUpdate: (ScaleUpdateDetails details) { // scaleUpdateNotifier.value = details; + if (!MyPlatform.isDebug) return; print( "onInteractionUpdate: total scale = ${scale}, focal=${details.focalPoint}"); // print the scale here // print("matrix=${transformationController.value}"); + print("transformedRect=$transformedRect"); + print("dims=$overallCanvasWidth x $overallCanvasHeight"); }, + constrained: false, transformationController: transformationController, child: CustomPaint( // key: Key("$overallCanvasWidth-$overallCanvasHeight"), - size: Size( - overallCanvasWidth / scale, 100 * overallCanvasHeight / scale), + size: Size(overallCanvasWidth, overallCanvasHeight), painter: MusicSystemPainter( sectionScaleNotifier: sectionScaleNotifier, score: widget.score, section: widget.currentSection, musicViewMode: widget.musicViewMode, - xScaleNotifier: ValueNotifier(scale), - yScaleNotifier: ValueNotifier(scale), + transformationController: transformationController, focusedMelodyId: widget.focusedMelody?.id, staves: stavesNotifier, partTopOffsets: partTopOffsets, @@ -454,7 +457,7 @@ class _MusicScrollContainerState extends State renderPartNames: true, isPreview: false, systemsToRender: systemsToRender, - otherListenables: [transformationController])), + otherListenables: [])), ); } @@ -475,7 +478,7 @@ class _MusicScrollContainerState extends State } // double get secondSystemOffset => - // widget.width - MusicSystemPainter.calculateClefWidth(scale); + // widget.width - clefWidth; // bool showOnSecondSystem(double animationPos) => // systemsToRender > 1 && animationPos > secondSystemOffset; @@ -570,11 +573,8 @@ class _MusicScrollContainerState extends State bool get sectionCanBeCentered => sectionWidth + targetClefWidth <= widget.width; int get staffCount => stavesNotifier.value.length; - double get maxSystemVerticalPosition => max( - 0, - (staffCount) * MusicSystemPainter.staffHeight * scale - - widget.height + - 100); + double get maxSystemVerticalPosition => + max(0, (staffCount) * staffHeight * scale - widget.height + 100); void scrollToCurrentBeat() { if (sectionCanBeCentered) { _constrainToSectionBounds(); @@ -608,8 +608,7 @@ class _MusicScrollContainerState extends State scrollToBeat(firstBeatOfSection - (marginBeatsForSection / 2)); } else { double sectionStart = - (firstBeatOfSection + MusicSystemPainter.extraBeatsSpaceForClefs) * - targetBeatWidth; + (firstBeatOfSection + extraBeatsSpaceForClefs) * targetBeatWidth; final allowedMargin = visibleWidth * 0.2; if (dx < sectionStart - allowedMargin) { scrollToBeat(firstBeatOfSection); @@ -646,8 +645,7 @@ class _MusicScrollContainerState extends State }); }); widget.staves.asMap().forEach((staffIndex, staff) { - double staffPosition = - staffIndex * MusicSystemPainter.staffHeight * scale; + double staffPosition = staffIndex * staffHeight; double initialStaffPosition = staffOffsets.value.putIfAbsent(staff.id, () => overallCanvasHeight); Animation staffAnimation; diff --git a/lib/music_view/music_system_painter.dart b/lib/music_view/music_system_painter.dart index 0edba6d3..651ddec7 100644 --- a/lib/music_view/music_system_painter.dart +++ b/lib/music_view/music_system_painter.dart @@ -23,15 +23,13 @@ import '../util/music_theory.dart'; // Top level for rendering of music to a Canvas. class MusicSystemPainter extends CustomPainter { - static const double extraBeatsSpaceForClefs = 2; - static const double staffHeight = 500; - Paint _tickPaint = Paint()..style = PaintingStyle.fill; final String focusedMelodyId; final Score score; final Section section; - final ValueNotifier xScaleNotifier, yScaleNotifier; + final TransformationController transformationController; + final bool rescale; final Rect Function() visibleRect; final Rect Function() verticallyVisibleRect; final MusicViewMode musicViewMode; @@ -54,9 +52,8 @@ class MusicSystemPainter extends CustomPainter { final double firstBeatOfSection; final int systemsToRender; - double get xScale => xScaleNotifier.value; - - double get yScale => yScaleNotifier.value; + double get xScale => 1; + double get yScale => 1; Melody get focusedMelody => score.parts .expand((p) => p.melodies) @@ -65,10 +62,7 @@ class MusicSystemPainter extends CustomPainter { int get numberOfBeats => /*isViewingSection ? section.harmony.beatCount :*/ score .beatCount; - double get standardBeatWidth => calculateStandardBeatWidth(xScale); - double get standardClefWidth => calculateClefWidth(xScale); - - double get width => standardBeatWidth * numberOfBeats; + double get standardClefWidth => clefWidth; int get colorGuideAlpha => (255 * colorGuideOpacityNotifier.value).toInt(); @@ -94,8 +88,8 @@ class MusicSystemPainter extends CustomPainter { this.bluetoothControllerPressedNotes, this.score, this.section, - this.xScaleNotifier, - this.yScaleNotifier, + this.transformationController, + this.rescale = false, this.visibleRect, this.verticallyVisibleRect, this.focusedMelodyId, @@ -121,31 +115,26 @@ class MusicSystemPainter extends CustomPainter { tappedBeat, BeatScratchPlugin.pressedMidiControllerNotes, BeatScratchPlugin.currentBeat, - xScaleNotifier, - yScaleNotifier, + transformationController, if (otherListenables != null) ...otherListenables ])) { _tickPaint.color = musicForegroundColor; _tickPaint.strokeWidth = 2.0; } - static double calculateHarmonyHeight(double yScale) => min(100, 30 * yScale); - static double calculateSectionHeight(double yScale) => - max(22, calculateHarmonyHeight(yScale)); - static double calculateMelodyHeight(double yScale) => staffHeight * yScale; - static double calculateSystemHeight(double yScale, int partCount) => - calculateSectionHeight(yScale) + - calculateHarmonyHeight(yScale) + - (calculateMelodyHeight(yScale) * partCount); - static double calculateSystemPadding(double yScale) => - calculateMelodyHeight(yScale) * 0.5; - static double calculateStandardBeatWidth(double xScale) => - unscaledStandardBeatWidth * xScale; - static double calculateClefWidth(double xScale) => - calculateStandardBeatWidth(xScale) * 2; - - double get harmonyHeight => calculateHarmonyHeight(yScale); - double get idealSectionHeight => max(22, harmonyHeight); + static double calculateHarmonyHeight(double scale) => 10 / scale; + static double calculateOldHarmonyHeight(double scale) => min(100, 30 * scale); + static double calculateSectionHeight(double scale) => + calculateHarmonyHeight(scale); + static double calculateSystemHeight(double scale, int partCount) => + calculateSectionHeight(scale) + + calculateHarmonyHeight(scale) + + (staffHeight * partCount); + static double calculateSystemPadding(double yScale) => staffHeight * 0.5; + + double get harmonyHeight => calculateHarmonyHeight( + transformationController.value.getMaxScaleOnAxis()); + double get idealSectionHeight => 2 * harmonyHeight; //max(22, harmonyHeight); double get sectionHeight => idealSectionHeight * sectionScaleNotifier.value; double get melodyHeight => staffHeight * yScale; @@ -154,19 +143,24 @@ class MusicSystemPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { canvas.save(); - translationTotal = 0; + if (rescale) { + canvas.scale(transformationController.value.getMaxScaleOnAxis()); + } + translationTotal = -verticallyVisibleRect().top; + canvas.translate(0, translationTotal); double translationIncrement = calculateSystemHeight(yScale, score.parts.length) + calculateSystemPadding(yScale); - // print("verticallyVisibleRect=${verticallyVisibleRect()}"); + print( + "verticallyVisibleRect=${verticallyVisibleRect()}, translationIncrement=$translationIncrement"); for (int i = 0; i < systemsToRender; i++) { if (translationTotal < verticallyVisibleRect().bottom + translationIncrement && translationTotal + translationIncrement > verticallyVisibleRect().top - translationIncrement) { + print("Drawing system $i at $translationTotal"); paintSystem(canvas, size, - offsetStart: - (visibleRect().width - calculateClefWidth(xScale)) * (i)); + offsetStart: (visibleRect().width - clefWidth) * (i)); } translationTotal += translationIncrement; canvas.translate(0, translationIncrement); @@ -195,13 +189,11 @@ class MusicSystemPainter extends CustomPainter { // canvas.clipRect(Offset.zero & size); // Calculate from which beat we should start drawing - int startBeat = ((visibleRect().left + offsetStart - standardBeatWidth) / - standardBeatWidth) - .floor(); + int startBeat = + ((visibleRect().left + offsetStart - beatWidth) / beatWidth).floor(); - // ignore: unused_local_variable double top, left, right, bottom; - left = startBeat * standardBeatWidth - offsetStart; + left = startBeat * beatWidth - offsetStart; // canvas.drawRect(visibleRect(), Paint()..style=PaintingStyle.stroke..strokeWidth=10); staves.value.forEach((staff) { @@ -223,7 +215,7 @@ class MusicSystemPainter extends CustomPainter { int renderingBeat = startBeat - extraBeatsSpaceForClefs.toInt(); // To make room for clefs // print("Drawing frame from beat=$renderingBeat. Colorblock alpha is ${colorblockOpacityNotifier.value}. Notation alpha is ${notationOpacityNotifier.value}"); - while (left < visibleRect().right + standardBeatWidth) { + while (left < visibleRect().right + beatWidth) { if (renderingBeat >= 0) { // Figure out what beat of what section we're drawing int renderingSectionBeat = renderingBeat; @@ -280,19 +272,19 @@ class MusicSystemPainter extends CustomPainter { textDirection: TextDirection.ltr, ); tp.layout(); - tp.paint(canvas, - new Offset(left + standardBeatWidth * 0.08, top + topOffset)); + tp.paint( + canvas, new Offset(left + beatWidth * 0.08, top + topOffset)); } top += sectionHeight; - right = left + standardBeatWidth; + right = left + beatWidth; String sectionName = renderingSection.name; if (sectionName == null || sectionName.isEmpty) { sectionName = renderingSection.id; } - Rect harmonyBounds = Rect.fromLTRB( - left, top, left + standardBeatWidth, top + harmonyHeight); + Rect harmonyBounds = + Rect.fromLTRB(left, top, left + beatWidth, top + harmonyHeight); if (isInBounds(harmonyBounds)) { _renderHarmonyBeat( harmonyBounds, renderingSection, renderingSectionBeat, canvas); @@ -309,17 +301,16 @@ class MusicSystemPainter extends CustomPainter { renderingSectionBeat, renderingBeat)); } - left += standardBeatWidth; + left += beatWidth; renderingBeat += 1; } if (visibleRect().right > left) { double extraWidth = 0; - double diff = left - visibleRect().left - standardBeatWidth; - if (offsetStart > 0 && diff < standardClefWidth + standardBeatWidth) { - extraWidth = standardClefWidth * - max(0, (standardClefWidth - diff)) / - standardBeatWidth; + double diff = left - visibleRect().left - beatWidth; + if (offsetStart > 0 && diff < standardClefWidth + beatWidth) { + extraWidth = + standardClefWidth * max(0, (standardClefWidth - diff)) / beatWidth; } canvas.drawRect( @@ -656,9 +647,9 @@ class MusicSystemPainter extends CustomPainter { double magicOpacityFactor(Rect melodyBounds) { double opacityFactor = 1; - if (melodyBounds.left < visibleRect().left + (2 * standardBeatWidth)) { + if (melodyBounds.left < visibleRect().left + (2 * beatWidth)) { double left = melodyBounds.left - visibleRect().left; - opacityFactor = max(0, min(1, (left) / (2 * standardBeatWidth))); + opacityFactor = max(0, min(1, (left) / (2 * beatWidth))); } return opacityFactor * opacityFactor; } @@ -799,17 +790,16 @@ class MusicSystemPainter extends CustomPainter { drawContinuousColorGuide(Canvas canvas, double top, double bottom) { // Calculate from which beat we should start drawing int renderingBeat = - ((visibleRect().left - standardBeatWidth) / standardBeatWidth).floor() - - 2; + ((visibleRect().left - beatWidth) / beatWidth).floor() - 2; - final double startOffset = renderingBeat * standardBeatWidth; + final double startOffset = renderingBeat * beatWidth; double left = startOffset; double chordLeft = left; Chord renderingChord; - while (left < visibleRect().right + standardBeatWidth) { + while (left < visibleRect().right + beatWidth) { if (renderingBeat < 0) { - left += standardBeatWidth; + left += beatWidth; renderingBeat += 1; continue; } @@ -856,9 +846,9 @@ class MusicSystemPainter extends CustomPainter { chordLeft = left; } renderingChord = chordAtSubdivision; - left += standardBeatWidth / renderingHarmony.subdivisionsPerBeat; + left += beatWidth / renderingHarmony.subdivisionsPerBeat; } - left = beatLeft + standardBeatWidth; + left = beatLeft + beatWidth; renderingBeat += 1; } Rect renderingRect = diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index a6a5cf62..55f59017 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -187,7 +187,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { } /// Used to maintain a locking mechanism as we animate from [_xScale] to [targetedScale] in the setter for [targetedScale]. - DateTime _xScaleLock, _yScaleLock; + DateTime _xScaleLock; List _xScaleAnimationControllers, _yScaleAnimationControllers; @@ -976,7 +976,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { if (MyPlatform.isDebug && true) IgnorePointer( child: Container( - width: 2 * targetedScale * unscaledStandardBeatWidth, + width: 2 * targetedScale * beatWidth, height: widget.height, decoration: BoxDecoration( color: Colors.black12, @@ -1030,13 +1030,12 @@ class _MusicViewState extends State with TickerProviderStateMixin { double systemXOffset = ((position.dy /*+ verticalScrollingPosition.value*/) / systemHeight) .floor() * - (widget.width - - MusicSystemPainter.calculateClefWidth(targetedScale)); + (widget.width - clefWidth); int beat = ((position.dx + systemXOffset + horizontallyVisibleRect.left - - 2 * unscaledStandardBeatWidth * targetedScale) / - (unscaledStandardBeatWidth * (targeted ? targetedScale : scale))) + 2 * beatWidth * targetedScale) / + (beatWidth * (targeted ? targetedScale : scale))) .floor(); // print("beat=$beat"); beat = max(0, min(beat, widget.score.maxBeat)); @@ -1202,8 +1201,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { while (partIndexY > systemHeight) { partIndexY -= systemHeight; } - int partIndex = - (partIndexY / (scale * MusicSystemPainter.staffHeight)).floor(); + int partIndex = (partIndexY / (scale * staffHeight)).floor(); print("partIndex=$partIndex"); print("mainPart=${mainPart?.midiName}"); if (!autoSort || widget.musicViewMode == MusicViewMode.section) { @@ -1396,10 +1394,9 @@ class _MusicViewState extends State with TickerProviderStateMixin { final double x = targetedScale, // y = targetedScale, w = widget.width, - numberOfBeatsOnScreen = w / (x * unscaledStandardBeatWidth), + numberOfBeatsOnScreen = w / (x * beatWidth), targetNumberOfBeatsOnScreen = numberOfBeatsOnScreen.roundToDouble(), - newXScale = - w / (targetNumberOfBeatsOnScreen * unscaledStandardBeatWidth); + newXScale = w / (targetNumberOfBeatsOnScreen * beatWidth); targetedScale = newXScale; // scale = newXScale; } @@ -1818,7 +1815,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { : 0; double get alignedScale { - double result = alignedStaffHeight / MusicSystemPainter.staffHeight; + double result = alignedStaffHeight / staffHeight; if (sectionsHeight != 0) { result *= (widget.height - 0) / (widget.height + sectionsHeight); } @@ -1826,7 +1823,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { } double get partAlignedScale { - double result = partAlignedStaffHeight / MusicSystemPainter.staffHeight; + double result = partAlignedStaffHeight / staffHeight; if (sectionsHeight != 0) { result *= (widget.height - 0) / (widget.height + sectionsHeight); } diff --git a/lib/storage/score_picker_preview.dart b/lib/storage/score_picker_preview.dart index a5c7acaa..9d7d12dc 100644 --- a/lib/storage/score_picker_preview.dart +++ b/lib/storage/score_picker_preview.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:beatscratch_flutter_redux/music_view/music_system_painter.dart'; import 'package:beatscratch_flutter_redux/settings/app_settings.dart'; +import 'package:beatscratch_flutter_redux/util/music_notation_theory.dart'; import 'package:beatscratch_flutter_redux/util/util.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -334,8 +335,7 @@ class _ScorePickerPreviewState extends State { widget.height, 36 + previewScale * - MusicSystemPainter - .staffHeight * + staffHeight * previewScore .parts .length), diff --git a/lib/util/music_notation_theory.dart b/lib/util/music_notation_theory.dart index 4e8ed3b2..ecb39e9f 100644 --- a/lib/util/music_notation_theory.dart +++ b/lib/util/music_notation_theory.dart @@ -2,6 +2,11 @@ import '../generated/protos/music.pb.dart'; import 'music_theory.dart'; import 'util.dart'; +const double beatWidth = 90.0; +const double clefWidth = beatWidth * 2; +const double extraBeatsSpaceForClefs = 2; +const double staffHeight = 500; + class NoteSpecification { final NoteName noteName; final int octave; @@ -150,8 +155,6 @@ extension HeptatonicConversions on int { } } -const double unscaledStandardBeatWidth = 90.0; - abstract class MusicStaff { MusicStaff(); From 3c397bdeb7f653c05843a113f150c2772b76c6f2 Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Mon, 10 Jan 2022 14:29:38 -0500 Subject: [PATCH 04/25] improve touch stuff --- lib/music_view/music_scroll_container.dart | 25 ++--- lib/music_view/music_system_painter.dart | 16 +-- lib/music_view/music_view.dart | 115 ++++++++++++--------- lib/util/music_notation_theory.dart | 6 +- 4 files changed, 93 insertions(+), 69 deletions(-) diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index f1435f94..c9ef48f0 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -84,8 +84,8 @@ class MusicScrollContainer extends StatefulWidget { _MusicScrollContainerState createState() => _MusicScrollContainerState(); } -Rect horizontallyVisibleRect = Rect.zero; -Rect verticallyVisibleRect = Rect.zero; +// Rect horizontallyVisibleRect = Rect.zero; +// Rect verticallyVisibleRect = Rect.zero; class _MusicScrollContainerState extends State with TickerProviderStateMixin { @@ -188,7 +188,7 @@ class _MusicScrollContainerState extends State double get sectionWidth => widget.currentSection.beatCount * targetBeatWidth; - double get visibleWidth => horizontallyVisibleRect.width; + double get visibleWidth => widget.width; double get visibleAreaForSection => visibleWidth - targetClefWidth; @@ -414,11 +414,11 @@ class _MusicScrollContainerState extends State onInteractionUpdate: (ScaleUpdateDetails details) { // scaleUpdateNotifier.value = details; if (!MyPlatform.isDebug) return; - print( - "onInteractionUpdate: total scale = ${scale}, focal=${details.focalPoint}"); // print the scale here + // print( + // "onInteractionUpdate: total scale = ${scale}, focal=${details.focalPoint}"); // print the scale here // print("matrix=${transformationController.value}"); - print("transformedRect=$transformedRect"); - print("dims=$overallCanvasWidth x $overallCanvasHeight"); + // print("transformedRect=$transformedRect"); + // print("dims=$overallCanvasWidth x $overallCanvasHeight"); }, constrained: false, transformationController: transformationController, @@ -500,11 +500,8 @@ class _MusicScrollContainerState extends State currentBeat = currentBeat.floorToDouble(); } double animationPos = (currentBeat) * beatWidth; - animationPos = min( - animationPos, - overallCanvasWidth - - horizontallyVisibleRect.width + - 0.62 * targetBeatWidth); + animationPos = min(animationPos, + overallCanvasWidth - widget.width + 0.62 * targetBeatWidth); animationPos = max(0, animationPos); return animationPos; } @@ -596,14 +593,14 @@ class _MusicScrollContainerState extends State firstBeatOfSection + 2.62 + (sectionWidth / targetBeatWidth) - - (horizontallyVisibleRect.width / targetBeatWidth); + (widget.width / targetBeatWidth); _constrainToSectionBounds() { if (widget.isTwoFingerScaling) return; // print("_constrainToSectionBounds"); try { MatrixUtils.getAsTranslation(transformationController.value).dx; double sectionWidth = widget.currentSection.beatCount * targetBeatWidth; - double visibleWidth = horizontallyVisibleRect.width; + double visibleWidth = widget.width; if (sectionCanBeCentered) { scrollToBeat(firstBeatOfSection - (marginBeatsForSection / 2)); } else { diff --git a/lib/music_view/music_system_painter.dart b/lib/music_view/music_system_painter.dart index 651ddec7..4af827da 100644 --- a/lib/music_view/music_system_painter.dart +++ b/lib/music_view/music_system_painter.dart @@ -123,14 +123,13 @@ class MusicSystemPainter extends CustomPainter { } static double calculateHarmonyHeight(double scale) => 10 / scale; - static double calculateOldHarmonyHeight(double scale) => min(100, 30 * scale); static double calculateSectionHeight(double scale) => calculateHarmonyHeight(scale); static double calculateSystemHeight(double scale, int partCount) => calculateSectionHeight(scale) + calculateHarmonyHeight(scale) + (staffHeight * partCount); - static double calculateSystemPadding(double yScale) => staffHeight * 0.5; + static double calculateSystemPadding(double scale) => staffHeight * 0.5; double get harmonyHeight => calculateHarmonyHeight( transformationController.value.getMaxScaleOnAxis()); @@ -143,22 +142,23 @@ class MusicSystemPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { canvas.save(); + final scale = transformationController.value.getMaxScaleOnAxis(); if (rescale) { - canvas.scale(transformationController.value.getMaxScaleOnAxis()); + canvas.scale(scale); } translationTotal = -verticallyVisibleRect().top; canvas.translate(0, translationTotal); double translationIncrement = - calculateSystemHeight(yScale, score.parts.length) + - calculateSystemPadding(yScale); - print( - "verticallyVisibleRect=${verticallyVisibleRect()}, translationIncrement=$translationIncrement"); + calculateSystemHeight(scale, score.parts.length) + + calculateSystemPadding(scale); + // print( + // "verticallyVisibleRect=${verticallyVisibleRect()}, translationIncrement=$translationIncrement"); for (int i = 0; i < systemsToRender; i++) { if (translationTotal < verticallyVisibleRect().bottom + translationIncrement && translationTotal + translationIncrement > verticallyVisibleRect().top - translationIncrement) { - print("Drawing system $i at $translationTotal"); + // print("Drawing system $i at $translationTotal"); paintSystem(canvas, size, offsetStart: (visibleRect().width - clefWidth) * (i)); } diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index 55f59017..58d62b4b 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -296,7 +296,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { if (value == null) { value = getBeat( Offset( - horizontallyVisibleRect.width / + widget.width / (context.isLandscape && widget.splitMode == SplitMode.half ? 4 : 2), @@ -1023,25 +1023,59 @@ class _MusicViewState extends State with TickerProviderStateMixin { return false; } + double get scaledSystemHeight => + MusicSystemPainter.calculateSystemHeight( + scale, widget.score.parts.length) + + MusicSystemPainter.calculateSystemPadding(scale); + + int systemNumber(transformedPosition) => + max(0, (transformedPosition.dy / scaledSystemHeight).floor()); + getBeat(Offset position, {bool targeted = true}) { - double systemHeight = MusicSystemPainter.calculateSystemHeight( - scale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(scale); - double systemXOffset = - ((position.dy /*+ verticalScrollingPosition.value*/) / systemHeight) - .floor() * - (widget.width - clefWidth); - int beat = ((position.dx + - systemXOffset + - horizontallyVisibleRect.left - - 2 * beatWidth * targetedScale) / - (beatWidth * (targeted ? targetedScale : scale))) - .floor(); + final inverse = Matrix4.copy(transformationController.value)..invert(); + position = MatrixUtils.transformPoint(inverse, position); + int systemNumber = this.systemNumber(position); + final scaledWidth = widget.width / scale; + final scaledAvailableWidth = scaledWidth - clefWidth; + double systemXOffset = systemNumber * scaledAvailableWidth; + double dx = position.dx + systemXOffset - clefWidth; + + int beat = (dx / beatWidth).floor(); // print("beat=$beat"); beat = max(0, min(beat, widget.score.maxBeat)); + print( + "getBeat: $position, width=$scaledWidth, systemNumber=$systemNumber, systemXOffset=$systemXOffset ==> $beat"); return beat; } + Part getPart(Offset position, {bool targeted = true}) { + final inverse = Matrix4.copy(transformationController.value)..invert(); + position = MatrixUtils.transformPoint(inverse, position); + int systemNumber = this.systemNumber(position); + double dy = position.dy; + dy -= systemNumber * scaledSystemHeight; + dy -= MusicSystemPainter.calculateHarmonyHeight(scale); + if (widget.musicViewMode == MusicViewMode.score || + scale < 2 * MusicScrollContainer.minScale || + !widget.showingSectionList) { + dy -= MusicSystemPainter.calculateSectionHeight(scale); + } + int partIndex = (dy / staffHeight).floor(); + List parts; + if (!autoSort || widget.musicViewMode == MusicViewMode.section) { + parts = widget.score.parts; + } else { + final mainPart = this.mainPart(); + parts = [mainPart] + + widget.score.parts + .where((p) => p.id != mainPart?.id) + .toList(growable: false); + } + final part = parts[min(parts.length - 1, partIndex)]; + // print("getPart: $position, partIndex=$partIndex ==> $part"); + return part; + } + /// In View Mode this is the Keyboard Part. In Edit Mode it's the Part that's focused, or the Part of the Melody /// that's focused, or the Keyboard Part if only a Section is focused. Part mainPart() { @@ -1191,38 +1225,27 @@ class _MusicViewState extends State with TickerProviderStateMixin { "pointerDown: ${localPosition} -> beat: $beat; x/t: $scale/$targetedScale"); tappedBeat.value = beat; - double partIndexY = /*verticalScrollingPosition.value +*/ localPosition - .dy; - partIndexY -= MusicSystemPainter.calculateHarmonyHeight(scale); - if (widget.musicViewMode == MusicViewMode.score || - targetedScale < minScale * 2) { - partIndexY -= MusicSystemPainter.calculateSectionHeight(scale); - } - while (partIndexY > systemHeight) { - partIndexY -= systemHeight; - } - int partIndex = (partIndexY / (scale * staffHeight)).floor(); - print("partIndex=$partIndex"); - print("mainPart=${mainPart?.midiName}"); - if (!autoSort || widget.musicViewMode == MusicViewMode.section) { - // print("not using autofocus"); - final parts = widget.score.parts; - tappedPart.value = parts[min(parts.length - 1, partIndex)]; - } else { - // print( - // "using autofocus; parts = ${widget.score.parts.map((e) => e.midiName)}"); - if (partIndex == 0 || widget.score.parts.length == 1) { - // print("using autofocus1"); - tappedPart.value = mainPart ?? widget.score.parts.first; - } else { - // print("using autofocus2"); - final parts = widget.score.parts - .where((p) => p.id != mainPart?.id) - .toList(growable: false); - if (parts.isEmpty) return; - tappedPart.value = parts[min(parts.length - 1, --partIndex)]; - } - } + final part = getPart(localPosition); + tappedPart.value = part; + // if (!autoSort || widget.musicViewMode == MusicViewMode.section) { + // // print("not using autofocus"); + // final parts = widget.score.parts; + // tappedPart.value = part; + // } else { + // // print( + // // "using autofocus; parts = ${widget.score.parts.map((e) => e.midiName)}"); + // if (partIndex == 0 || widget.score.parts.length == 1) { + // // print("using autofocus1"); + // tappedPart.value = mainPart ?? widget.score.parts.first; + // } else { + // // print("using autofocus2"); + // final parts = widget.score.parts + // .where((p) => p.id != mainPart?.id) + // .toList(growable: false); + // if (parts.isEmpty) return; + // tappedPart.value = parts[min(parts.length - 1, --partIndex)]; + // } + // } // Future.delayed(Duration(milliseconds: 800), () { // tappedBeat.value = null; // tappedPart.value = null; diff --git a/lib/util/music_notation_theory.dart b/lib/util/music_notation_theory.dart index ecb39e9f..76731711 100644 --- a/lib/util/music_notation_theory.dart +++ b/lib/util/music_notation_theory.dart @@ -2,10 +2,14 @@ import '../generated/protos/music.pb.dart'; import 'music_theory.dart'; import 'util.dart'; +// beatWidth is in the scale of the renderer, not the overlying InteractiveView. const double beatWidth = 90.0; +// clefWidth is in the scale of the renderer, not the overlying InteractiveView. const double clefWidth = beatWidth * 2; -const double extraBeatsSpaceForClefs = 2; +// staffHeight is in the scale of the renderer, not the overlying InteractiveView. const double staffHeight = 500; +// extraBeatsSpaceForClefs is relative to beatWidth, not any particular scale :) +const double extraBeatsSpaceForClefs = 2; class NoteSpecification { final NoteName noteName; From e6e0fa06a16a3539b42ea639a5ed9d451aef9c62 Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Mon, 10 Jan 2022 17:26:32 -0500 Subject: [PATCH 05/25] gettin prettier n prettier --- ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/music_view/music_scroll_container.dart | 30 ++++++++++--- lib/music_view/music_system_painter.dart | 42 +++++++++++-------- lib/music_view/music_view.dart | 42 ++++--------------- lib/util/music_notation_theory.dart | 2 + pubspec.yaml | 2 +- 7 files changed, 61 insertions(+), 61 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e8ceedfd..87b8e963 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -251,7 +251,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1250; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7785fe32..c87d15a3 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ double get _systemHeightForScrolling => MusicSystemPainter.calculateSystemHeight( scale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(scale); + systemPadding; double get _extraHeightForScrolling => _systemHeightForScrolling < widget.height ? max(0, (widget.height - _systemHeightForScrolling) / 3) @@ -334,6 +334,11 @@ class _MusicScrollContainerState extends State get minScale => MusicScrollContainer.minScale; get maxScale => MusicScrollContainer.maxScale; + double get scaledSystemHeight => MusicSystemPainter.calculateSystemHeight( + scale, widget.score.parts.length); + + int systemNumber(transformedPosition) => + max(0, (transformedPosition.dy / scaledSystemHeight).floor()); @override Widget build(BuildContext context) { @@ -402,8 +407,8 @@ class _MusicScrollContainerState extends State _prevSectionId = widget.currentSection.id; _prevPartId = widget.focusedPart?.id; _hasBuilt = true; - print( - "InteractiveViewer overallCanvasHeight=$overallCanvasHeight, systemsToRender=$systemsToRender, systemHeight=$systemHeight"); + // print( + // "InteractiveViewer overallCanvasHeight=$overallCanvasHeight, systemsToRender=$systemsToRender, systemHeight=$systemHeight"); return InteractiveViewer( minScale: minScale, maxScale: maxScale, @@ -413,9 +418,22 @@ class _MusicScrollContainerState extends State // horizontal: widget.width / scale, vertical: widget.height / scale), onInteractionUpdate: (ScaleUpdateDetails details) { // scaleUpdateNotifier.value = details; - if (!MyPlatform.isDebug) return; - // print( - // "onInteractionUpdate: total scale = ${scale}, focal=${details.focalPoint}"); // print the scale here + if (details.scale != 1) { + final focal = inverseTransformPoint( + transformationController.value, details.focalPoint); + int systemNumber = this.systemNumber(focal); + final scaledWidth1 = widget.width / scale; + final scaledWidth2 = widget.width / (scale * details.scale); + final scaledAvailableWidth1 = scaledWidth1 - clefWidth; + final scaledAvailableWidth2 = scaledWidth2 - clefWidth; + double systemXOffset1 = systemNumber * scaledAvailableWidth1; + double systemXOffset2 = systemNumber * scaledAvailableWidth2; + transformationController.value + .translate(systemXOffset2 - systemXOffset1, 0, 0); + + if (!MyPlatform.isDebug) return; + print("onInteractionUpdate: total scale = ${scale}, focal=$focal"); + } // print("matrix=${transformationController.value}"); // print("transformedRect=$transformedRect"); // print("dims=$overallCanvasWidth x $overallCanvasHeight"); diff --git a/lib/music_view/music_system_painter.dart b/lib/music_view/music_system_painter.dart index 4af827da..a373e499 100644 --- a/lib/music_view/music_system_painter.dart +++ b/lib/music_view/music_system_painter.dart @@ -54,6 +54,7 @@ class MusicSystemPainter extends CustomPainter { double get xScale => 1; double get yScale => 1; + double get scale => transformationController.value.getMaxScaleOnAxis(); Melody get focusedMelody => score.parts .expand((p) => p.melodies) @@ -128,11 +129,10 @@ class MusicSystemPainter extends CustomPainter { static double calculateSystemHeight(double scale, int partCount) => calculateSectionHeight(scale) + calculateHarmonyHeight(scale) + - (staffHeight * partCount); - static double calculateSystemPadding(double scale) => staffHeight * 0.5; + (staffHeight * partCount) + + systemPadding; - double get harmonyHeight => calculateHarmonyHeight( - transformationController.value.getMaxScaleOnAxis()); + double get harmonyHeight => calculateHarmonyHeight(scale); double get idealSectionHeight => 2 * harmonyHeight; //max(22, harmonyHeight); double get sectionHeight => idealSectionHeight * sectionScaleNotifier.value; @@ -142,27 +142,28 @@ class MusicSystemPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { canvas.save(); - final scale = transformationController.value.getMaxScaleOnAxis(); + final scale = this.scale; if (rescale) { canvas.scale(scale); } translationTotal = -verticallyVisibleRect().top; canvas.translate(0, translationTotal); double translationIncrement = - calculateSystemHeight(scale, score.parts.length) + - calculateSystemPadding(scale); + calculateSystemHeight(scale, score.parts.length); // print( // "verticallyVisibleRect=${verticallyVisibleRect()}, translationIncrement=$translationIncrement"); - for (int i = 0; i < systemsToRender; i++) { - if (translationTotal < - verticallyVisibleRect().bottom + translationIncrement && - translationTotal + translationIncrement > - verticallyVisibleRect().top - translationIncrement) { - // print("Drawing system $i at $translationTotal"); - paintSystem(canvas, size, - offsetStart: (visibleRect().width - clefWidth) * (i)); - } + final int firstSystem = + max(0, (verticallyVisibleRect().top / translationIncrement).floor()); + translationTotal += firstSystem * translationIncrement; + canvas.translate(0, firstSystem * translationIncrement); + for (int i = firstSystem; i < systemsToRender; i++) { + // print("Drawing system $i at $translationTotal"); + paintSystem(canvas, size, + offsetStart: (visibleRect().width - clefWidth) * (i)); translationTotal += translationIncrement; + if (translationTotal > verticallyVisibleRect().bottom) { + break; + } canvas.translate(0, translationIncrement); } canvas.restore(); @@ -494,6 +495,7 @@ class MusicSystemPainter extends CustomPainter { if (notationOpacityNotifier.value > 0) { MelodyStaffLinesRenderer() ..alphaDrawerPaint = (Paint() + ..strokeWidth = 1 / sqrt(scale) ..color = musicForegroundColor .withAlpha((255 * notationOpacityNotifier.value).toInt())) ..bounds = bounds @@ -558,7 +560,7 @@ class MusicSystemPainter extends CustomPainter { text: text, style: TextStyle( fontFamily: "VulfSans", - fontSize: max(11, 20 * yScale), + fontSize: max(11, 10 / scale), fontWeight: FontWeight.w800, color: musicForegroundColor.withOpacity( colorblockOpacityNotifier.value > 0.5 ? 0.8 : 1))); @@ -875,3 +877,9 @@ class MusicSystemPainter extends CustomPainter { return false; } } + +Offset inverseTransformPoint(Matrix4 transform, Offset point) { + if (MatrixUtils.isIdentity(transform)) return point; + transform = Matrix4.copy(transform)..invert(); + return MatrixUtils.transformPoint(transform, point); +} diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index 58d62b4b..e6f4f78b 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -1023,17 +1023,14 @@ class _MusicViewState extends State with TickerProviderStateMixin { return false; } - double get scaledSystemHeight => - MusicSystemPainter.calculateSystemHeight( - scale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(scale); + double get scaledSystemHeight => MusicSystemPainter.calculateSystemHeight( + scale, widget.score.parts.length); int systemNumber(transformedPosition) => max(0, (transformedPosition.dy / scaledSystemHeight).floor()); getBeat(Offset position, {bool targeted = true}) { - final inverse = Matrix4.copy(transformationController.value)..invert(); - position = MatrixUtils.transformPoint(inverse, position); + position = inverseTransformPoint(transformationController.value, position); int systemNumber = this.systemNumber(position); final scaledWidth = widget.width / scale; final scaledAvailableWidth = scaledWidth - clefWidth; @@ -1043,14 +1040,13 @@ class _MusicViewState extends State with TickerProviderStateMixin { int beat = (dx / beatWidth).floor(); // print("beat=$beat"); beat = max(0, min(beat, widget.score.maxBeat)); - print( - "getBeat: $position, width=$scaledWidth, systemNumber=$systemNumber, systemXOffset=$systemXOffset ==> $beat"); + // print( + // "getBeat: $position, width=$scaledWidth, systemNumber=$systemNumber, systemXOffset=$systemXOffset ==> $beat"); return beat; } Part getPart(Offset position, {bool targeted = true}) { - final inverse = Matrix4.copy(transformationController.value)..invert(); - position = MatrixUtils.transformPoint(inverse, position); + position = inverseTransformPoint(transformationController.value, position); int systemNumber = this.systemNumber(position); double dy = position.dy; dy -= systemNumber * scaledSystemHeight; @@ -1188,8 +1184,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { pointerDown(Offset localPosition) { double systemHeight = MusicSystemPainter.calculateSystemHeight( - scale, widget.score.parts.length) + - MusicSystemPainter.calculateSystemPadding(scale); + scale, widget.score.parts.length); if (systemHeight < localPosition.dy) { if (autoScroll) { setState(() { @@ -1227,29 +1222,6 @@ class _MusicViewState extends State with TickerProviderStateMixin { tappedBeat.value = beat; final part = getPart(localPosition); tappedPart.value = part; - // if (!autoSort || widget.musicViewMode == MusicViewMode.section) { - // // print("not using autofocus"); - // final parts = widget.score.parts; - // tappedPart.value = part; - // } else { - // // print( - // // "using autofocus; parts = ${widget.score.parts.map((e) => e.midiName)}"); - // if (partIndex == 0 || widget.score.parts.length == 1) { - // // print("using autofocus1"); - // tappedPart.value = mainPart ?? widget.score.parts.first; - // } else { - // // print("using autofocus2"); - // final parts = widget.score.parts - // .where((p) => p.id != mainPart?.id) - // .toList(growable: false); - // if (parts.isEmpty) return; - // tappedPart.value = parts[min(parts.length - 1, --partIndex)]; - // } - // } - // Future.delayed(Duration(milliseconds: 800), () { - // tappedBeat.value = null; - // tappedPart.value = null; - // }); } return AnimatedContainer( diff --git a/lib/util/music_notation_theory.dart b/lib/util/music_notation_theory.dart index 76731711..1575447f 100644 --- a/lib/util/music_notation_theory.dart +++ b/lib/util/music_notation_theory.dart @@ -8,6 +8,8 @@ const double beatWidth = 90.0; const double clefWidth = beatWidth * 2; // staffHeight is in the scale of the renderer, not the overlying InteractiveView. const double staffHeight = 500; +// systemPadding is in the scale of the renderer, not the overlying InteractiveView. +const double systemPadding = staffHeight * 0.5; // extraBeatsSpaceForClefs is relative to beatWidth, not any particular scale :) const double extraBeatsSpaceForClefs = 2; diff --git a/pubspec.yaml b/pubspec.yaml index cba02d74..5d99e194 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: beatscratch_flutter_redux description: BeatScratch for Flutter publish_to: none -version: 1.5.2+414 +version: 1.6.0+421 environment: sdk: ">=2.6.0 <3.0.0" From ec3aada703c0a09162e785157c5487db4d818fc0 Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Wed, 12 Jan 2022 15:14:09 -0500 Subject: [PATCH 06/25] more tweaks etc --- .vscode/launch.json | 30 +++---------- lib/music_view/music_scroll_container.dart | 51 +++++++++++++++++----- lib/music_view/music_system_painter.dart | 14 ++++-- lib/music_view/music_view.dart | 32 +++++++------- pubspec.yaml | 2 +- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ab08bf5f..16f1ec2b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -48,38 +48,20 @@ "deviceId": "emulator-5554" }, { - "name": "iPhone SE (2nd generation) (mobile)", + "name": "iPhone 13 (mobile)", "request": "launch", "type": "dart", - "deviceId": "B74B1F44-56AD-4D14-B1FD-6F685C8BB7C2" - }, - { - "name": "iPhone 12 (mobile)", - "request": "launch", - "type": "dart", - "deviceId": "7182391C-3F76-4577-9A48-CDDD0EE66DFB" - }, - { - "name": "iPad Pro (12.9-inch) (2nd generation) (mobile)", - "request": "launch", - "type": "dart", - "deviceId": "086D2D71-E736-4291-A91F-C575C0B5C060" - }, - { - "name": "iPad Pro (12.9-inch) (4th generation) (mobile)", - "request": "launch", - "type": "dart", - "deviceId": "47D83A08-59A9-4E80-A6FD-7805D9F9BA52" + "deviceId": "77171629-E070-4F03-9FD7-0898D08E2DEA" }, ], "compounds": [ { "name": "iOS Screenshot Devices", "configurations": [ - "iPad Pro (12.9-inch) (4th generation) (mobile)", - "iPhone SE (2nd generation) (mobile)", - "iPhone 12 (mobile)", - "iPad Pro (12.9-inch) (2nd generation) (mobile)" + // "iPad Pro (12.9-inch) (4th generation) (mobile)", + // "iPhone SE (2nd generation) (mobile)", + // "iPhone 12 (mobile)", + // "iPad Pro (12.9-inch) (2nd generation) (mobile)" ], } ] diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index 4a32d963..1946d735 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -239,6 +239,16 @@ class _MusicScrollContainerState extends State WidgetsBinding.instance.addPostFrameCallback((_) { scrollToCurrentBeat(); }); + interactionStartFocal.addListener(_onInteractionStartFocal); + } + + _onInteractionStartFocal() { + if (interactionStartFocal.value == null) { + interactionStartSystem.value = null; + } else { + interactionStartSystem.value = + max(0, (interactingFocal.dy / scaledSystemHeight).floor()); + } } DateTime _lastScrollEventSeen = DateTime(0); @@ -310,7 +320,6 @@ class _MusicScrollContainerState extends State @override void dispose() { animationController.dispose(); - transformationController.removeListener(_transformationListener); colorblockOpacityNotifier.dispose(); notationOpacityNotifier.dispose(); sectionScaleNotifier.dispose(); @@ -321,6 +330,11 @@ class _MusicScrollContainerState extends State colorboardPart.dispose(); focusedPart.dispose(); sectionColor.dispose(); + interactionStartSystem.dispose(); + interactionStartFocal + ..removeListener(_onInteractionStartFocal) + ..dispose(); + transformationController.removeListener(_transformationListener); widget.scrollToCurrentBeat.removeListener(scrollToCurrentBeat); widget.scrollToPart.removeListener(scrollToPart); widget.centerCurrentSection.removeListener(_constrainToSectionBounds); @@ -339,6 +353,10 @@ class _MusicScrollContainerState extends State int systemNumber(transformedPosition) => max(0, (transformedPosition.dy / scaledSystemHeight).floor()); + ValueNotifier interactionStartSystem = ValueNotifier(null); + ValueNotifier interactionStartFocal = ValueNotifier(null); + Offset get interactingFocal => interactionStartFocal.value; + int get interactingSystem => interactionStartSystem.value; @override Widget build(BuildContext context) { @@ -416,23 +434,34 @@ class _MusicScrollContainerState extends State bottom: widget.width / minScale, right: widget.height / scale), // boundaryMargin: EdgeInsets.symmetric( // horizontal: widget.width / scale, vertical: widget.height / scale), + onInteractionStart: (ScaleStartDetails details) { + interactionStartFocal.value = + transformationController.toScene(details.focalPoint); + }, + onInteractionEnd: (ScaleEndDetails details) { + interactionStartFocal.value = null; + }, onInteractionUpdate: (ScaleUpdateDetails details) { // scaleUpdateNotifier.value = details; - if (details.scale != 1) { - final focal = inverseTransformPoint( - transformationController.value, details.focalPoint); - int systemNumber = this.systemNumber(focal); - final scaledWidth1 = widget.width / scale; - final scaledWidth2 = widget.width / (scale * details.scale); + if (true || details.scale != 1) { + int systemNumber = interactingSystem; + final scaledWidth1 = widget.width / (scale / details.scale); + final scaledWidth2 = widget.width / (scale); final scaledAvailableWidth1 = scaledWidth1 - clefWidth; final scaledAvailableWidth2 = scaledWidth2 - clefWidth; double systemXOffset1 = systemNumber * scaledAvailableWidth1; double systemXOffset2 = systemNumber * scaledAvailableWidth2; - transformationController.value - .translate(systemXOffset2 - systemXOffset1, 0, 0); - + double diff = systemXOffset2 - systemXOffset1; + double translationX = transformedRect.left; if (!MyPlatform.isDebug) return; - print("onInteractionUpdate: total scale = ${scale}, focal=$focal"); + print( + "onInteractionUpdate: scale = ${details.scale}, focal=$interactingFocal, scaledSystemHeight=$scaledSystemHeight, systemNumber=$systemNumber, translationX=${translationX}, diff=$diff, transformedRect=$transformedRect"); + + if (translationX - diff > 0) { + transformationController.value.translate(diff, 0, 0); + } else { + transformationController.value.translate(translationX, 0, 0); + } } // print("matrix=${transformationController.value}"); // print("transformedRect=$transformedRect"); diff --git a/lib/music_view/music_system_painter.dart b/lib/music_view/music_system_painter.dart index a373e499..9afa04eb 100644 --- a/lib/music_view/music_system_painter.dart +++ b/lib/music_view/music_system_painter.dart @@ -315,8 +315,14 @@ class MusicSystemPainter extends CustomPainter { } canvas.drawRect( - Rect.fromLTRB(left - extraWidth, visibleRect().top + sectionHeight, - visibleRect().right, visibleRect().bottom), + Rect.fromLTRB( + left - extraWidth, + translationTotal + + visibleRect().top - + translationTotal + + sectionHeight, + visibleRect().right, + visibleRect().bottom - translationTotal), Paint()..color = musicBackgroundColor); } } @@ -878,8 +884,8 @@ class MusicSystemPainter extends CustomPainter { } } +Matrix4 inverse(Matrix4 transform) => Matrix4.copy(transform)..invert(); Offset inverseTransformPoint(Matrix4 transform, Offset point) { if (MatrixUtils.isIdentity(transform)) return point; - transform = Matrix4.copy(transform)..invert(); - return MatrixUtils.transformPoint(transform, point); + return MatrixUtils.transformPoint(inverse(transform), point); } diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index e6f4f78b..a1876f6d 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -406,8 +406,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { double get dy => MatrixUtils.getAsTranslation(transformationController.value).dy; double get scale => transformationController.value.getMaxScaleOnAxis(); - set scale(value) => - null; //transformationController.value.scale(value / scale); + set scale(value) => // null; + transformationController.value.scale(value / scale, value / scale, 1); _targetedScaleAnimationListener(double value) { value = max(0, min(maxScale, value)); @@ -554,17 +554,17 @@ class _MusicViewState extends State with TickerProviderStateMixin { @override Widget build(context) { - if (scale == null || targetedScale == null) { - final musicScale = widget.appSettings.musicScale; - if (musicScale == null) { - if (context.isTablet) { - scale = 0.33; - } else { - scale = 0.22; - } - } else { - scale = musicScale; - } + if (/*scale == null || */ targetedScale == null) { + // final musicScale = widget.appSettings.musicScale; + // if (musicScale == null) { + // if (context.isTablet) { + // scale = 0.33; + // } else { + // scale = 0.22; + // } + // } else { + // scale = musicScale; + // } targetedScale = scale; } // if (targetedScale != null && @@ -1030,7 +1030,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { max(0, (transformedPosition.dy / scaledSystemHeight).floor()); getBeat(Offset position, {bool targeted = true}) { - position = inverseTransformPoint(transformationController.value, position); + position = transformationController.toScene(position); int systemNumber = this.systemNumber(position); final scaledWidth = widget.width / scale; final scaledAvailableWidth = scaledWidth - clefWidth; @@ -1046,7 +1046,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { } Part getPart(Offset position, {bool targeted = true}) { - position = inverseTransformPoint(transformationController.value, position); + position = transformationController.toScene(position); int systemNumber = this.systemNumber(position); double dy = position.dy; dy -= systemNumber * scaledSystemHeight; @@ -1067,7 +1067,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { .where((p) => p.id != mainPart?.id) .toList(growable: false); } - final part = parts[min(parts.length - 1, partIndex)]; + final part = parts[max(0, min(parts.length - 1, partIndex))]; // print("getPart: $position, partIndex=$partIndex ==> $part"); return part; } diff --git a/pubspec.yaml b/pubspec.yaml index 5d99e194..50f797bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: beatscratch_flutter_redux description: BeatScratch for Flutter publish_to: none -version: 1.6.0+421 +version: 1.6.0+427 environment: sdk: ">=2.6.0 <3.0.0" From 8625480a478e9884c822e97ae56f70d398726c5b Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Tue, 18 Jan 2022 15:47:24 -0500 Subject: [PATCH 07/25] perfect scrolling --- lib/music_view/music_scroll_container.dart | 11 ++++++----- pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index 1946d735..7288bb99 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -453,14 +453,15 @@ class _MusicScrollContainerState extends State double systemXOffset2 = systemNumber * scaledAvailableWidth2; double diff = systemXOffset2 - systemXOffset1; double translationX = transformedRect.left; - if (!MyPlatform.isDebug) return; - print( - "onInteractionUpdate: scale = ${details.scale}, focal=$interactingFocal, scaledSystemHeight=$scaledSystemHeight, systemNumber=$systemNumber, translationX=${translationX}, diff=$diff, transformedRect=$transformedRect"); + // if (!MyPlatform.isDebug) return; + // print( + // "onInteractionUpdate: scale = ${details.scale}, focal=$interactingFocal, scaledSystemHeight=$scaledSystemHeight, systemNumber=$systemNumber, translationX=${translationX}, diff=$diff, transformedRect=$transformedRect"); if (translationX - diff > 0) { transformationController.value.translate(diff, 0, 0); - } else { - transformationController.value.translate(translationX, 0, 0); + } else if (transformedRect.top < systemHeight) { + transformationController.value + .translate(diff + scaledAvailableWidth2, -systemHeight, 0); } } // print("matrix=${transformationController.value}"); diff --git a/pubspec.yaml b/pubspec.yaml index 50f797bf..efcbdf7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: beatscratch_flutter_redux description: BeatScratch for Flutter publish_to: none -version: 1.6.0+427 +version: 1.6.0+429 environment: sdk: ">=2.6.0 <3.0.0" From a3bbcad96e6dc106ec31c75e318ef26edace4f8c Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Sat, 22 Jan 2022 17:14:32 -0500 Subject: [PATCH 08/25] stuff, smooth scrolling --- lib/music_view/music_scroll_container.dart | 39 +++++++++++----------- pubspec.yaml | 2 +- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index 7288bb99..62a338ba 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -239,16 +239,7 @@ class _MusicScrollContainerState extends State WidgetsBinding.instance.addPostFrameCallback((_) { scrollToCurrentBeat(); }); - interactionStartFocal.addListener(_onInteractionStartFocal); - } - - _onInteractionStartFocal() { - if (interactionStartFocal.value == null) { - interactionStartSystem.value = null; - } else { - interactionStartSystem.value = - max(0, (interactingFocal.dy / scaledSystemHeight).floor()); - } + interactionStartFocal.addListener(_deriveStartSystemFromFocal); } DateTime _lastScrollEventSeen = DateTime(0); @@ -332,7 +323,7 @@ class _MusicScrollContainerState extends State sectionColor.dispose(); interactionStartSystem.dispose(); interactionStartFocal - ..removeListener(_onInteractionStartFocal) + ..removeListener(_deriveStartSystemFromFocal) ..dispose(); transformationController.removeListener(_transformationListener); widget.scrollToCurrentBeat.removeListener(scrollToCurrentBeat); @@ -351,12 +342,20 @@ class _MusicScrollContainerState extends State double get scaledSystemHeight => MusicSystemPainter.calculateSystemHeight( scale, widget.score.parts.length); - int systemNumber(transformedPosition) => - max(0, (transformedPosition.dy / scaledSystemHeight).floor()); - ValueNotifier interactionStartSystem = ValueNotifier(null); ValueNotifier interactionStartFocal = ValueNotifier(null); + ValueNotifier interactionStartSystem = ValueNotifier(null); + _deriveStartSystemFromFocal() { + if (interactionStartFocal.value == null) { + interactionStartSystem.value = null; + } else { + interactionStartSystem.value = + max(0, (interactingFocal.dy / scaledSystemHeight).floor()); + } + } + Offset get interactingFocal => interactionStartFocal.value; - int get interactingSystem => interactionStartSystem.value; + int get interactingSystem => + max(0, min(systemsToRender - 1, interactionStartSystem.value)); @override Widget build(BuildContext context) { @@ -443,7 +442,8 @@ class _MusicScrollContainerState extends State }, onInteractionUpdate: (ScaleUpdateDetails details) { // scaleUpdateNotifier.value = details; - if (true || details.scale != 1) { + widget.tappedBeat.value = null; + if (details.scale != 1) { int systemNumber = interactingSystem; final scaledWidth1 = widget.width / (scale / details.scale); final scaledWidth2 = widget.width / (scale); @@ -452,16 +452,15 @@ class _MusicScrollContainerState extends State double systemXOffset1 = systemNumber * scaledAvailableWidth1; double systemXOffset2 = systemNumber * scaledAvailableWidth2; double diff = systemXOffset2 - systemXOffset1; - double translationX = transformedRect.left; // if (!MyPlatform.isDebug) return; // print( // "onInteractionUpdate: scale = ${details.scale}, focal=$interactingFocal, scaledSystemHeight=$scaledSystemHeight, systemNumber=$systemNumber, translationX=${translationX}, diff=$diff, transformedRect=$transformedRect"); - if (translationX - diff > 0) { + if (transformedRect.left > diff) { transformationController.value.translate(diff, 0, 0); - } else if (transformedRect.top < systemHeight) { + } else if (transformedRect.top > systemHeight) { transformationController.value - .translate(diff + scaledAvailableWidth2, -systemHeight, 0); + .translate(diff + scaledAvailableWidth2, systemHeight, 0); } } // print("matrix=${transformationController.value}"); diff --git a/pubspec.yaml b/pubspec.yaml index efcbdf7e..1cc62fbe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: beatscratch_flutter_redux description: BeatScratch for Flutter publish_to: none -version: 1.6.0+429 +version: 1.6.0+430 environment: sdk: ">=2.6.0 <3.0.0" From 69d61b763b22dffcdcbfe2b326ad4b60620f3ed5 Mon Sep 17 00:00:00 2001 From: Jon Latane Date: Sat, 22 Jan 2022 18:46:56 -0500 Subject: [PATCH 09/25] added first round of custom scaling support --- build-release-web | 14 +-- lib/main.dart | 4 +- lib/music_view/music_scroll_container.dart | 52 +++++++++++ lib/music_view/music_view.dart | 102 ++++++--------------- pubspec.yaml | 2 +- web/index.html | 4 +- 6 files changed, 94 insertions(+), 84 deletions(-) diff --git a/build-release-web b/build-release-web index 824c4c0c..c504983a 100755 --- a/build-release-web +++ b/build-release-web @@ -4,14 +4,14 @@ cd "$(dirname "$0")" echo "#-----------------------------------------------------------------------" && \ -echo "# Building for Web and copying to ../beatscratch-page/app-staging[-skia]" && \ +echo "# Building for Web and copying to ../beatscratch-page/app-staging" && \ echo "#-----------------------------------------------------------------------" && \ -flutter build web --dart-define=FLUTTER_WEB_USE_SKIA=false --release | sed 's/.*/\[Web\] &/' && \ +flutter build web --web-renderer canvaskit --release | sed 's/.*/\[Web\] &/' && \ echo "Copying build..." && \ cp -r build/web/* ../beatscratch-page/app-staging && \ -echo "Web app built to app-staging with FLUTTER_WEB_USE_SKIA=false! Manually copy to prod app and/or deploy to beatscratch.io" && \ -flutter build web --dart-define=FLUTTER_WEB_USE_SKIA=true --release | sed 's/.*/\[Web\] &/' && \ -echo "Copying build..." && \ -cp -r build/web/* ../beatscratch-page/app-staging-skia && \ -echo "Web app built to app-staging with FLUTTER_WEB_USE_SKIA=true! Manually copy to prod app and/or deploy to beatscratch.io" +echo "Web app built to app-staging! Manually copy to prod app and/or deploy to beatscratch.io" && \ +# flutter build web --dart-define=FLUTTER_WEB_USE_SKIA=true --release | sed 's/.*/\[Web\] &/' && \ +# echo "Copying build..." && \ +# cp -r build/web/* ../beatscratch-page/app-staging-skia && \ +# echo "Web app built to app-staging with FLUTTER_WEB_USE_SKIA=true! Manually copy to prod app and/or deploy to beatscratch.io" diff --git a/lib/main.dart b/lib/main.dart index 425140d8..8c64ab55 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2463,11 +2463,11 @@ class _MyHomePageState extends State with TickerProviderStateMixin { webWarningHeight - _bottomNotchPadding - exportUI.height - - universeViewUIHeight - + (context.isPortraitPhone ? universeViewUIHeight : 0) - downloadLinksHeight - _topNotchPaddingReal - _bottomTapInBarHeight - - // universeScoreTitleHeight - + universeScoreTitleHeight - duplicateScoreWarningHeight - beatScratchToolbarHeight(context); // if (context.isPortraitPhone) { diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index 62a338ba..b2984e81 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -213,12 +213,50 @@ class _MusicScrollContainerState extends State bool get autoSort => widget.appSettings.autoSortMusic; bool get autoZoomAlign => widget.appSettings.autoZoomAlignMusic; + Animation interactiveAnimation; + AnimationController interactiveController; + void _interactiveAnimationStop() { + interactiveController.stop(); + interactiveAnimation?.removeListener(_onInteractiveAnimation); + interactiveAnimation = null; + interactiveController.reset(); + } + + void _onInteractiveAnimation() { + transformationController.value = interactiveAnimation.value; + if (!interactiveController.isAnimating) { + interactiveAnimation.removeListener(_onInteractiveAnimation); + interactiveAnimation = null; + interactiveController.reset(); + } + } + + bool _animateToTargetedScale = true; + void animateToTargetScaleAndPosition() { + if (!_animateToTargetedScale) return; + _interactiveAnimationStop(); + // interactiveController.reset(); + final targetedScale = widget.targetScaleNotifier.value; + final targetedMatrix = transformationController.value.clone() + ..scale( + targetedScale / scale, targetedScale / scale, targetedScale / scale); + interactiveAnimation = Matrix4Tween( + begin: transformationController.value, + end: targetedMatrix, + ).animate(interactiveController); + interactiveAnimation.addListener(_onInteractiveAnimation); + interactiveController.forward(); + } + @override void initState() { super.initState(); + interactiveController = + AnimationController(vsync: this, duration: animationDuration); widget.scrollToFocusedBeat.addListener(() { scrollToFocusedBeat(); }); + widget.targetScaleNotifier.addListener(animateToTargetScaleAndPosition); animationController = AnimationController( vsync: this, duration: Duration(milliseconds: kIsWeb ? 1000 : 500)); colorblockOpacityNotifier = ValueNotifier(0); @@ -310,6 +348,7 @@ class _MusicScrollContainerState extends State @override void dispose() { + widget.targetScaleNotifier.removeListener(animateToTargetScaleAndPosition); animationController.dispose(); colorblockOpacityNotifier.dispose(); notationOpacityNotifier.dispose(); @@ -434,6 +473,11 @@ class _MusicScrollContainerState extends State // boundaryMargin: EdgeInsets.symmetric( // horizontal: widget.width / scale, vertical: widget.height / scale), onInteractionStart: (ScaleStartDetails details) { + // If the user tries to cause a transformation while the reset animation is + // running, cancel the reset animation. + if (interactiveController.status == AnimationStatus.forward) { + _interactiveAnimationStop(); + } interactionStartFocal.value = transformationController.toScene(details.focalPoint); }, @@ -441,9 +485,17 @@ class _MusicScrollContainerState extends State interactionStartFocal.value = null; }, onInteractionUpdate: (ScaleUpdateDetails details) { + // If the user tries to cause a transformation while the reset animation is + // running, cancel the reset animation. + if (interactiveController.status == AnimationStatus.forward) { + _interactiveAnimationStop(); + } // scaleUpdateNotifier.value = details; widget.tappedBeat.value = null; if (details.scale != 1) { + _animateToTargetedScale = false; + widget.targetScaleNotifier.value = scale; + _animateToTargetedScale = true; int systemNumber = interactingSystem; final scaledWidth1 = widget.width / (scale / details.scale); final scaledWidth2 = widget.width / (scale); diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index a1876f6d..b701dbfb 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'dart:ui'; +import 'package:beatscratch_flutter_redux/generated/i18n.dart'; import 'package:beatscratch_flutter_redux/widget/color_filtered_image_asset.dart'; import '../colors.dart'; @@ -182,6 +183,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { ValueNotifier _targetedScale; double get targetedScale => _targetedScale.value; set targetedScale(double v) { + print("set targetedScale=$v"); _targetedScale.value = v; widget.appSettings.musicScale = v; } @@ -406,62 +408,13 @@ class _MusicViewState extends State with TickerProviderStateMixin { double get dy => MatrixUtils.getAsTranslation(transformationController.value).dy; double get scale => transformationController.value.getMaxScaleOnAxis(); - set scale(value) => // null; - transformationController.value.scale(value / scale, value / scale, 1); - - _targetedScaleAnimationListener(double value) { - value = max(0, min(maxScale, value)); - targetedScale = value; - _animateScaleAtomically( - value: () => targetedScale, - currentValue: () => scale, - applyAnimatedValue: (value) => scale = value, - controllers: _xScaleAnimationControllers, - setLockTime: (it) { - _xScaleLock = it; - }, - getLockTime: () => _xScaleLock, - // notifyUpdate: _xScaleUpdate, - ); - // if (autoScroll) { - // widget.scrollToCurrentBeat(); - // } else { - // scrollToFocusedBeat(); - // } + set scale(value) { + print( + "Setting scale to $value, currently=$scale, scaling by ${value / scale}"); + transformationController.value + .scale(value / scale, value / scale, value / scale); } - // set rawXScale(double value) { - // value = max(minScale, min(maxScale, value)); - // final oldValue = _xScale; - // targetedScale = value; - // _xScale = value; - // // _xScaleUpdate(ScaleUpdate(oldValue, value)); - // } - - // set scale(double value) { - // value = max(minScale, min(maxScale, value)); - // targetedScale = value; - // _animateScaleAtomically( - // value: () => scale, - // currentValue: () => _yScale, - // applyAnimatedValue: (value) => _yScale = value, - // controllers: _yScaleAnimationControllers, - // setLockTime: (it) { - // _yScaleLock = it; - // }, - // getLockTime: () => _yScaleLock, - // // notifyUpdate: _yScaleUpdate, - // ); - // } - - // set rawYScale(double value) { - // value = max(minScale, min(maxScale, value)); - // final oldValue = _yScale; - // targetedScale = value; - // _yScale = value; - // _yScaleUpdate(ScaleUpdate(oldValue, value)); - // } - @override void initState() { super.initState(); @@ -554,17 +507,17 @@ class _MusicViewState extends State with TickerProviderStateMixin { @override Widget build(context) { - if (/*scale == null || */ targetedScale == null) { - // final musicScale = widget.appSettings.musicScale; - // if (musicScale == null) { - // if (context.isTablet) { - // scale = 0.33; - // } else { - // scale = 0.22; - // } - // } else { - // scale = musicScale; - // } + if (scale == null || targetedScale == null) { + final musicScale = widget.appSettings.musicScale; + if (musicScale == null) { + if (context.isTablet) { + scale = 0.33; + } else { + scale = 0.22; + } + } else { + scale = musicScale; + } targetedScale = scale; } // if (targetedScale != null && @@ -1605,7 +1558,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { musicForegroundColor.withOpacity(0.54)))), Transform.translate( offset: Offset(2, 20), - child: Text(scaleText(scale), + child: Text(scaleText(targetedScale), style: TextStyle( fontWeight: FontWeight.w800, fontSize: 12, @@ -1633,7 +1586,9 @@ class _MusicViewState extends State with TickerProviderStateMixin { _aligned = false; _partAligned = false; setState(() { - scale = min(maxScale, (targetedScale) * zoomIncrement); + print("zoomIn: scale=$scale, targetedScale=$targetedScale"); + targetedScale = + min(maxScale, (targetedScale) * zoomIncrement); // print("zoomIn done; targetedScale=$targetXScale, scale=$targetYScale"); }); } @@ -1644,7 +1599,10 @@ class _MusicViewState extends State with TickerProviderStateMixin { _aligned = false; _partAligned = false; setState(() { - scale = max(minScale, targetedScale / zoomIncrement); + print( + "zoomOut: scale=$scale, targetedScale=$targetedScale"); + targetedScale = + max(minScale, targetedScale / zoomIncrement); // print("zoomOut done; targetedScale=$targetedScale, scale=$scale"); }); } @@ -1793,14 +1751,14 @@ class _MusicViewState extends State with TickerProviderStateMixin { } alignVertically() { - final scale = alignedScale; - targetedScale = scale; + final newScale = alignedScale; + targetedScale = newScale; // scale = scale; } partAlignVertically() { - final scale = partAlignedScale; - targetedScale = scale; + final newScale = partAlignedScale; + targetedScale = newScale; // scale = scale; } diff --git a/pubspec.yaml b/pubspec.yaml index 1cc62fbe..1824ce54 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: beatscratch_flutter_redux description: BeatScratch for Flutter publish_to: none -version: 1.6.0+430 +version: 1.6.0+431 environment: sdk: ">=2.6.0 <3.0.0" diff --git a/web/index.html b/web/index.html index cf7f7b4f..307ad591 100644 --- a/web/index.html +++ b/web/index.html @@ -1,5 +1,5 @@ - + @@ -54,7 +54,7 @@ BeatScratch Web Preview - +