import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:ui' as ui; import 'dart:math' as math; /// Copyright © 2014-2023 PDF Technologies, Inc. All Rights Reserved. /// /// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW /// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT. /// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. /// This notice may not be removed from this file. class CPDFImageSliderThumbShape extends SliderComponentShape { final double imageWidth; final double imageHeight; final ui.Image image; CPDFImageSliderThumbShape({ this.imageWidth = 20, this.imageHeight = 20, required this.image, }); @override Size getPreferredSize(bool isEnabled, bool isDiscrete) { return Size(imageWidth, imageHeight); } @override void paint( PaintingContext context, Offset center, { Animation? activationAnimation, Animation? enableAnimation, bool? isDiscrete, TextPainter? labelPainter, RenderBox? parentBox, SliderThemeData? sliderTheme, TextDirection? textDirection, double? value, double? textScaleFactor, Size? sizeWithOverflow, }) { final canvas = context.canvas; final imageOffset = Offset(center.dx - imageWidth / 2, center.dy - imageHeight / 2); final double targetWidth = imageWidth; final double targetHeight = imageHeight; final double scaleX = targetWidth / image.width.toDouble(); final double scaleY = targetHeight / image.height.toDouble(); final double scale = scaleX < scaleY ? scaleX : scaleY; final double destWidth = image.width.toDouble() * scale; final double destHeight = image.height.toDouble() * scale; final paint = Paint(); final srcRect = Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()); final dstRect = Rect.fromLTWH(imageOffset.dx, imageOffset.dy, destWidth, destHeight); canvas.drawImageRect(image, srcRect, dstRect, paint); } } enum CPDFSliderIndicatorPosition { top, left, right, bottom } class CPDFPageSliderValueIndicatorShape extends SliderComponentShape { CPDFSliderIndicatorPosition position; late _RectangularSliderValueIndicatorPathPainter _pathPainter; CPDFPageSliderValueIndicatorShape({required this.position}) { _pathPainter = _RectangularSliderValueIndicatorPathPainter(position: position); } @override Size getPreferredSize( bool isEnabled, bool isDiscrete, { TextPainter? labelPainter, double? textScaleFactor, }) { assert(labelPainter != null); assert(textScaleFactor != null && textScaleFactor >= 0); return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!); } @override void paint( PaintingContext context, Offset center, { required Animation activationAnimation, required Animation enableAnimation, required bool isDiscrete, required TextPainter labelPainter, required RenderBox parentBox, required SliderThemeData sliderTheme, required TextDirection textDirection, required double value, required double textScaleFactor, required Size sizeWithOverflow, }) { final Canvas canvas = context.canvas; final double scale = activationAnimation.value; _pathPainter.paint( parentBox: parentBox, canvas: canvas, center: center, scale: scale, labelPainter: labelPainter, textScaleFactor: textScaleFactor, sizeWithOverflow: sizeWithOverflow, backgroundPaintColor: sliderTheme.valueIndicatorColor!, ); } } class _RectangularSliderValueIndicatorPathPainter { CPDFSliderIndicatorPosition position; _RectangularSliderValueIndicatorPathPainter({required this.position}); static const double _labelPadding = 8.0; static const double _minLabelWidth = 32.0; static const double _bottomTipYOffset = 24.0; static const double _upperRectRadius = 4; Size getPreferredSize( TextPainter labelPainter, double textScaleFactor, ) { return Size( _upperRectangleWidth(labelPainter, 1, textScaleFactor), labelPainter.height + _labelPadding, ); } double getHorizontalShift({ required RenderBox parentBox, required Offset center, required TextPainter labelPainter, required double textScaleFactor, required Size sizeWithOverflow, required double scale, }) { assert(!sizeWithOverflow.isEmpty); const double edgePadding = 16.0; final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor); /// Value indicator draws on the Overlay and by using the global Offset /// we are making sure we use the bounds of the Overlay instead of the Slider. final Offset globalCenter = parentBox.localToGlobal(center); // The rectangle must be shifted towards the center so that it minimizes the // chance of it rendering outside the bounds of the render box. If the shift // is negative, then the lobe is shifted from right to left, and if it is // positive, then the lobe is shifted from left to right. final double overflowLeft = math.max(0, rectangleWidth / 2 - globalCenter.dx + edgePadding); final double overflowRight = math.max( 0, rectangleWidth / 2 - (sizeWithOverflow.width - globalCenter.dx - edgePadding)); if (rectangleWidth < sizeWithOverflow.width) { return overflowLeft - overflowRight; } else if (overflowLeft - overflowRight > 0) { return overflowLeft - (edgePadding * textScaleFactor); } else { return -overflowRight + (edgePadding * textScaleFactor); } } double _upperRectangleWidth( TextPainter labelPainter, double scale, double textScaleFactor) { final double unscaledWidth = math.max(_minLabelWidth * textScaleFactor, labelPainter.width) + _labelPadding * 2; return unscaledWidth * scale; } void paint({ required RenderBox parentBox, required Canvas canvas, required Offset center, required double scale, required TextPainter labelPainter, required double textScaleFactor, required Size sizeWithOverflow, required Color backgroundPaintColor, Color? strokePaintColor, }) { if (scale == 0.0) { // Zero scale essentially means "do not draw anything", so it's safe to just return. return; } assert(!sizeWithOverflow.isEmpty); final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor); final double horizontalShift = getHorizontalShift( parentBox: parentBox, center: center, labelPainter: labelPainter, textScaleFactor: textScaleFactor, sizeWithOverflow: sizeWithOverflow, scale: scale, ); final double rectHeight = labelPainter.height + _labelPadding; final Rect upperRect = Rect.fromLTWH( -rectangleWidth / 2 + horizontalShift, -rectHeight, rectangleWidth, rectHeight, ); final Path trianglePath = Path()..close(); final Paint fillPaint = Paint()..color = backgroundPaintColor; final RRect upperRRect = RRect.fromRectAndRadius( upperRect, const Radius.circular(_upperRectRadius)); trianglePath.addRRect(upperRRect); canvas.save(); // Prepare the canvas for the base of the tooltip, which is relative to the // center of the thumb. switch (position) { case CPDFSliderIndicatorPosition.top: canvas.translate(center.dx, center.dy + _bottomTipYOffset * 2); canvas.scale(scale, scale); break; case CPDFSliderIndicatorPosition.bottom: canvas.translate(center.dx, center.dy - _bottomTipYOffset); canvas.scale(scale, scale); break; case CPDFSliderIndicatorPosition.left: final double bottomPadding = parentBox.size.height / 2 + _bottomTipYOffset; canvas.translate( center.dx + horizontalShift / 2, center.dy - bottomPadding / 2); canvas.scale(scale, scale); canvas.rotate(-(math.pi / 2)); break; case CPDFSliderIndicatorPosition.right: final double bottomPadding = parentBox.size.height / 2 + _bottomTipYOffset; canvas.translate( center.dx - horizontalShift / 2, center.dy + bottomPadding / 2); canvas.scale(scale, scale); canvas.rotate(-(math.pi / 2)); break; } canvas.save(); if (strokePaintColor != null) { final Paint strokePaint = Paint() ..color = strokePaintColor ..strokeWidth = 1.0 ..style = PaintingStyle.stroke; canvas.drawPath(trianglePath, strokePaint); } canvas.drawPath(trianglePath, fillPaint); // The label text is centered within the value indicator. final double bottomTipToUpperRectTranslateY = -upperRect.height; canvas.translate(0, bottomTipToUpperRectTranslateY); final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2); final Offset halfLabelPainterOffset = Offset(labelPainter.width / 2, labelPainter.height / 2); final Offset labelOffset = boxCenter - halfLabelPainterOffset; labelPainter.paint(canvas, labelOffset); canvas.restore(); } }