Previous  | Next  | Home

Draggable Symbols II


 

In this project we will start with the basic DraggableSymbols project developed in the previous section and extend it to do more sophisticated things.

 

Creating the Project

Create a new project in Eclipse: File > New > Android Project, and select

 

Copying Resources

First we copy some resources. Open the course images directory and copy the files green_square.png, red_square.png, and yellow_square.png to the res/drawable-hdpi directory of the DragSymbols project.

 

Import the SymbolDragger Class from the Previous Project

Import the class SymbolDragger from the DraggableSymbols project: right-click on src/<namespace>dragsymbols, select Import > General > File System, then click Next and Browse. Navigate to the directory containing the file SymbolDragger.java, click OK, select the file, and then click Finish. Eclipse will indicate an error in this imported file because the package name is wrong (force a rebuild if no error shows initially). Fix that by editing the first line of the imported file SymbolDragger.java to


    package com.lightcone.dragsymbols;

which gives it the appropriate package name for this project.

 

Edit the DragSymbols Class

Now edit the file DragSymbols.java to give it the content


    package com.lightcone.dragsymbols;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.ViewGroup;
    
    /*
    Demonstration of one way to put a set of draggable symbols on screen. Define symbols as image 
    files in res/drawable-hdpi/filename.png (or .jpg or .gif files). Refer to them then by 
    R.drawable.filename in the array symbolIndex, and give the initial x and y coordinates for 
    each symbol in the arrays X and Y.
    */
    
    public class DragSymbols extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            
            // Define the symbols and their initial coordinates in arrays. No limit in principle 
            // to how many. Coordinates are measured from the upper left corner of the screen, 
            // with x increasing to the right and y increasing downward
            
            float [] X = {2, 48, 94};   // Initial x coord in pixels of upper left corner of symbol
            float [] Y = {2, 2, 2};     // Initial y coord in pixels of upper left corner of symbol
            
            // The Drawable corresponding to the symbol. R.drawable.file refers to file.png, .jpg, 
            // or .gif stored in res/drawable-hdpi (referenced from code without the extension).
            
            int[]symbolIndex = {R.drawable.red_square, R.drawable.green_square, R.drawable.yellow_square};
            
            // Instantiate a SymbolDragger instance (which subclasses View), passing to it in the
            // constructor the context (this) and the above arrays.  Then set the content view to 
            // this instance of SymbolDragger (so the layout is being specified entirely by SymbolDragger, 
            // with no XML layout file).  The resulting view should then place draggable symbols with 
            // content and initial position defined by the above arrays on the screen.
            
            SymbolDragger view = new SymbolDragger(this, X, Y, symbolIndex);
            view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.FILL_PARENT));
            setContentView(view);
        }
    }

which is the same as DraggableSymbols.java in the DraggableSymbols project except for the package name. If you now execute this you should get the same results as for the project DraggableSymbols.

 

Renaming SymbolDragger to DragginSlayer

Before proceeding, let's use the global rename facility of Eclipse to change the name of the class SymbolDragger. (We do this for clarity, since we are going to alter its content substantially from that of the corresponding class in DraggableSymbols.)

  1. Right-click on SymbolDragger.java and select Refactor > Rename.

  2. On the resulting screen, give DragginSlayer for the New name, and confirm that the Update reference box is checked and all other boxes are unchecked.

  3. Click Finish.

  4. Eclipse should now consistently and systematically change all SymbolDragger references to the new name DragginSlayer, throughout the project.

Scroll through the code in both classes for this project and notice the changes; confirm that renaming has not changed any functionality and everything works as before by doing a clean build (Project > Clean) and executing the app.

 

Creating a Source of Symbols

A typical application of the dragging capability that we have implemented would be to have a "source" of symbols off-stage so that when they are dragged a new instance of the object is created and dragged to the stage, but the original "source" of the symbols remains. Let's implement that capability. Basically, we have to modify DragginSlayer so that a set of original symbols remains at fixed positions when we drag the new symbols to the stage. To implement that in simplest form, we add a new set of Drawables symbol0, with coordinates X0 and Y0, that do not change as the objects corresponding to symbol are dragged about the screen. We modify DragginSlayer.java so that it reads (with the changes relative to the original SymbolDragger.java marked in red)


package com.lightcone.dragsymbols;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;

/*
Demonstration of one way to put a set of draggable symbols on screen.
Adapted loosely from material discussed in
  http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
See also
  http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
This example requires only API 3 (Android 1.5).   
*/


public class DragginSlayer extends View {
	
    // Colors for background and text
    static final int BACKGROUND_COLOR = Color.argb(255, 0, 0, 0);
    static final int HEADER_COLOR = Color.argb(255, 30, 30, 30);
    static final int TEXT_COLOR = Color.argb(255,255,255,0);
    
    private int numberSymbols;      // Total number of symbols to use
    private Drawable [] symbol;     // Array of symbols (dimension numberSymbols)
    private int [] symbolIndex;     // Index of drawable resource (R.drawable.symbol)
    private Drawable [] symbol0;    // Array of symbols (dimension numberSymbols)
    private float [] X0;            // Initial x coordinate, upper left corner of symbol i
    private float [] Y0;            // Initial y coordinate, upper left corner of symbol i 
    private float [] X;             // Current x coordinate, upper left corner of symbol i
    private float [] Y;             // Current y coordinate, upper left corner of symbol i
    private int [] symbolWidth;     // Width of symbol i
    private int [] symbolHeight;    // Height of symbol i
    private float [] lastTouchX;    // x coordinate of symbol i at last touch
    private float [] lastTouchY;    // y coordinate of symbol i at last touch
    private int symbolSelected;     // Index of symbol last touched (-1 if none selected)
    private Paint paint;
    
    // Following define upper left and lower right corners of display stage rectangle
    private int stageX1 = 0;
    private int stageY1 = 45;
    private int stageX2 = 500;
    private int stageY2 = 500;
    
    private boolean isDragging = false;     // True if some symbol is currently being dragged
    
    
    public DragginSlayer(Context context, float[]X, float[]Y, int[]symbolIndex) {
    	
    	// Call through to simplest constructor of View superclass
        super(context);
        
        // Set up local arrays defining symbols and their positions
        this.X = X;
        this.Y = Y;
        this.symbolIndex = symbolIndex;
        
    	numberSymbols = X.length;
        X0 = new float[numberSymbols];
        Y0 = new float[numberSymbols];
        symbol0 = new Drawable[numberSymbols];
        symbol = new Drawable[numberSymbols];    
        symbolWidth = new int[numberSymbols];
        symbolHeight = new int[numberSymbols];
        lastTouchX = new float[numberSymbols];
        lastTouchY = new float[numberSymbols];

        
        // Fill the symbol arrays with data
        for(int i=0; i<numberSymbols; i++){
            X0[i] = X[i];
            Y0[i] = Y[i];
            symbol[i] = context.getResources().getDrawable(symbolIndex[i]);
            symbolWidth[i] = symbol[i].getIntrinsicWidth();
            symbolHeight[i] = symbol[i].getIntrinsicHeight();
            symbol[i].setBounds(0,0,symbolWidth[i],symbolHeight[i]);  
            symbol0[i] = context.getResources().getDrawable(symbolIndex[i]);
            symbol0[i].setBounds(0,0,symbolWidth[i],symbolHeight[i]);
        }
        
        // Set up the Paint object that will control format of screen draws
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setTextSize(14);
        paint.setStrokeWidth(0);
    }
   

    /* Process MotionEvents corresponding to screen touches and drags. MotionEvent 
     reports movement (mouse, pen, finger, trackball) events. The MotionEvent method 
     getAction() returns the kind of action being performed as an integer constant of
     the MotionEvent class, with possible values ACTION_DOWN, ACTION_MOVE, ACTION_UP, 
     and ACTION_CANCEL. Thus we can switch on the returned integer to determine the
     kind of event and the appropriate action.
     */

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
    	
    	final int action = ev.getAction();
    	
        switch (action) {
        
            // MotionEvent class constant signifying a finger-down event
    
            case MotionEvent.ACTION_DOWN: {
                    
                isDragging = false;
                
                // Get coordinates of touch event
                final float x = ev.getX();
                final float y = ev.getY();
                
                // Initialize
                symbolSelected = -1;  // -1 if touch not within current bounds of some symbol
                
                // Determine if touch within bounds of one of the symbols
                
                for(int i=0; i<numberSymbols; i++){
                    if((x>X[i] && x<(X[i]+symbolWidth[i])) && (y>Y[i] 
                        && y<(Y[i]+symbolHeight[i]))) {
                            symbolSelected = i;
                            break;
                    }
                }
                
                // If touch within bounds of a symbol, remember start position for this symbol
                
                if(symbolSelected > -1){
                    lastTouchX[symbolSelected] = x;
                    lastTouchY[symbolSelected] = y;
                }         
                break;
            }
            
            // MotionEvent class constant signifying a finger-drag event  
            
            case MotionEvent.ACTION_MOVE: {
                    
                // Only process if touch selected a symbol
                if(symbolSelected > -1 ){
                    isDragging = true;
                    final float x = ev.getX();
                    final float y = ev.getY();
                    
                    // Calculate the distance moved
                    final float dx = x - lastTouchX[symbolSelected];
                    final float dy = y - lastTouchY[symbolSelected];
                    
                    // Move the object selected
                    X[symbolSelected] += dx;
                    Y[symbolSelected] += dy;
                    
                    // Remember this touch position for the next move event of this object
                    lastTouchX[symbolSelected] = x;
                    lastTouchY[symbolSelected] = y;
                    
                    // Request a redraw
                    invalidate();
                    
                }
                break;
            }
            
            // MotionEvent class constant signifying a finger-up event
            
            case MotionEvent.ACTION_UP:
                isDragging = false;
                invalidate();  // Request redraw
                break;
	        	
        }
        return true;
    }
    
    
    // This method will be called each time the screen is redrawn. The draw is
    // on the Canvas object, with formatting controlled by the Paint object. 
    // When to redraw is under Android control, but we can request a redraw 
    // using the method invalidate() inherited from the View superclass.
    
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas); 
          
        // Draw backgrounds
        drawBackground(paint,canvas);
        
        // Draw all draggable symbols at their current dragged locations
        for(int i=0; i<numberSymbols; i++){
            canvas.save();
            canvas.translate(X[i],Y[i]);
            symbol[i].draw(canvas);
            canvas.restore();
        }
    }
    
    
    // Method to draw the background for the screen.  Invoked from onDraw each time
    // the screen is redrawn.
    
    private void drawBackground(Paint paint, Canvas canvas){
		
        // Draw header bar background
        paint.setColor(HEADER_COLOR);
        canvas.drawRect(0,0,stageX2,stageY2,paint);
        
        // Draw main stage background
        paint.setColor(BACKGROUND_COLOR);
        canvas.drawRect(stageX1,stageY1,stageX2,stageY2,paint);
        
        // Draw image of symbols at their original locations to denote source
        // (But presently set up so that only one instance of each symbol can
        // be dragged onto the stage.)

        for(int i=0; i<numberSymbols; i++){
            canvas.save();
            canvas.translate(X0[i],Y0[i]);
            symbol0[i].draw(canvas);
            canvas.restore();
        }
        
        // While a symbol is being dragged, display its x and y coordinates in a readout
        if(isDragging){		
            paint.setColor(TEXT_COLOR);
            canvas.drawText("X = "+X[symbolSelected], 200, 18, paint);
            canvas.drawText("Y = "+Y[symbolSelected], 200, 33, paint);
        }	
    }
}

Everything behaves as before, except that in the method drawBackground there is a new block of code that always draws the symbols at their original positions, in addition to their new positions.

 

Trying It Out

If you try it in an emulator or on a phone you should get something similar to the following figures.



On the left side we see the screen in its initial state and on the right side the yellow and green squares have already been dragged and positioned, and the red square is currently being dragged (with the readout showing its current position). Notice that the original "source" images remain, off the top of the stage.


