viewer: fixed minimap for some videos
This commit is contained in:
parent
86e24881af
commit
8f6ce6674b
5 changed files with 71 additions and 67 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,6 @@ class ViewerTopOverlay extends StatelessWidget {
|
||||||
FadeTransition(
|
FadeTransition(
|
||||||
opacity: scale,
|
opacity: scale,
|
||||||
child: Minimap(
|
child: Minimap(
|
||||||
entry: pageEntry,
|
|
||||||
viewStateNotifier: viewStateNotifier,
|
viewStateNotifier: viewStateNotifier,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue