User Tools

Site Tools


script_shape_background

About the script

How to use the script

Still in development, but the main feature will be that everything will be customizable with an internal GUI

  1. Set the script in the category “Lightning menu” and “Item menu” then long-press on any container (panel was tested) or shortcut and run the script.
  2. A dialog will appear that will guide you further.
  3. Remember to scroll (if the preview image is too big) for the buttons

Known issue: can't import an exported string

Script code


// v1.1.test6
function DrawData()
{
	this.shapeArr = [];
	this.width = -1;
	this.height = -1;
}
var enumShapeType = {
	RECTANGLE: 1,
	OVAL: 2,
	ARC: 3,
	ROUND_RECT: 4,
	properties: {
		1: {name: 'Rectangle'},
		2: {name: 'Oval'},
		3: {name: 'Arc'},
		4: {name: 'Round rectangle'},
	},
	findByName: function( name )
	{
		for ( var key in enumShapeType.properties )
		{
			if ( enumShapeType.properties[key].name == name )
				return parseInt(key);
		}
		return enumShapeType.RECTANGLE; // should never happen
	},
	getNameArr: function()
	{
		return [
			enumShapeType.properties[enumShapeType.RECTANGLE].name,
			enumShapeType.properties[enumShapeType.ROUND_RECT].name,
			enumShapeType.properties[enumShapeType.OVAL].name,
			enumShapeType.properties[enumShapeType.ARC].name
		];
	},
	getIdx: function( shapeType )
	{
		var arr = this.getNameArr();
		for ( var i = 0; i < arr.length; i+= 1 )
			if ( shapeType == this.findByName(arr[i]) )
				return i;
		return 0; // should never happen
	}
};
function ShapeInfo()
{
	this.name = "shape";
	this.type = enumShapeType.OVAL;
	this.color = 0x80ff0000;
	this.width = -1;
	this.height = -1;
	this.offsetX = 0;
	this.offsetY = 0;
	this.fill = true;
	// for arc
	this.startAngle = 270;
	this.sweepAngle = 45;
	// for round corners
	this.cornerX = 10;
	this.cornerY = 20;
	
	this.validate = function( c )
	{
		// if not all properties found, copy them to "c"
		for( var prop in this )
		{
			if ( (typeof this[prop] == 'boolean') || (typeof this[prop] == 'number') || (typeof this[prop] == 'string') )
			{
				if ( (typeof c[prop] == 'undefined') || (typeof c[prop] == 'object') )
					c[prop] = this[prop];
			}
		}
		if ( c.width < 0 )
			c.width = this.width;
		if ( c.height < 0 )
			c.height = this.height;
	};
}
function empty( mixedVar )
{
	/*discuss at: http://locutus.io/php/empty/ */
	var undef, key, i, len;
	var	emptyValues = [undef, null, false, 0, '', '0'];
	for ( i = 0, len = emptyValues.length; i < len; i++ )
	{
		if ( mixedVar === emptyValues[i] )
		{
			return true;
		}
	}
	if ( typeof mixedVar === 'object' )
	{
		for ( key in mixedVar )
		{
			if ( Object.hasOwnProperty.call(mixedVar, key) )
			{
				return false;
			}
		}
		return true;
	}
	return false;
}

function drawShapes( canvas, shapeArr )
{
	bindClass( 'android.graphics.Paint' );
	
	var w = canvas.getWidth();
	var h = canvas.getHeight();
	for ( var i = 0; i < shapeArr.length; i+= 1 )
	{
		var shapeInfo = shapeArr[i];
		var paint = new Paint( Paint.ANTI_ALIAS_FLAG );
		paint.setColor( shapeInfo.color );
		if ( shapeInfo.fill )
			paint.setStyle( Paint.Style.FILL_AND_STROKE );
		else
			paint.setStyle( Paint.Style.STROKE );
		var shapeWidth = (shapeInfo.width >= 0) ? shapeInfo.width : w;
		var shapeHeight = (shapeInfo.height >= 0) ? shapeInfo.height : h;
		switch( parseInt(shapeInfo.type) )
		{
			case enumShapeType.RECTANGLE:
				canvas.drawRect( shapeInfo.offsetX, shapeInfo.offsetY, shapeInfo.offsetX + shapeWidth, shapeInfo.offsetY + shapeHeight, paint );
				break;
			case enumShapeType.OVAL:
				canvas.drawOval( shapeInfo.offsetX, shapeInfo.offsetY, shapeInfo.offsetX + shapeWidth, shapeInfo.offsetY + shapeHeight, paint );
				break;
			case enumShapeType.ARC:
				canvas.drawArc( shapeInfo.offsetX, shapeInfo.offsetY, shapeInfo.offsetX + shapeWidth, shapeInfo.offsetY + shapeHeight, shapeInfo.startAngle, shapeInfo.sweepAngle, true, paint );
				break;
			case enumShapeType.ROUND_RECT:
				canvas.drawRoundRect( shapeInfo.offsetX, shapeInfo.offsetY, shapeInfo.offsetX + shapeWidth, shapeInfo.offsetY + shapeHeight, shapeInfo.cornerX, shapeInfo.cornerY, paint );
				break;
			default:
				Android.makeNewToast( 'Unknown shape type "' + shapeInfo.type + '"', true ).show();
		}
	}
}

var g_cache = {itemId: null, data: null};
function getDrawData( item )
{
	if ( g_cache.itemId === item.getId() )
		return g_cache.data;
	var jsonData = item.getTag('tbImgData');
	if ( empty( jsonData ) )
	{
		g_cache.data = new DrawData();
	}
	else
	{
		var data = JSON.parse( jsonData );
		if ( empty( data ) )
		{
			g_cache.data = new DrawData();
		}
		else
		{
			g_cache.data = data;
		}
	}
	g_cache.itemId = item.getId();
	
	// hack to initialize with item size
	if ( g_cache.data.width <= 0 )
		g_cache.data.width = item.getWidth();
	if ( g_cache.data.height <= 0 )
		g_cache.data.height = item.getHeight();
	
	// validate shapes
	var defaultShape = new ShapeInfo();
	defaultShape.width = g_cache.data.width;
	defaultShape.height = g_cache.data.height;
	for ( var i = 0; i < g_cache.data.shapeArr.length; i+= 1 )
	{
		defaultShape.validate( g_cache.data.shapeArr[i] );
	}
	return g_cache.data;
}

function setDrawData( item, data )
{
	if ( empty(data) )
		item.setTag( 'tbImgData', null );
	else
		item.setTag( 'tbImgData', JSON.stringify(data) );
	g_cache.itemId = item.getId();
	g_cache.data = data;
}

function showEditShape( item, shapeIdx )
{
	bindClass( 'android.app.AlertDialog' );
	bindClass( 'net.pierrox.lightning_launcher.prefs.LLPreferenceListView' );
	bindClass( 'net.pierrox.lightning_launcher.prefs.LLPreferenceText' );
	bindClass( 'net.pierrox.lightning_launcher.prefs.LLPreferenceList' );
	bindClass( 'net.pierrox.lightning_launcher.prefs.LLPreferenceColor' );
	bindClass( 'net.pierrox.lightning_launcher.prefs.LLPreferenceSlider' );
	bindClass( 'net.pierrox.lightning_launcher.prefs.LLPreferenceCheckBox' );
	
	var drawData = getDrawData( item );
	var shapeInfo = drawData.shapeArr[shapeIdx];
	
	var ctx = getActiveScreen().getContext();
	var builder = new AlertDialog.Builder( ctx, R.style.Theme_Material_Dialog );
	var ctxTheme = new ContextThemeWrapper( ctx, R.style.Theme_Material_Dialog );
	
	var prefName = new LLPreferenceText( 0, 'Name', shapeInfo.name, 'shape' );
	var prefType = new LLPreferenceList( 0, 'Shape', enumShapeType.getNameArr(), enumShapeType.getIdx(shapeInfo.type), enumShapeType.OVAL );
	var prefColor = new LLPreferenceColor( 0, 'Color', null, shapeInfo.color, 0xFFff0000, true );
	var prefWidth = new LLPreferenceSlider(0, 'Width', 'pixel or -1', shapeInfo.width, -1, 'INT', 0, 512, 1, 'px');
	var prefHeight = new LLPreferenceSlider(0, 'Height', 'pixel or -1', shapeInfo.height, -1, 'INT', 0, 512, 1, 'px');
	var prefOffX = new LLPreferenceSlider(0, 'Offset X', 'pixel', shapeInfo.offsetX, 0, 'INT', 0, 512, 1, 'px');
	var prefOffY = new LLPreferenceSlider(0, 'Offset Y', 'pixel', shapeInfo.offsetY, 0, 'INT', 0, 512, 1, 'px');
	var prefCornerRadX = new LLPreferenceSlider(0, 'Corner radius X', 'pixel', shapeInfo.cornerX, 0, 'INT', 0, 512, 1, 'px');
	var prefCornerRadY = new LLPreferenceSlider(0, 'Corner radius Y', 'pixel', shapeInfo.cornerY, 0, 'INT', 0, 512, 1, 'px');
	var prefStartAng = new LLPreferenceSlider(0, 'Start angle', 'degree', shapeInfo.startAngle, 0, 'INT', 0, 360, 1, 'deg');
	var prefSweepAng = new LLPreferenceSlider(0, 'Sweep angle', 'degree', shapeInfo.sweepAngle, 0, 'INT', 0, 360, 1, 'deg');
	var prefFill = new LLPreferenceCheckBox( 0, 'Fill', null, shapeInfo.fill, null );

	var prefList = new LLPreferenceListView( ctxTheme, null );
	prefList.setCompactMode(true);
	var prefListener =
	{
		onLLPreferenceChanged: function( pref )
		{
			if ( pref == prefType )
			{
				var type = enumShapeType.findByName( prefType.getLabels()[prefType.getValueIndex()] );
				switch ( type )
				{
					case enumShapeType.RECTANGLE:
					case enumShapeType.OVAL:
						prefStartAng.setVisible(false);
						prefSweepAng.setVisible(false);

						prefCornerRadX.setVisible(false);
						prefCornerRadY.setVisible(false);
						break;
					case enumShapeType.ARC:
						prefStartAng.setVisible(true);
						prefSweepAng.setVisible(true);

						prefCornerRadX.setVisible(false);
						prefCornerRadY.setVisible(false);
						break;
					case enumShapeType.ROUND_RECT:
						prefStartAng.setVisible(false);
						prefSweepAng.setVisible(false);

						prefCornerRadX.setVisible(true);
						prefCornerRadY.setVisible(true);
						break;
				}
				prefList.refresh();
			}
		},
		onLLPreferenceClicked: function( pref )
		{
		},
		onLLPreferenceLongClicked: function( pref )
		{
		}
	};
	prefList.setListener( prefListener );
	
	prefList.setPreferences( [
		prefName,
		prefType,
		prefColor,
		prefWidth,
		prefHeight,
		prefOffX,
		prefOffY,
		prefCornerRadX,
		prefCornerRadY,
		prefStartAng,
		prefSweepAng,
		prefFill
	] );
	prefListener.onLLPreferenceChanged( prefType );

	builder.setView( prefList );
	builder.setTitle( 'Edit shape #' + shapeIdx );
	var dialog;
	
	builder.setCancelable( false );
	builder.setNeutralButton( 'Close',
	{
		onClick: function( dialog, id )
		{
			dialog.dismiss();
			var time = setTimeout(function(){clearTimeout(time); showShapeMenu(item);}, 0);
		}
	} );
	builder.setPositiveButton( 'Save',
	{
		onClick: function( dialog, id )
		{
			var drawData = getDrawData( item );
			var shapeInfo = drawData.shapeArr[shapeIdx];
			
			shapeInfo.name = prefName.getValue();
			shapeInfo.type = enumShapeType.findByName( prefType.getLabels()[prefType.getValueIndex()] );
			shapeInfo.color = prefColor.getColor();
			shapeInfo.width = prefWidth.getValue();
			shapeInfo.height = prefHeight.getValue();
			shapeInfo.offsetX = prefOffX.getValue();
			shapeInfo.offsetY = prefOffY.getValue();
			shapeInfo.fill = prefFill.isChecked();
			shapeInfo.startAngle = prefStartAng.getValue();
			shapeInfo.sweepAngle = prefSweepAng.getValue();
			shapeInfo.cornerX = prefCornerRadX.getValue();
			shapeInfo.cornerY = prefCornerRadY.getValue();
			
			setDrawData( item, drawData );
			dialog.dismiss();
			var time = setTimeout(function(){clearTimeout(time); showShapeMenu(item);}, 0);
		}
	} );
	dialog = builder.create();
	dialog.show();
}

function showShapeMenu( item )
{
	bindClass( 'android.app.AlertDialog' );
	bindClass( 'android.content.DialogInterface' );
	bindClass( 'android.text.Html' );
	bindClass( 'android.text.InputType' );
	bindClass( 'android.view.View' );
	bindClass( 'android.view.ViewGroup' );
	bindClass( 'android.view.ViewGroup.LayoutParams' );
	bindClass( 'android.view.ContextThemeWrapper' );
	bindClass( 'android.widget.ListView' );
	bindClass( 'android.widget.LinearLayout' );
	bindClass( 'android.widget.LinearLayout.LayoutParams' );
	bindClass( 'android.widget.ScrollView' );
	bindClass( 'android.widget.TextView' );
	bindClass( 'android.widget.EditText' );
	bindClass( 'android.widget.ImageView' );
	bindClass( 'android.widget.Button' );
	bindClass( 'android.graphics.Bitmap' );
	bindClass( 'android.graphics.Canvas' );
	bindClass( 'android.graphics.drawable.BitmapDrawable' );
	bindClass( 'android.R' );

	var drawData = getDrawData( item );
	
	var ctx = getActiveScreen().getContext();
	var builder = new AlertDialog.Builder( ctx, R.style.Theme_Material_Dialog );
	var ctxTheme = new ContextThemeWrapper( ctx, R.style.Theme_Material_Dialog );
	var rootView = new ScrollView( ctxTheme );
	var linearView = new LinearLayout( ctxTheme );
	
	rootView.addView( linearView );
	builder.setView( rootView );
	builder.setTitle( 'Shape list' );
	var dialog;
	
	linearView.setOrientation( LinearLayout.VERTICAL );
	
	var shapeInfoLayout, shapeName, shapeRemove, shapeEdit;
	for ( var i = 0; i < drawData.shapeArr.length; i+= 1 )
	{
		var shapeInfo = drawData.shapeArr[i];
		if ( empty(shapeInfo) )
		{
			drawData.shapeArr.splice(i--, 1);
			continue;
		}
		linearView.addView( shapeInfoLayout = new LinearLayout( ctxTheme ) );
		
		shapeInfoLayout.addView( shapeName = new TextView( ctxTheme ), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0) );
		shapeName.setText( Html.fromHtml('<b>' + i + '</b>. ' + shapeInfo.name) );
		
		shapeInfoLayout.addView( shapeEdit = new ImageView( ctxTheme ) );
		shapeEdit.setImageResource( R.drawable.ic_menu_edit );
		shapeEdit.setTag(i);
		shapeEdit.setOnClickListener( new View.OnClickListener()
		{
			onClick: function( view )
			{
				var idx = parseInt( view.getTag() );
				var drawData = getDrawData( item );
				if ( idx >= drawData.shapeArr.length )
				{
					Android.makeNewToast( 'Failed to edit shape #' + idx, true ).show();
				}

				Android.makeNewToast( 'Edit shape #' + idx, true ).show();
				dialog.dismiss();
				var time = setTimeout(function(){clearTimeout(time); showEditShape(item, idx);}, 0);
			}
		} );

		shapeInfoLayout.addView( shapeRemove = new ImageView( ctxTheme ) );
		shapeRemove.setImageResource( R.drawable.ic_menu_delete );
		shapeRemove.setTag(i);
		shapeRemove.setOnClickListener( new View.OnClickListener()
		{
			onClick: function( view )
			{
				var idx = parseInt( view.getTag() );
				var drawData = getDrawData( item );
				if ( idx >= drawData.shapeArr.length )
				{
					Android.makeNewToast( 'Failed to remove shape #' + idx, true ).show();
				}
				delete drawData.shapeArr[idx];
				Android.makeNewToast( 'Removed shape #' + idx, true ).show();
				setDrawData(item, drawData);
				dialog.dismiss();
				var time = setTimeout(function(){clearTimeout(time); showShapeMenu(item);}, 0);
			}
		} );
	}
	
	var shapeAdd;
	linearView.addView( shapeInfoLayout = new LinearLayout( ctxTheme ) );
	shapeInfoLayout.addView( shapeAdd = new ImageView( ctxTheme ) );
	shapeAdd.setImageResource( R.drawable.ic_menu_add );
	shapeAdd.setOnClickListener( new View.OnClickListener()
	{
		onClick: function( view )
		{
			var drawData = getDrawData( item );
			drawData.shapeArr.push( new ShapeInfo() );
			Android.makeNewToast( 'New shape', true ).show();
			setDrawData(item, drawData);
			dialog.dismiss();
			var time = setTimeout(function(){clearTimeout(time); showShapeMenu(item);}, 0);
		}
	} );

	linearView.addView( shapeName = new TextView( ctxTheme ) );
	shapeName.setText( 'Preview' );
	
	var bitmap = Bitmap.createBitmap(drawData.width, drawData.height, Bitmap.Config.ARGB_8888);
	var c = new Canvas( bitmap );
	drawShapes( c, drawData.shapeArr );
	
	var shapeCanvas;
	linearView.addView( shapeCanvas = new ImageView( ctxTheme ) );
	shapeCanvas.setImageDrawable( new BitmapDrawable(ctx.getResources(), bitmap) );

	var hex = Number(item.getId()).toString(16);
	hex = "000000".substr(0, 6 - hex.length) + hex;
	linearView.addView( shapeName = new TextView( ctxTheme ) );
	shapeName.setText( Html.fromHtml('Current selected item is <b>#' + hex + '</b>. ') );
	
	var editWidth, editHeight;
	linearView.addView( shapeInfoLayout = new LinearLayout( ctxTheme ) );
	shapeInfoLayout.addView( shapeName = new TextView( ctxTheme ) );
	shapeName.setText( 'Width' );
	shapeInfoLayout.addView( editWidth = new EditText( ctxTheme ) );
	editWidth.setText( '' + drawData.width );
	editWidth.setInputType( InputType.TYPE_CLASS_NUMBER );

	linearView.addView( shapeInfoLayout = new LinearLayout( ctxTheme ) );
	shapeInfoLayout.addView( shapeName = new TextView( ctxTheme ) );
	shapeName.setText( 'Height' );
	shapeInfoLayout.addView( editHeight = new EditText( ctxTheme ) );
	editHeight.setText( '' + drawData.height );
	editHeight.setInputType( InputType.TYPE_CLASS_NUMBER );

	linearView.addView( shapeInfoLayout = new LinearLayout( ctxTheme ) );
	var btn;
	shapeInfoLayout.addView( btn = new Button( ctxTheme ) );
	btn.setAllCaps( false );
	btn.setText( 'Set size' );
	btn.setOnClickListener( new View.OnClickListener()
	{
		onClick: function( view )
		{
			var drawData = getDrawData( item );
			
			drawData.width = parseInt( editWidth.getText() );
			drawData.height = parseInt( editHeight.getText() );
			
			setDrawData( item, drawData );
			dialog.dismiss();
			var time = setTimeout(function(){clearTimeout(time); showShapeMenu(item);}, 0);
		}
	});
	shapeInfoLayout.addView( btn = new Button( ctxTheme ) );
	btn.setAllCaps( false );
	btn.setText( 'Reset size' );
	btn.setOnClickListener( new View.OnClickListener()
	{
		onClick: function( view )
		{
			var drawData = getDrawData( item );
			
			drawData.width = item.getWidth();
			drawData.height = item.getHeight();
			
			setDrawData( item, drawData );
			dialog.dismiss();
			var time = setTimeout(function(){clearTimeout(time); showShapeMenu(item);}, 0);
		}
	});

	linearView.addView( shapeName = new TextView( ctxTheme ) );
	shapeName.setText( 'Actions' );
	
	if ( item.getType() == 'Shortcut' )
	{
		linearView.addView( btn = new Button( ctxTheme ) );
		btn.setAllCaps( false );
		btn.setText( 'Set custom icon' );
		btn.setOnClickListener( new View.OnClickListener()
		{
			onClick: function( view )
			{
				var drawData = getDrawData( item );
				var img = LL.createImage( drawData.width, drawData.height );
				item.setCustomIcon( img );
				drawShapes( img.draw(), drawData.shapeArr );
				img.update();
			}
		});
		
		linearView.addView( btn = new Button( ctxTheme ) );
		btn.setAllCaps( false );
		btn.setText( 'Set image (temporary)' );
		btn.setOnClickListener( new View.OnClickListener()
		{
			onClick: function( view )
			{
				var img = LL.createImage(
				{
					draw: function(context)
					{
						var canvas = context.getCanvas();
						var item = context.getItem(); // Currently this method will only return a value when drawing shortcut icons (set through setImage(Image)), otherwise it will return null.
						drawShapes( canvas, getDrawData( item ).shapeArr );
					}
				}, -1, -1);
				item.setImage( img );
			}
		});
		
		linearView.addView( btn = new Button( ctxTheme ) );
		btn.setAllCaps( false );
		btn.setText( 'Set icon background layer' );
		btn.setOnClickListener( new View.OnClickListener()
		{
			onClick: function( view )
			{
				var drawData = getDrawData( item );
				var img = LL.createImage( drawData.width, drawData.height );
				item.setIconLayer( img, 'b', false );
				drawShapes( img.draw(), drawData.shapeArr );
				img.update();
				item.setIconLayer( img, 'b', true );
			}
		});
	}
	
	linearView.addView( btn = new Button( ctxTheme ) );
	btn.setAllCaps( false );
	btn.setText( 'Set box background' );
	btn.setOnClickListener( new View.OnClickListener()
	{
		onClick: function( view )
		{
			var drawData = getDrawData( item );
			var img = LL.createImage( drawData.width, drawData.height );
			item.setBoxBackground( img, 'nsf', false );
			drawShapes( img.draw(), drawData.shapeArr );
			img.update();
			item.setBoxBackground( img, 'nsf', true );
		}
	});
	
	linearView.addView( btn = new Button( ctxTheme ) );
	btn.setAllCaps( false );
	btn.setText( 'Export to string' );
	btn.setOnClickListener( new View.OnClickListener()
	{
		onClick: function( view )
		{
			var drawData = getDrawData( item );
			var text = JSON.stringify(drawData);
			var ctx = getActiveScreen().getContext();
			//bindClass( 'android.content.Context' );
			bindClass( 'android.content.ClipboardManager' );
			bindClass( 'android.content.ClipData' );
			var clipboard = ctx.getSystemService(Context.CLIPBOARD_SERVICE);
			var clip = ClipData.newPlainText('shape export', text);
			clipboard.setPrimaryClip(clip);
			Android.makeNewToast('Shape data exported to clipboard.', true).show();
		}
	});
	
	builder.setCancelable( true );
	builder.setNeutralButton( 'Close',
	{
		onClick: function( dialog, id )
		{
			dialog.dismiss();
		}
	} );
	dialog = builder.create();
	dialog.setOnShowListener( new DialogInterface.OnShowListener()
	{
		onShow:function()
		{
			rootView.scrollTo(0,0);
		}
	} );
	dialog.show();
}

var event = getEvent();
switch ( event.getSource() )
{
	case 'MENU_ITEM':
	case 'I_CLICK':
	case 'SHORTCUT':
		showShapeMenu( event.getItem() );
		return;
	case 'MENU_APP':
		showShapeMenu( event.getContainer().getOpener() );
		return;
	default:
		alert( 'unknown event source ' + event.getSource() );
		return;
}

script_shape_background.txt · Last modified: 2017/06/20 20:26 by tbog