A project that implements the application as developed to this point is archived at the link DraggableSymbols2. (In that project the class SymbolDragger has not been renamed to DragginSlayer, but the functionality is the same.)

 

Allowing for Multiple Instances of Each Symbol

The preceding example is a simple implementation of the idea, but it allows only a single instance of each of the three color squares to be dragged to the stage. If you attempt to drag additional instances to the stage nothing will happen. Let's now implement the bookkeeping that permits multiple instances of each symbol to be dragged to the stage and positioned. The essential changes required are

  1. We must keep track separately of the "source" images positioned offstage, which do not move, and instances of these that we drag onto the stage and can move around. We use the arrays symbol0, X0, and Y0 to keep track of the source drawables and positions, respectively, and use source, X, and Y to keep track of the corresponding quantities for the instances dragged onstage. The dimension of the source arrays is set by the arrays passed through the constructor; the dimension of the instance arrays is set by the variable maxInstances. The currently selected index in the source arrays is tracked by symbolSelected and the corresponding index in the instance arrays is tracked by instanceSelected. If a touch is not on a source symbolSelected = -1, and if a touch is not on an already-created instance, instanceSelected = -1.

  2. We continue to create the offstage source objects source0 in the constructor since their number does not change, but now we must move the creation of the instance objects source to the touchEvent handlers, since the instances are created dynamically by dragging a source onstage.

  3. Within the event handlers we must now distinguish between a touch and drag on an offstage source, which creates a new instance and then drags it to a position on the stage, and a touch and drag on an instance already onstage, which just moves that instance to a new position without creating a new instance.

  4. We should prevent the user creating more than maxInstances instances, and warn the user when the maximum number has been reached.

