mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-31 16:18:22 +08:00
163
lib/common/widgets/cropped_image.dart
Normal file
163
lib/common/widgets/cropped_image.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* This file is part of PiliPlus
|
||||
*
|
||||
* PiliPlus is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PiliPlus is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with PiliPlus. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class CroppedImage extends LeafRenderObjectWidget {
|
||||
const CroppedImage({
|
||||
super.key,
|
||||
required this.size,
|
||||
required this.image,
|
||||
required this.srcRect,
|
||||
required this.dstRect,
|
||||
required this.rrect,
|
||||
required this.imgPaint,
|
||||
required this.borderPaint,
|
||||
});
|
||||
|
||||
final Size size;
|
||||
final ui.Image image;
|
||||
final Rect srcRect;
|
||||
final Rect dstRect;
|
||||
final RRect rrect;
|
||||
final Paint imgPaint;
|
||||
final Paint borderPaint;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderCroppedImage(
|
||||
preferredSize: size,
|
||||
image: image,
|
||||
srcRect: srcRect,
|
||||
dstRect: dstRect,
|
||||
rrect: rrect,
|
||||
imgPaint: imgPaint,
|
||||
borderPaint: borderPaint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderCroppedImage renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..preferredSize = size
|
||||
..image = image
|
||||
..srcRect = srcRect
|
||||
..dstRect = dstRect
|
||||
..rrect = rrect
|
||||
..imgPaint = imgPaint
|
||||
..borderPaint = borderPaint;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderCroppedImage extends RenderBox {
|
||||
RenderCroppedImage({
|
||||
required Size preferredSize,
|
||||
required ui.Image image,
|
||||
required Rect srcRect,
|
||||
required Rect dstRect,
|
||||
required RRect rrect,
|
||||
required Paint imgPaint,
|
||||
required Paint borderPaint,
|
||||
}) : _preferredSize = preferredSize,
|
||||
_image = image,
|
||||
_srcRect = srcRect,
|
||||
_dstRect = dstRect,
|
||||
_rrect = rrect,
|
||||
_imgPaint = imgPaint,
|
||||
_borderPaint = borderPaint;
|
||||
|
||||
Size _preferredSize;
|
||||
Size get preferredSize => _preferredSize;
|
||||
set preferredSize(Size value) {
|
||||
if (_preferredSize == value) return;
|
||||
_preferredSize = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
ui.Image _image;
|
||||
ui.Image get image => _image;
|
||||
set image(ui.Image value) {
|
||||
if (_image == value) return;
|
||||
_image = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Rect _srcRect;
|
||||
Rect get srcRect => _srcRect;
|
||||
set srcRect(Rect value) {
|
||||
if (_srcRect == value) return;
|
||||
_srcRect = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Rect _dstRect;
|
||||
Rect get dstRect => _dstRect;
|
||||
set dstRect(Rect value) {
|
||||
if (_dstRect == value) return;
|
||||
_dstRect = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
RRect _rrect;
|
||||
RRect get rrect => _rrect;
|
||||
set rrect(RRect value) {
|
||||
if (_rrect == value) return;
|
||||
_rrect = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Paint _imgPaint;
|
||||
Paint get imgPaint => _imgPaint;
|
||||
set imgPaint(Paint value) {
|
||||
if (_imgPaint == value) return;
|
||||
_imgPaint = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Paint _borderPaint;
|
||||
Paint get borderPaint => _borderPaint;
|
||||
set borderPaint(Paint value) {
|
||||
if (_borderPaint == value) return;
|
||||
_borderPaint = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = computeDryLayout(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return constraints.constrain(_preferredSize);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
context.canvas
|
||||
..drawImageRect(image, srcRect, dstRect, _imgPaint)
|
||||
..drawRRect(rrect, _borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
117
lib/common/widgets/custom_arc.dart
Normal file
117
lib/common/widgets/custom_arc.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'dart:math' show pi;
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class Arc extends LeafRenderObjectWidget {
|
||||
const Arc({
|
||||
super.key,
|
||||
required this.size,
|
||||
required this.color,
|
||||
required this.sweepAngle,
|
||||
this.strokeWidth = 2,
|
||||
});
|
||||
|
||||
final double size;
|
||||
final Color color;
|
||||
final double sweepAngle;
|
||||
final double strokeWidth;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderArc(
|
||||
size: size,
|
||||
color: color,
|
||||
sweepAngle: sweepAngle,
|
||||
strokeWidth: strokeWidth,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderArc renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..color = color
|
||||
..sweepAngle = sweepAngle
|
||||
..strokeWidth = strokeWidth;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderArc extends RenderBox {
|
||||
RenderArc({
|
||||
required double size,
|
||||
required Color color,
|
||||
required double sweepAngle,
|
||||
required double strokeWidth,
|
||||
}) : _preferredSize = Size.square(size),
|
||||
_color = color,
|
||||
_sweepAngle = sweepAngle,
|
||||
_strokeWidth = strokeWidth;
|
||||
|
||||
Color _color;
|
||||
Color get color => _color;
|
||||
set color(Color value) {
|
||||
if (_color == value) return;
|
||||
_color = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _sweepAngle;
|
||||
double get sweepAngle => _sweepAngle;
|
||||
set sweepAngle(double value) {
|
||||
if (_sweepAngle == value) return;
|
||||
_sweepAngle = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _strokeWidth;
|
||||
double get strokeWidth => _strokeWidth;
|
||||
set strokeWidth(double value) {
|
||||
if (_strokeWidth == value) return;
|
||||
_strokeWidth = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Size _preferredSize;
|
||||
set preferredSize(Size value) {
|
||||
if (_preferredSize == value) return;
|
||||
_preferredSize = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = computeDryLayout(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return constraints.constrain(_preferredSize);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (sweepAngle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final size = this.size;
|
||||
final rect = Rect.fromCircle(
|
||||
center: Offset(size.width / 2, size.height / 2),
|
||||
radius: size.width / 2,
|
||||
);
|
||||
|
||||
const startAngle = -pi / 2;
|
||||
|
||||
context.canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import 'dart:ui' show clampDouble;
|
||||
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum TooltipType { top, right }
|
||||
|
||||
@@ -261,19 +261,83 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
class TrianglePainter extends CustomPainter {
|
||||
TrianglePainter(this.color, {this.type = TooltipType.top});
|
||||
final TooltipType type;
|
||||
class Triangle extends LeafRenderObjectWidget {
|
||||
const Triangle({
|
||||
super.key,
|
||||
required this.color,
|
||||
required this.size,
|
||||
this.type = .top,
|
||||
});
|
||||
|
||||
final Color color;
|
||||
final Size size;
|
||||
final TooltipType type;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderTriangle(
|
||||
color: color,
|
||||
size: size,
|
||||
type: type,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderTriangle renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..color = color
|
||||
..preferredSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderTriangle extends RenderBox {
|
||||
RenderTriangle({
|
||||
required Color color,
|
||||
required Size size,
|
||||
required TooltipType type,
|
||||
}) : _color = color,
|
||||
_preferredSize = size,
|
||||
_type = type;
|
||||
|
||||
Color _color;
|
||||
Color get color => _color;
|
||||
set color(Color value) {
|
||||
if (_color == value) return;
|
||||
_color = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Size _preferredSize;
|
||||
set preferredSize(Size value) {
|
||||
if (_preferredSize == value) return;
|
||||
_preferredSize = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
final TooltipType _type;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = computeDryLayout(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return constraints.constrain(_preferredSize);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final size = this.size;
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
Path path;
|
||||
switch (type) {
|
||||
switch (_type) {
|
||||
case TooltipType.top:
|
||||
path = Path()
|
||||
..moveTo(0, 0)
|
||||
@@ -288,11 +352,11 @@ class TrianglePainter extends CustomPainter {
|
||||
..close();
|
||||
}
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
context.canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(TrianglePainter oldDelegate) => color != oldDelegate.color;
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
Offset positionDependentBox({
|
||||
|
||||
@@ -4,10 +4,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class DisabledIcon<T extends Widget> extends SingleChildRenderObjectWidget {
|
||||
final Color? color;
|
||||
final double lineLengthScale;
|
||||
final StrokeCap strokeCap;
|
||||
|
||||
const DisabledIcon({
|
||||
super.key,
|
||||
required T child,
|
||||
@@ -18,27 +14,70 @@ class DisabledIcon<T extends Widget> extends SingleChildRenderObjectWidget {
|
||||
strokeCap = strokeCap ?? StrokeCap.butt,
|
||||
super(child: child);
|
||||
|
||||
final Color? color;
|
||||
final StrokeCap strokeCap;
|
||||
final double lineLengthScale;
|
||||
|
||||
T enable() => child as T;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderMaskedIcon(
|
||||
color:
|
||||
color ??
|
||||
(child is Icon
|
||||
? (child as Icon).color ?? IconTheme.of(context).color!
|
||||
: IconTheme.of(context).color!),
|
||||
lineLengthScale,
|
||||
strokeCap,
|
||||
strokeCap: strokeCap,
|
||||
lineLengthScale: lineLengthScale,
|
||||
);
|
||||
}
|
||||
|
||||
T enable() => child as T;
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderMaskedIcon renderObject) {
|
||||
renderObject
|
||||
..color =
|
||||
color ??
|
||||
(child is Icon
|
||||
? (child as Icon).color ?? IconTheme.of(context).color!
|
||||
: IconTheme.of(context).color!)
|
||||
..strokeCap = strokeCap
|
||||
..lineLengthScale = lineLengthScale;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderMaskedIcon extends RenderProxyBox {
|
||||
final Color color;
|
||||
final double lineLengthScale;
|
||||
final StrokeCap strokeCap;
|
||||
RenderMaskedIcon({
|
||||
required Color color,
|
||||
required StrokeCap strokeCap,
|
||||
required double lineLengthScale,
|
||||
}) : _color = color,
|
||||
_strokeCap = strokeCap,
|
||||
_lineLengthScale = lineLengthScale;
|
||||
|
||||
RenderMaskedIcon(this.color, this.lineLengthScale, this.strokeCap);
|
||||
Color _color;
|
||||
Color get color => _color;
|
||||
set color(Color value) {
|
||||
if (_color == value) return;
|
||||
_color = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
StrokeCap _strokeCap;
|
||||
StrokeCap get strokeCap => _strokeCap;
|
||||
set strokeCap(StrokeCap value) {
|
||||
if (_strokeCap == value) return;
|
||||
_strokeCap = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _lineLengthScale;
|
||||
double get lineLengthScale => _lineLengthScale;
|
||||
set lineLengthScale(double value) {
|
||||
if (_lineLengthScale == value) return;
|
||||
_lineLengthScale = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
@@ -77,7 +116,7 @@ class RenderMaskedIcon extends RenderProxyBox {
|
||||
..clipPath(path, doAntiAlias: false);
|
||||
super.paint(context, offset);
|
||||
|
||||
context.canvas.restore();
|
||||
canvas.restore();
|
||||
|
||||
final linePaint = Paint()
|
||||
..color = color
|
||||
@@ -94,6 +133,9 @@ class RenderMaskedIcon extends RenderProxyBox {
|
||||
linePaint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
extension DisabledIconExt on Icon {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_arc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
@@ -30,19 +30,14 @@ class LoadingWidget extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
//loading animation
|
||||
RepaintBoundary.wrap(
|
||||
Obx(
|
||||
() => CustomPaint(
|
||||
size: const Size.square(40),
|
||||
painter: ArcPainter(
|
||||
() => Arc(
|
||||
size: 40,
|
||||
color: onSurfaceVariant,
|
||||
strokeWidth: 3,
|
||||
sweepAngle: progress.value * 2 * pi,
|
||||
),
|
||||
),
|
||||
),
|
||||
0,
|
||||
),
|
||||
//msg
|
||||
Text(msg, style: TextStyle(color: onSurfaceVariant)),
|
||||
],
|
||||
|
||||
@@ -104,9 +104,9 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
);
|
||||
if (!isTextEmote) {
|
||||
child = CustomTooltip(
|
||||
indicator: () => CustomPaint(
|
||||
indicator: () => Triangle(
|
||||
color: color,
|
||||
size: const Size(14, 8),
|
||||
painter: TrianglePainter(color),
|
||||
),
|
||||
overlayWidget: () => Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
||||
@@ -105,9 +105,9 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
||||
}
|
||||
},
|
||||
child: CustomTooltip(
|
||||
indicator: () => CustomPaint(
|
||||
indicator: () => Triangle(
|
||||
color: color,
|
||||
size: const Size(14, 8),
|
||||
painter: TrianglePainter(color),
|
||||
),
|
||||
overlayWidget: () => Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:math' show pi;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/custom_arc.dart';
|
||||
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -52,18 +51,14 @@ class ActionItem extends StatelessWidget {
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
RepaintBoundary(
|
||||
child: AnimatedBuilder(
|
||||
AnimatedBuilder(
|
||||
animation: animation!,
|
||||
builder: (context, child) => CustomPaint(
|
||||
size: const Size.square(28),
|
||||
painter: ArcPainter(
|
||||
builder: (context, child) => Arc(
|
||||
size: 28,
|
||||
color: primary,
|
||||
sweepAngle: animation!.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child,
|
||||
],
|
||||
);
|
||||
@@ -115,40 +110,3 @@ class ActionItem extends StatelessWidget {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
class ArcPainter extends CustomPainter {
|
||||
const ArcPainter({
|
||||
required this.color,
|
||||
required this.sweepAngle,
|
||||
this.strokeWidth = 2,
|
||||
});
|
||||
final Color color;
|
||||
final double sweepAngle;
|
||||
final double strokeWidth;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (sweepAngle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final rect = Rect.fromCircle(
|
||||
center: Offset(size.width / 2, size.height / 2),
|
||||
radius: size.width / 2,
|
||||
);
|
||||
|
||||
const startAngle = -pi / 2;
|
||||
|
||||
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant ArcPainter oldDelegate) {
|
||||
return sweepAngle != oldDelegate.sweepAngle || color != oldDelegate.color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/cropped_image.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/immediate_tap_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/mouse_interactive_viewer.dart';
|
||||
@@ -63,6 +64,7 @@ import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show RenderProxyBox;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -2352,9 +2354,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
return Positioned(
|
||||
right: right,
|
||||
top: top,
|
||||
child: RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
painter: _DanmakuTipPainter(offset: triangleOffset),
|
||||
child: _DanmakuTip(
|
||||
offset: triangleOffset,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
@@ -2478,7 +2479,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2661,6 +2661,7 @@ Future<ui.Image?> _loadImg(String path) async {
|
||||
|
||||
class _VideoShotImageState extends State<VideoShotImage> {
|
||||
late Size _size;
|
||||
late Rect _srcRect;
|
||||
late Rect _dstRect;
|
||||
late RRect _rrect;
|
||||
ui.Image? _image;
|
||||
@@ -2686,14 +2687,17 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
final height = widget.height;
|
||||
final width = height * imgXSize / imgYSize;
|
||||
_setRect(width, height);
|
||||
_setSrcRect(imgXSize, imgYSize);
|
||||
widget.onSetSize(imgXSize, imgYSize);
|
||||
} else {
|
||||
_setRect(double.nan, double.nan);
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
} else {
|
||||
final height = widget.height;
|
||||
final width = height * widget.imgXSize / widget.imgYSize;
|
||||
_setRect(width, height);
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2703,6 +2707,15 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
_rrect = RRect.fromRectAndRadius(_dstRect, const Radius.circular(10));
|
||||
}
|
||||
|
||||
void _setSrcRect(double imgXSize, double imgYSize) {
|
||||
_srcRect = Rect.fromLTWH(
|
||||
widget.x * imgXSize,
|
||||
widget.y * imgYSize,
|
||||
imgXSize,
|
||||
imgYSize,
|
||||
);
|
||||
}
|
||||
|
||||
void _loadImg() {
|
||||
final url = widget.url;
|
||||
_image = widget.imageCache[url];
|
||||
@@ -2733,6 +2746,9 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
if (oldWidget.url != widget.url) {
|
||||
_loadImg();
|
||||
}
|
||||
if (oldWidget.x != widget.x || oldWidget.y != widget.y) {
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
}
|
||||
|
||||
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
|
||||
@@ -2744,101 +2760,56 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_image != null) {
|
||||
return RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
painter: _CroppedImagePainter(
|
||||
return CroppedImage(
|
||||
size: _size,
|
||||
image: _image!,
|
||||
x: widget.x,
|
||||
y: widget.y,
|
||||
imgXSize: widget.imgXSize,
|
||||
imgYSize: widget.imgYSize,
|
||||
srcRect: _srcRect,
|
||||
dstRect: _dstRect,
|
||||
rrect: _rrect,
|
||||
imgPaint: _imgPaint,
|
||||
borderPaint: _borderPaint,
|
||||
),
|
||||
size: _size,
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class _CroppedImagePainter extends CustomPainter {
|
||||
final ui.Image image;
|
||||
final Rect srcRect;
|
||||
final Rect dstRect;
|
||||
final RRect rrect;
|
||||
final Paint imgPaint;
|
||||
final Paint borderPaint;
|
||||
|
||||
_CroppedImagePainter({
|
||||
required this.image,
|
||||
required int x,
|
||||
required int y,
|
||||
required double imgXSize,
|
||||
required double imgYSize,
|
||||
required this.dstRect,
|
||||
required this.rrect,
|
||||
required this.imgPaint,
|
||||
required this.borderPaint,
|
||||
}) : srcRect = Rect.fromLTWH(x * imgXSize, y * imgYSize, imgXSize, imgYSize);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
canvas
|
||||
..drawImageRect(image, srcRect, dstRect, imgPaint)
|
||||
..drawRRect(rrect, borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CroppedImagePainter oldDelegate) {
|
||||
return oldDelegate.image != image || oldDelegate.srcRect != srcRect;
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildViewPointWidget(
|
||||
VideoDetailController videoDetailController,
|
||||
PlPlayerController plPlayerController,
|
||||
double offset,
|
||||
double maxWidth,
|
||||
) {
|
||||
return Container(
|
||||
height: 16,
|
||||
margin: EdgeInsets.only(bottom: offset),
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPointerDown: (event) {
|
||||
try {
|
||||
double seg = event.localPosition.dx / maxWidth;
|
||||
Segment item = videoDetailController.viewPointList
|
||||
.where((item) => item.start >= seg)
|
||||
.reduce((a, b) => a.start < b.start ? a : b);
|
||||
if (item.from != null) {
|
||||
plPlayerController
|
||||
..danmakuController?.clear()
|
||||
..videoPlayerController?.seek(Duration(seconds: item.from!));
|
||||
}
|
||||
// if (kDebugMode) debugPrint('${item.title},,${item.from}');
|
||||
} catch (e) {
|
||||
if (kDebugMode) rethrow;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const double _triangleHeight = 5.6;
|
||||
|
||||
class _DanmakuTipPainter extends CustomPainter {
|
||||
class _DanmakuTip extends SingleChildRenderObjectWidget {
|
||||
const _DanmakuTip({
|
||||
this.offset = 0,
|
||||
super.child,
|
||||
});
|
||||
|
||||
final double offset;
|
||||
|
||||
const _DanmakuTipPainter({this.offset = 0});
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderDanmakuTip(offset: offset);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderDanmakuTip renderObject) {
|
||||
renderObject.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderDanmakuTip extends RenderProxyBox {
|
||||
RenderDanmakuTip({
|
||||
required double offset,
|
||||
}) : _offset = offset;
|
||||
|
||||
double _offset;
|
||||
double get offset => _offset;
|
||||
set offset(double value) {
|
||||
if (_offset == value) return;
|
||||
_offset = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final paint = Paint()
|
||||
..color = const Color(0xB3000000)
|
||||
..style = PaintingStyle.fill;
|
||||
@@ -2851,7 +2822,7 @@ class _DanmakuTipPainter extends CustomPainter {
|
||||
final radius = size.height / 2;
|
||||
const triangleBase = _triangleHeight * 2 / 3;
|
||||
|
||||
final triangleCenterX = (size.width / 2 + offset).clamp(
|
||||
final triangleCenterX = (size.width / 2 + _offset).clamp(
|
||||
radius + triangleBase,
|
||||
size.width - radius - triangleBase,
|
||||
);
|
||||
@@ -2876,12 +2847,13 @@ class _DanmakuTipPainter extends CustomPainter {
|
||||
)
|
||||
..close();
|
||||
|
||||
canvas
|
||||
context.canvas
|
||||
..drawPath(path, paint)
|
||||
..drawPath(path, strokePaint);
|
||||
|
||||
super.paint(context, offset);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _DanmakuTipPainter oldDelegate) =>
|
||||
oldDelegate.offset != offset;
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user