Преглед на файлове

HTMLMesh: Add some more input types and support rounded rectangles. (#24030)

* Add some more input types and support rounded rectangles

* actually perform border check

* remove unneeded code

* s/roundRectPath/buildRectPath/

* range input handler

* Clean up.

* More clean up.

Co-authored-by: mrdoob <[email protected]>
Ada Rose Cannon преди 3 години
родител
ревизия
3ac055ce61
променени са 1 файла, в които са добавени 182 реда и са изтрити 15 реда
  1. 182 15
      examples/jsm/interactive/HTMLMesh.js

+ 182 - 15
examples/jsm/interactive/HTMLMesh.js

@@ -4,7 +4,8 @@ import {
 	Mesh,
 	MeshBasicMaterial,
 	PlaneGeometry,
-	sRGBEncoding
+	sRGBEncoding,
+	Color
 } from 'three';
 
 class HTMLMesh extends Mesh {
@@ -14,7 +15,7 @@ class HTMLMesh extends Mesh {
 		const texture = new HTMLTexture( dom );
 
 		const geometry = new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 );
-		const material = new MeshBasicMaterial( { map: texture, toneMapped: false } );
+		const material = new MeshBasicMaterial( { map: texture, toneMapped: false, transparent: true } );
 
 		super( geometry, material );
 
@@ -122,6 +123,7 @@ const canvases = new WeakMap();
 function html2canvas( element ) {
 
 	const range = document.createRange();
+	const color = new Color();
 
 	function Clipper( context ) {
 
@@ -192,15 +194,30 @@ function html2canvas( element ) {
 
 			}
 
-			context.font = style.fontSize + ' ' + style.fontFamily;
+			context.font = style.fontWeight + ' ' + style.fontSize + ' ' + style.fontFamily;
 			context.textBaseline = 'top';
 			context.fillStyle = style.color;
-			context.fillText( string, x, y );
+			context.fillText( string, x, y + parseFloat( style.fontSize ) * 0.1 );
 
 		}
 
 	}
 
+	function buildRectPath(x, y, w, h, r) {
+
+		if ( w < 2 * r ) r = w / 2;
+		if ( h < 2 * r ) r = h / 2;
+
+		context.beginPath();
+		context.moveTo( x + r, y );
+		context.arcTo( x + w, y, x + w, y + h, r );
+		context.arcTo( x + w, y + h, x, y + h, r );
+		context.arcTo( x, y + h, x, y, r );
+		context.arcTo( x, y, x + w, y, r );
+		context.closePath();
+
+	}
+
 	function drawBorder( style, which, x, y, width, height ) {
 
 		const borderWidth = style[ which + 'Width' ];
@@ -210,6 +227,7 @@ function html2canvas( element ) {
 		if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
 
 			context.strokeStyle = borderColor;
+			context.lineWidth = parseFloat( borderWidth );
 			context.beginPath();
 			context.moveTo( x, y );
 			context.lineTo( x + width, y + height );
@@ -249,8 +267,8 @@ function html2canvas( element ) {
 
 			context.save();
 			const dpr = window.devicePixelRatio;
-			context.scale(1/dpr, 1/dpr);
-			context.drawImage(element, 0, 0 );
+			context.scale( 1 / dpr, 1 / dpr );
+			context.drawImage( element, 0, 0 );
 			context.restore();
 
 		} else {
@@ -266,27 +284,164 @@ function html2canvas( element ) {
 
 			style = window.getComputedStyle( element );
 
+			// Get the border of the element used for fill and border
+
+			buildRectPath( x, y, width, height, parseFloat( style.borderRadius ) );
+
 			const backgroundColor = style.backgroundColor;
 
 			if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
 
 				context.fillStyle = backgroundColor;
-				context.fillRect( x, y, width, height );
+				context.fill();
 
 			}
 
-			drawBorder( style, 'borderTop', x, y, width, 0 );
-			drawBorder( style, 'borderLeft', x, y, 0, height );
-			drawBorder( style, 'borderBottom', x, y + height, width, 0 );
-			drawBorder( style, 'borderRight', x + width, y, 0, height );
+			// If all the borders match then stroke the round rectangle
+
+			const borders = [ 'borderTop', 'borderLeft', 'borderBottom', 'borderRight' ];
+
+			let match = true;
+			let prevBorder = null;
+
+			for ( const border of borders ) {
+
+				if ( prevBorder !== null ) {
+
+					match = ( style[ border + 'Width' ] === style[ prevBorder + 'Width' ] ) &&
+					( style[ border + 'Color' ] === style[ prevBorder + 'Color' ] ) &&
+					( style[ border + 'Style' ] === style[ prevBorder + 'Style' ] );
+
+				}
+
+				if ( match === false ) break;
+
+				prevBorder = border;
+
+			}
+
+			if ( match === true ) {
+
+				// They all match so stroke the rectangle from before allows for border-radius
+
+				const width = parseFloat( style.borderTopWidth );
+
+				if ( style.borderTopWidth !== '0px' && style.borderTopStyle !== 'none' && style.borderTopColor !== 'transparent' && style.borderTopColor !== 'rgba(0, 0, 0, 0)' ) {
+
+					context.strokeStyle = style.borderTopColor;
+					context.lineWidth = width;
+					context.stroke();
+
+				}
+
+			} else {
+
+				// Otherwise draw individual borders
+
+				drawBorder( style, 'borderTop', x, y, width, 0 );
+				drawBorder( style, 'borderLeft', x, y, 0, height );
+				drawBorder( style, 'borderBottom', x, y + height, width, 0 );
+				drawBorder( style, 'borderRight', x + width, y, 0, height );
+
+			}
+
+			if ( element instanceof HTMLInputElement ) {
+
+				let accentColor = style.accentColor;
+
+				if ( accentColor === undefined || accentColor === 'auto' ) accentColor = style.color;
 
-			if ( element.type === 'color' || element.type === 'text' || element.type === 'number' ) {
+				color.set( accentColor );
 
-				clipper.add( { x: x, y: y, width: width, height: height } );
+				const luminance = Math.sqrt( 0.299 * ( color.r ** 2 ) + 0.587 * ( color.g ** 2 ) + 0.114 * ( color.b ** 2 ) );
+				const accentTextColor = luminance < 0.5 ? 'white' : '#111111';
 
-				drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value );
+				if ( element.type  === 'radio' ) {
 
-				clipper.remove();
+					buildRectPath( x, y, width, height, height );
+
+					context.fillStyle = 'white';
+					context.strokeStyle = accentColor;
+					context.lineWidth = 1;
+					context.fill();
+					context.stroke();
+
+					if ( element.checked ) {
+
+						buildRectPath( x + 2, y + 2, width - 4, height - 4, height );
+
+						context.fillStyle = accentColor;
+						context.strokeStyle = accentTextColor;
+						context.lineWidth = 2;
+						context.fill();
+						context.stroke();
+
+					}
+
+				}
+
+				if ( element.type  === 'checkbox' ) {
+
+					buildRectPath( x, y, width, height, 2 );
+
+					context.fillStyle = element.checked ? accentColor : 'white';
+					context.strokeStyle = element.checked ? accentTextColor : accentColor;
+					context.lineWidth = 1;
+					context.stroke();
+					context.fill();
+
+					if ( element.checked ) {
+
+						const currentTextAlign = context.textAlign;
+
+						context.textAlign = 'center';
+						
+						const properties = {
+							color: accentTextColor,
+							fontFamily: style.fontFamily,
+							fontSize: height + 'px',
+							fontWeight: 'bold'
+						};
+
+						drawText( properties, x + ( width / 2 ), y, '✔' );
+
+						context.textAlign = currentTextAlign;
+
+					}
+
+				}
+
+				if ( element.type  === 'range' ) {
+
+					const [min,max,value] = ['min','max','value'].map(property => parseFloat(element[property]));
+					const position = ((value-min)/(max-min)) * (width - height);
+
+					buildRectPath( x, y + ( height / 4 ), width, height / 2, height / 4 );
+					context.fillStyle = accentTextColor;
+					context.strokeStyle = accentColor;
+					context.lineWidth = 1;
+					context.fill();
+					context.stroke();
+
+					buildRectPath( x, y + ( height / 4 ),position + ( height / 2 ), height / 2, height / 4 );
+					context.fillStyle = accentColor;
+					context.fill();
+
+					buildRectPath( x + position, y, height, height, height / 2 );
+					context.fillStyle = accentColor;
+					context.fill();
+
+				}
+
+				if ( element.type === 'color' || element.type === 'text' || element.type === 'number' ) {
+
+					clipper.add( { x: x, y: y, width: width, height: height } );
+
+					drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value );
+
+					clipper.remove();
+
+				}
 
 			}
 
@@ -367,6 +522,18 @@ function htmlevent( element, event, x, y ) {
 
 				element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
 
+				if ( element instanceof HTMLInputElement && element.type  === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
+
+					const [ min, max ] = [ 'min', 'max' ].map( property => parseFloat( element[ property ] ) );
+
+					const width = rect.width;
+					const offsetX = x - rect.x;
+					const proportion = offsetX / width;
+					element.value = min + ( max - min ) * proportion;
+					element.dispatchEvent( new InputEvent( 'input', { bubbles: true } ) );
+
+				}
+
 			}
 
 			for ( let i = 0; i < element.childNodes.length; i ++ ) {