The modified form of DragginSlayer.java that accomplishes these tasks is illustrated in the following listing.


    package com.lightcone.dragsymbols;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.drawable.Drawable;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Toast;
    
    /*
    Demonstration of one way to put a set of draggable symbols on screen.
    Adapted loosely from material discussed in
    http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
    See also
    http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
    This example requires only API 3 (Android 1.5).   
    */
    
    
    public class DragginSlayer extends View {
        
        // Colors for background and text
        static final int BACKGROUND_COLOR = Color.argb(255, 0, 0, 0);
        static final int HEADER_COLOR = Color.argb(255, 30, 30, 30);
        static final int TEXT_COLOR = Color.argb(255,255,255,0);
        
        private int numberSymbols;      // Total number of symbols to use
        private int numberInstances;    // Total number of symbol instances onstage
        private int maxInstances;       // Maximum number of symbol instances permitted onstage
        private Drawable [] symbol;     // Array of symbols (dimension numberSymbols)
        private int [] symbolIndex;     // Index of drawable resource (R.drawable.symbol)
        private Drawable [] symbol0;    // Array of symbols (dimension numberSymbols)
        private float [] X0;            // Initial x coordinate, upper left corner of symbol i
        private float [] Y0;            // Initial y coordinate, upper left corner of symbol i 
        private float [] X;             // Current x coordinate, upper left corner of symbol i
        private float [] Y;             // Current y coordinate, upper left corner of symbol i
        private int [] symbolWidth;     // Width of symbol i
        private int [] symbolHeight;    // Height of symbol i
        private float [] lastTouchX;    // x coordinate of symbol i at last touch
        private float [] lastTouchY;    // y coordinate of symbol i at last touch
        private int symbolSelected;     // Index of symbol last touched (-1 if none selected)
        private int instanceSelected;   // Index of symbol instance last touched (-1 if none)
        private Paint paint;            // Paint object holding draw formatting information
        
        // Following define upper left and lower right corners of display stage rectangle
        private int stageX1 = 0;
        private int stageY1 = 45;
        private int stageX2 = 500;
        private int stageY2 = 500;
        
        private boolean isDragging = false;   // True if any symbol is currently being dragged
        
        private Context context;
        
        public DragginSlayer(Context context, float[]XX, float[]YY, int[]symbolIndex) {
            
            // Call through to simplest constructor of View superclass
            super(context);
            
            this.context = context;
            
            // Initialize instance counters
            maxInstances = 12;
            numberInstances = 0;
            
            // Set up local arrays defining symbols and their positions
            this.X0 = XX;
            this.Y0 = YY;
            this.symbolIndex = symbolIndex;
            
            numberSymbols = X0.length;
            this.X = new float[maxInstances];
            this.Y = new float[maxInstances];
            symbol0 = new Drawable[numberSymbols];
            symbol = new Drawable[maxInstances];    
            symbolWidth = new int[numberSymbols];
            symbolHeight = new int[numberSymbols];
            lastTouchX = new float[maxInstances];
            lastTouchY = new float[maxInstances];
        
            // Fill the symbol arrays with data
            for(int i=0; i<numberSymbols; i++){
                symbol0[i] = context.getResources().getDrawable(symbolIndex[i]);
                symbolWidth[i] = symbol0[i].getIntrinsicWidth();
                symbolHeight[i] = symbol0[i].getIntrinsicHeight();      
                symbol0[i].setBounds(0,0,symbolWidth[i],symbolHeight[i]);
            }
            
            // Set up the Paint object that will control format of screen draws
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setTextSize(13);
            paint.setStrokeWidth(0);
        }
    
        
        /* Process MotionEvents corresponding to screen touches and drags. MotionEvent 
        reports movement (mouse, pen, finger, trackball) events. The MotionEvent method 
        getAction() returns the kind of action being performed as an integer constant of
        the MotionEvent class, with possible values ACTION_DOWN, ACTION_MOVE, ACTION_UP, 
        and ACTION_CANCEL. Thus we can switch on the returned integer to determine the
        kind of event and the appropriate action.
        */
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            
            final int action = ev.getAction();
            
            switch (action) {
            
                // MotionEvent class constant signifying a finger-down event
        
                case MotionEvent.ACTION_DOWN: {
                        
                    isDragging = false;
                    
                    // Get coordinates of touch event
                    final float x = ev.getX();
                    final float y = ev.getY();
                    
                    // Initialize symbol and instance indices
                    symbolSelected = -1;    // -1 if touch not within current bounds of symbol source
                    instanceSelected = -1;  // -1 if touch not within bounds of symbol instance
                    
                    // Determine if touch within bounds of one of the symbol sources offstage
                    
                    for(int i=0; i<numberSymbols; i++){
                        if((x>X0[i] && x<(X0[i]+symbolWidth[i])) && (y>Y0[i] 
                            && y<(Y0[i]+symbolHeight[i]))) {
                                symbolSelected = i;     // Index of symbol source touched
                                break;
                        }
                        // Warn if max number of instances has been reached (it won't create any more)
                        if(numberInstances == maxInstances){
                            String toaster = "Maximum number of instances ";
                            toaster += "("+maxInstances+") has been reached.";
                            Toast.makeText(context, toaster, Toast.LENGTH_LONG).show();
                        }
                    }
                    
                    // Determine if touch within bounds of one of the symbol instances onstage
                    
                    for(int i=0; i<numberInstances; i++){
                        if((x>X[i] && x<(X[i]+symbol[i].getIntrinsicWidth())) && (y>Y[i] 
                            && y<(Y[i]+symbol[i].getIntrinsicHeight() ))) {
                                instanceSelected = i;   // Index of symbol instance touched
                                break;
                        }
                    }
                    
                    // If touch within bounds of a symbol source or instance, remember start 
                    // position for this symbol
                    
                    if(symbolSelected > -1 || instanceSelected > -1){
                        if(instanceSelected > -1)lastTouchX[instanceSelected] = x;
                        if(instanceSelected > -1)lastTouchY[instanceSelected] = y;
                    }         
                    break;
                }
                
                // MotionEvent class constant signifying a finger-drag event  
                
                case MotionEvent.ACTION_MOVE: {
                        
                    // Only process if initial touch selected a symbol and not background
                    
                    // If touch and drag were on symbol source, and this hasn't yet been processed, first create a 
                    // new symbol instance (but only if the max number of instances will not be exceeded).  Do it
                    // here rather than in ACTION_DOWN so that just pressing the source symbol without a drag will
                    // not create a new instance.
    
                    if(symbolSelected > -1 && instanceSelected == -1 && numberInstances < maxInstances){
                        symbol[numberInstances] = context.getResources().getDrawable(symbolIndex[symbolSelected]);
                        symbol[numberInstances].setBounds(0,0,symbolWidth[symbolSelected],symbolHeight[symbolSelected]);
                        instanceSelected = numberInstances;
                        numberInstances ++;
                    }
                    
                    // Drag the instance, if selected (either an old instance or one just created)
                    
                    if(instanceSelected > -1 ){
                        isDragging = true;
                        final float x = ev.getX();
                        final float y = ev.getY();
                        
                        // Calculate the distance moved
                        final float dx = x - lastTouchX[instanceSelected];
                        final float dy = y - lastTouchY[instanceSelected];
                        
                        // Move the instance selected
                        X[instanceSelected] += dx;
                        Y[instanceSelected] += dy;
                        
                        // Remember this touch position for the next move event of this object
                        lastTouchX[instanceSelected] = x;
                        lastTouchY[instanceSelected] = y;
                        
                        // Request a redraw
                        invalidate();
                        
                    }
                    break;
                }
                
                // MotionEvent class constant signifying a finger-up event
                
                case MotionEvent.ACTION_UP:
                    isDragging = false;
                    invalidate();  // Request redraw
                    break;
                    
            }
            return true;
        }
        
        
        // This method will be called each time the screen is redrawn. The draw is
        // on the Canvas object, with formatting controlled by the Paint object. 
        // When to redraw is under Android control, but we can request a redraw 
        // using the method invalidate() inherited from the View superclass.
        
        @Override
        public void onDraw(Canvas canvas) {
            super.onDraw(canvas); 
            
            // Draw backgrounds
            drawBackground(paint,canvas);
            
            // Draw all draggable symbols at their current dragged locations
            for(int i=0; i<numberInstances; i++){
                canvas.save();
                canvas.translate(X[i],Y[i]);
                symbol[i].draw(canvas);
                canvas.restore();
            }
        }
        
        
        // Method to draw the background for the screen.  Invoked from onDraw each time
        // the screen is redrawn.
        
        private void drawBackground(Paint paint, Canvas canvas){
            
            // Draw header bar background
            paint.setColor(HEADER_COLOR);
            canvas.drawRect(0,0,stageX2,stageY2,paint);
            
            // Draw main stage background
            paint.setColor(BACKGROUND_COLOR);
            canvas.drawRect(stageX1,stageY1,stageX2,stageY2,paint);
            
            // Draw image of symbols at their original locations to denote source
    
            for(int i=0; i<numberSymbols; i++){
                canvas.save();
                canvas.translate(X0[i],Y0[i]);
                symbol0[i].draw(canvas);
                canvas.restore();
            }
            
            // While a symbol is being dragged, display its x and y coordinates in a readout
            if(isDragging){		
                paint.setColor(TEXT_COLOR);
                canvas.drawText("Instance "+instanceSelected, 200, 12, paint);
                canvas.drawText("X = "+X[instanceSelected], 200, 27, paint);
                canvas.drawText("Y = "+Y[instanceSelected], 200, 42, paint);
            }	
        }
    }

Now if you execute this on a device or emulator, you should be able to drag multiple instances of each symbol onto the stage and reposition these instances at will. The following figures illustrate.



For this case maxInstances = 12. In the left figure the user has already dragged 6 assorted instances onto the stage and is presently dragging the 7th (the red square near the lower left). The readout gives the instance number (counted from 0) and the position as the red square is being dragged. In the right figure the user has dragged 12 instances onto the stage and has just tried to drag another. She gets a popup warning that the maximum number of instances has been reached, and the app refuses to add another to the stage. She can still drag the 12 instances already on the stage to reposition at will.


Previous  | Next  | Home