cpdf_slider_theme.dart 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'dart:ui' as ui;
  4. import 'dart:math' as math;
  5. /// Copyright © 2014-2023 PDF Technologies, Inc. All Rights Reserved.
  6. ///
  7. /// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
  8. /// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT.
  9. /// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
  10. /// This notice may not be removed from this file.
  11. class CPDFImageSliderThumbShape extends SliderComponentShape {
  12. final double imageWidth;
  13. final double imageHeight;
  14. final ui.Image image;
  15. CPDFImageSliderThumbShape({
  16. this.imageWidth = 20,
  17. this.imageHeight = 20,
  18. required this.image,
  19. });
  20. @override
  21. Size getPreferredSize(bool isEnabled, bool isDiscrete) {
  22. return Size(imageWidth, imageHeight);
  23. }
  24. @override
  25. void paint(
  26. PaintingContext context,
  27. Offset center, {
  28. Animation<double>? activationAnimation,
  29. Animation<double>? enableAnimation,
  30. bool? isDiscrete,
  31. TextPainter? labelPainter,
  32. RenderBox? parentBox,
  33. SliderThemeData? sliderTheme,
  34. TextDirection? textDirection,
  35. double? value,
  36. double? textScaleFactor,
  37. Size? sizeWithOverflow,
  38. }) {
  39. final canvas = context.canvas;
  40. final imageOffset =
  41. Offset(center.dx - imageWidth / 2, center.dy - imageHeight / 2);
  42. final double targetWidth = imageWidth;
  43. final double targetHeight = imageHeight;
  44. final double scaleX = targetWidth / image.width.toDouble();
  45. final double scaleY = targetHeight / image.height.toDouble();
  46. final double scale = scaleX < scaleY ? scaleX : scaleY;
  47. final double destWidth = image.width.toDouble() * scale;
  48. final double destHeight = image.height.toDouble() * scale;
  49. final paint = Paint();
  50. final srcRect =
  51. Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
  52. final dstRect =
  53. Rect.fromLTWH(imageOffset.dx, imageOffset.dy, destWidth, destHeight);
  54. canvas.drawImageRect(image, srcRect, dstRect, paint);
  55. }
  56. }
  57. enum CPDFSliderIndicatorPosition { top, left, right, bottom }
  58. class CPDFPageSliderValueIndicatorShape extends SliderComponentShape {
  59. CPDFSliderIndicatorPosition position;
  60. late _RectangularSliderValueIndicatorPathPainter _pathPainter;
  61. CPDFPageSliderValueIndicatorShape({required this.position}) {
  62. _pathPainter =
  63. _RectangularSliderValueIndicatorPathPainter(position: position);
  64. }
  65. @override
  66. Size getPreferredSize(
  67. bool isEnabled,
  68. bool isDiscrete, {
  69. TextPainter? labelPainter,
  70. double? textScaleFactor,
  71. }) {
  72. assert(labelPainter != null);
  73. assert(textScaleFactor != null && textScaleFactor >= 0);
  74. return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
  75. }
  76. @override
  77. void paint(
  78. PaintingContext context,
  79. Offset center, {
  80. required Animation<double> activationAnimation,
  81. required Animation<double> enableAnimation,
  82. required bool isDiscrete,
  83. required TextPainter labelPainter,
  84. required RenderBox parentBox,
  85. required SliderThemeData sliderTheme,
  86. required TextDirection textDirection,
  87. required double value,
  88. required double textScaleFactor,
  89. required Size sizeWithOverflow,
  90. }) {
  91. final Canvas canvas = context.canvas;
  92. final double scale = activationAnimation.value;
  93. _pathPainter.paint(
  94. parentBox: parentBox,
  95. canvas: canvas,
  96. center: center,
  97. scale: scale,
  98. labelPainter: labelPainter,
  99. textScaleFactor: textScaleFactor,
  100. sizeWithOverflow: sizeWithOverflow,
  101. backgroundPaintColor: sliderTheme.valueIndicatorColor!,
  102. );
  103. }
  104. }
  105. class _RectangularSliderValueIndicatorPathPainter {
  106. CPDFSliderIndicatorPosition position;
  107. _RectangularSliderValueIndicatorPathPainter({required this.position});
  108. static const double _labelPadding = 8.0;
  109. static const double _minLabelWidth = 32.0;
  110. static const double _bottomTipYOffset = 24.0;
  111. static const double _upperRectRadius = 4;
  112. Size getPreferredSize(
  113. TextPainter labelPainter,
  114. double textScaleFactor,
  115. ) {
  116. return Size(
  117. _upperRectangleWidth(labelPainter, 1, textScaleFactor),
  118. labelPainter.height + _labelPadding,
  119. );
  120. }
  121. double getHorizontalShift({
  122. required RenderBox parentBox,
  123. required Offset center,
  124. required TextPainter labelPainter,
  125. required double textScaleFactor,
  126. required Size sizeWithOverflow,
  127. required double scale,
  128. }) {
  129. assert(!sizeWithOverflow.isEmpty);
  130. const double edgePadding = 16.0;
  131. final double rectangleWidth =
  132. _upperRectangleWidth(labelPainter, scale, textScaleFactor);
  133. /// Value indicator draws on the Overlay and by using the global Offset
  134. /// we are making sure we use the bounds of the Overlay instead of the Slider.
  135. final Offset globalCenter = parentBox.localToGlobal(center);
  136. // The rectangle must be shifted towards the center so that it minimizes the
  137. // chance of it rendering outside the bounds of the render box. If the shift
  138. // is negative, then the lobe is shifted from right to left, and if it is
  139. // positive, then the lobe is shifted from left to right.
  140. final double overflowLeft =
  141. math.max(0, rectangleWidth / 2 - globalCenter.dx + edgePadding);
  142. final double overflowRight = math.max(
  143. 0,
  144. rectangleWidth / 2 -
  145. (sizeWithOverflow.width - globalCenter.dx - edgePadding));
  146. if (rectangleWidth < sizeWithOverflow.width) {
  147. return overflowLeft - overflowRight;
  148. } else if (overflowLeft - overflowRight > 0) {
  149. return overflowLeft - (edgePadding * textScaleFactor);
  150. } else {
  151. return -overflowRight + (edgePadding * textScaleFactor);
  152. }
  153. }
  154. double _upperRectangleWidth(
  155. TextPainter labelPainter, double scale, double textScaleFactor) {
  156. final double unscaledWidth =
  157. math.max(_minLabelWidth * textScaleFactor, labelPainter.width) +
  158. _labelPadding * 2;
  159. return unscaledWidth * scale;
  160. }
  161. void paint({
  162. required RenderBox parentBox,
  163. required Canvas canvas,
  164. required Offset center,
  165. required double scale,
  166. required TextPainter labelPainter,
  167. required double textScaleFactor,
  168. required Size sizeWithOverflow,
  169. required Color backgroundPaintColor,
  170. Color? strokePaintColor,
  171. }) {
  172. if (scale == 0.0) {
  173. // Zero scale essentially means "do not draw anything", so it's safe to just return.
  174. return;
  175. }
  176. assert(!sizeWithOverflow.isEmpty);
  177. final double rectangleWidth =
  178. _upperRectangleWidth(labelPainter, scale, textScaleFactor);
  179. final double horizontalShift = getHorizontalShift(
  180. parentBox: parentBox,
  181. center: center,
  182. labelPainter: labelPainter,
  183. textScaleFactor: textScaleFactor,
  184. sizeWithOverflow: sizeWithOverflow,
  185. scale: scale,
  186. );
  187. final double rectHeight = labelPainter.height + _labelPadding;
  188. final Rect upperRect = Rect.fromLTWH(
  189. -rectangleWidth / 2 + horizontalShift,
  190. -rectHeight,
  191. rectangleWidth,
  192. rectHeight,
  193. );
  194. final Path trianglePath = Path()..close();
  195. final Paint fillPaint = Paint()..color = backgroundPaintColor;
  196. final RRect upperRRect = RRect.fromRectAndRadius(
  197. upperRect, const Radius.circular(_upperRectRadius));
  198. trianglePath.addRRect(upperRRect);
  199. canvas.save();
  200. // Prepare the canvas for the base of the tooltip, which is relative to the
  201. // center of the thumb.
  202. switch (position) {
  203. case CPDFSliderIndicatorPosition.top:
  204. canvas.translate(center.dx, center.dy + _bottomTipYOffset * 2);
  205. canvas.scale(scale, scale);
  206. break;
  207. case CPDFSliderIndicatorPosition.bottom:
  208. canvas.translate(center.dx, center.dy - _bottomTipYOffset);
  209. canvas.scale(scale, scale);
  210. break;
  211. case CPDFSliderIndicatorPosition.left:
  212. final double bottomPadding =
  213. parentBox.size.height / 2 + _bottomTipYOffset;
  214. canvas.translate(
  215. center.dx + horizontalShift / 2, center.dy - bottomPadding / 2);
  216. canvas.scale(scale, scale);
  217. canvas.rotate(-(math.pi / 2));
  218. break;
  219. case CPDFSliderIndicatorPosition.right:
  220. final double bottomPadding =
  221. parentBox.size.height / 2 + _bottomTipYOffset;
  222. canvas.translate(
  223. center.dx - horizontalShift / 2, center.dy + bottomPadding / 2);
  224. canvas.scale(scale, scale);
  225. canvas.rotate(-(math.pi / 2));
  226. break;
  227. }
  228. canvas.save();
  229. if (strokePaintColor != null) {
  230. final Paint strokePaint = Paint()
  231. ..color = strokePaintColor
  232. ..strokeWidth = 1.0
  233. ..style = PaintingStyle.stroke;
  234. canvas.drawPath(trianglePath, strokePaint);
  235. }
  236. canvas.drawPath(trianglePath, fillPaint);
  237. // The label text is centered within the value indicator.
  238. final double bottomTipToUpperRectTranslateY = -upperRect.height;
  239. canvas.translate(0, bottomTipToUpperRectTranslateY);
  240. final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2);
  241. final Offset halfLabelPainterOffset =
  242. Offset(labelPainter.width / 2, labelPainter.height / 2);
  243. final Offset labelOffset = boxCenter - halfLabelPainterOffset;
  244. labelPainter.paint(canvas, labelOffset);
  245. canvas.restore();
  246. }
  247. }