viewer: fixed minimap for some videos

This commit is contained in:
Thibault Deckers 2022-02-26 10:37:24 +09:00
parent 86e24881af
commit 8f6ce6674b
5 changed files with 71 additions and 67 deletions

View file

@ -1,24 +1,17 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/entry.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves/widgets/viewer/visual/state.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Minimap extends StatelessWidget { class Minimap extends StatelessWidget {
final AvesEntry entry;
final ValueNotifier<ViewState> viewStateNotifier; final ValueNotifier<ViewState> viewStateNotifier;
final Size size;
static const defaultSize = Size(96, 96); static const Size minimapSize = Size(96, 96);
const Minimap({ const Minimap({
Key? key, Key? key,
required this.entry,
required this.viewStateNotifier, required this.viewStateNotifier,
this.size = defaultSize,
}) : super(key: key); }) : super(key: key);
@override @override
@ -28,48 +21,33 @@ class Minimap extends StatelessWidget {
valueListenable: viewStateNotifier, valueListenable: viewStateNotifier,
builder: (context, viewState, child) { builder: (context, viewState, child) {
final viewportSize = viewState.viewportSize; final viewportSize = viewState.viewportSize;
if (viewportSize == null) return const SizedBox.shrink(); final contentSize = viewState.contentSize;
return AnimatedBuilder( if (viewportSize == null || contentSize == null) return const SizedBox();
animation: entry.imageChangeNotifier, return CustomPaint(
builder: (context, child) {
Widget _builder(Size displaySize) => CustomPaint(
painter: MinimapPainter( painter: MinimapPainter(
viewportSize: viewportSize, viewportSize: viewportSize,
entrySize: displaySize, contentSize: contentSize,
viewCenterOffset: viewState.position, viewCenterOffset: viewState.position,
viewScale: viewState.scale!, viewScale: viewState.scale!,
minimapBorderColor: Colors.white30, minimapBorderColor: Colors.white30,
), ),
size: size, size: minimapSize,
); );
if (entry.isVideo) {
final videoController = context.read<VideoConductor>().getController(entry);
if (videoController == null) return const SizedBox();
return ValueListenableBuilder<double>(
valueListenable: videoController.sarNotifier,
builder: (context, sar, child) {
return _builder(entry.videoDisplaySize(sar));
}, },
); ),
}
return _builder(entry.displaySize);
},
);
}),
); );
} }
} }
class MinimapPainter extends CustomPainter { class MinimapPainter extends CustomPainter {
final Size entrySize, viewportSize; final Size contentSize, viewportSize;
final Offset viewCenterOffset; final Offset viewCenterOffset;
final double viewScale; final double viewScale;
final Color minimapBorderColor, viewportBorderColor; final Color minimapBorderColor, viewportBorderColor;
const MinimapPainter({ const MinimapPainter({
required this.viewportSize, required this.viewportSize,
required this.entrySize, required this.contentSize,
required this.viewCenterOffset, required this.viewCenterOffset,
required this.viewScale, required this.viewScale,
this.minimapBorderColor = Colors.white, this.minimapBorderColor = Colors.white,
@ -78,30 +56,30 @@ class MinimapPainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (entrySize.width <= 0 || entrySize.height <= 0) return; if (contentSize.width <= 0 || contentSize.height <= 0) return;
final viewSize = entrySize * viewScale; final viewSize = contentSize * viewScale;
if (viewSize.isEmpty) return; if (viewSize.isEmpty) return;
// hide minimap when image is in full view // hide minimap when image is in full view
if (viewportSize + const Offset(precisionErrorTolerance, precisionErrorTolerance) >= viewSize) return; if (viewportSize + const Offset(precisionErrorTolerance, precisionErrorTolerance) >= viewSize) return;
final canvasScale = size.longestSide / viewSize.longestSide; final canvasScale = size.longestSide / viewSize.longestSide;
final scaledEntrySize = viewSize * canvasScale; final scaledContentSize = viewSize * canvasScale;
final scaledViewportSize = viewportSize * canvasScale; final scaledViewportSize = viewportSize * canvasScale;
final entryRect = Rect.fromCenter( final contentRect = Rect.fromCenter(
center: size.center(Offset.zero), center: size.center(Offset.zero),
width: scaledEntrySize.width, width: scaledContentSize.width,
height: scaledEntrySize.height, height: scaledContentSize.height,
); );
final viewportRect = Rect.fromCenter( final viewportRect = Rect.fromCenter(
center: size.center(Offset.zero) - viewCenterOffset * canvasScale, center: size.center(Offset.zero) - viewCenterOffset * canvasScale,
width: min(scaledEntrySize.width, scaledViewportSize.width), width: min(scaledContentSize.width, scaledViewportSize.width),
height: min(scaledEntrySize.height, scaledViewportSize.height), height: min(scaledContentSize.height, scaledViewportSize.height),
); );
canvas.translate((entryRect.width - size.width) / 2, (entryRect.height - size.height) / 2); canvas.translate((contentRect.width - size.width) / 2, (contentRect.height - size.height) / 2);
final fill = Paint() final fill = Paint()
..style = PaintingStyle.fill ..style = PaintingStyle.fill
@ -114,8 +92,8 @@ class MinimapPainter extends CustomPainter {
..color = viewportBorderColor; ..color = viewportBorderColor;
canvas.drawRect(viewportRect, fill); canvas.drawRect(viewportRect, fill);
canvas.drawRect(entryRect, fill); canvas.drawRect(contentRect, fill);
canvas.drawRect(entryRect, minimapStroke); canvas.drawRect(contentRect, minimapStroke);
canvas.drawRect(viewportRect, viewportStroke); canvas.drawRect(viewportRect, viewportStroke);
} }

View file

@ -145,7 +145,6 @@ class ViewerTopOverlay extends StatelessWidget {
FadeTransition( FadeTransition(
opacity: scale, opacity: scale,
child: Minimap( child: Minimap(
entry: pageEntry,
viewStateNotifier: viewStateNotifier, viewStateNotifier: viewStateNotifier,
), ),
) )

View file

@ -29,15 +29,16 @@ class ViewStateConductor {
// try to initialize the view state to match magnifier initial state // try to initialize the view state to match magnifier initial state
const initialScale = ScaleLevel(ref: ScaleReference.contained); const initialScale = ScaleLevel(ref: ScaleReference.contained);
final initialValue = ViewState( final initialValue = ViewState(
Offset.zero, position: Offset.zero,
ScaleBoundaries( scale: ScaleBoundaries(
minScale: initialScale, minScale: initialScale,
maxScale: initialScale, maxScale: initialScale,
initialScale: initialScale, initialScale: initialScale,
viewportSize: _viewportSize, viewportSize: _viewportSize,
childSize: entry.displaySize, childSize: entry.displaySize,
).initialScale, ).initialScale,
_viewportSize, viewportSize: _viewportSize,
contentSize: entry.displaySize,
); );
controller = Tuple2(entry.uri, ValueNotifier<ViewState>(initialValue)); controller = Tuple2(entry.uri, ValueNotifier<ViewState>(initialValue));
} }

View file

@ -305,15 +305,17 @@ class _EntryPageViewState extends State<EntryPageView> {
void _onTap() => const ToggleOverlayNotification().dispatch(context); void _onTap() => const ToggleOverlayNotification().dispatch(context);
void _onViewStateChanged(MagnifierState v) { void _onViewStateChanged(MagnifierState v) {
final current = _viewStateNotifier.value; _viewStateNotifier.value = _viewStateNotifier.value.copyWith(
final viewState = ViewState(v.position, v.scale, current.viewportSize); position: v.position,
_viewStateNotifier.value = viewState; scale: v.scale,
);
} }
void _onViewScaleBoundariesChanged(ScaleBoundaries v) { void _onViewScaleBoundariesChanged(ScaleBoundaries v) {
final current = _viewStateNotifier.value; _viewStateNotifier.value = _viewStateNotifier.value.copyWith(
final viewState = ViewState(current.position, current.scale, v.viewportSize); viewportSize: v.viewportSize,
_viewStateNotifier.value = viewState; contentSize: v.childSize,
);
} }
static ScaleState _vectorScaleStateCycle(ScaleState actual) { static ScaleState _vectorScaleStateCycle(ScaleState actual) {

View file

@ -5,12 +5,36 @@ import 'package:flutter/widgets.dart';
class ViewState extends Equatable { class ViewState extends Equatable {
final Offset position; final Offset position;
final double? scale; final double? scale;
final Size? viewportSize; final Size? viewportSize, contentSize;
static const ViewState zero = ViewState(Offset.zero, 0, null); static const ViewState zero = ViewState(
position: Offset.zero,
scale: 0,
viewportSize: null,
contentSize: null,
);
@override @override
List<Object?> get props => [position, scale, viewportSize]; List<Object?> get props => [position, scale, viewportSize, contentSize];
const ViewState(this.position, this.scale, this.viewportSize); const ViewState({
required this.position,
required this.scale,
required this.viewportSize,
required this.contentSize,
});
ViewState copyWith({
Offset? position,
double? scale,
Size? viewportSize,
Size? contentSize,
}) {
return ViewState(
position: position ?? this.position,
scale: scale ?? this.scale,
viewportSize: viewportSize ?? this.viewportSize,
contentSize: contentSize ?? this.contentSize,
);
}
} }