
/*------------------------------------------------------------------------*
 * Originally implemented by Scott Flinn, in association with the
 * Imager Graphics Laboratory at the University of British Columbia
 * (scottflinn@alumni.uwaterloo.ca).
 *
 * This source code may be freely distributed and modified for any purpose
 * as long as these introductory comments are not removed.  Please be
 * aware that this represents the author's initial experiments with the
 * Java platform and should not necessarily be considered good examples
 * of Java programming.
 *------------------------------------------------------------------------*/

/*------------------------------------------------------------------------*
 *  The CW class implements the Cafe Wall illusion.  It is meant to be
 *    run in a separate window using AppletStarter.
 *------------------------------------------------------------------------*/

import java.awt.*;

/*------------------------------  Class CW  ------------------------------*/

public class CW extends WindowApplet
{
  CWPanel     canvas;
  CWControls  controls;
  Button      dismiss;

    /*-----------------------  constructor  ------------------------*/

    CW()
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;

        // Create the layout manager and a constraint object
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout( gridbag );

        // Create the canvas panel and put it on top
        canvas = new CWPanel();
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 1.0;
        gridbag.setConstraints( canvas, c );
        add( canvas );

        // Create the control panel and put it second from the bottom
        controls = new CWControls( canvas );
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weighty = 0.0;
        gridbag.setConstraints( controls, c );
        add( controls );

        // Create the dismiss button and put it at the bottom
        dismiss = new Button( "Dismiss" );
        gridbag.setConstraints( dismiss, c );
        add( dismiss );
        validate();
    }

    /*--------------------------  action  --------------------------*/

    public boolean action( Event event, Object obj )
    {
        if ( event.target == dismiss )
        {
            hide();
            return true;
        }
        return false;
    }
}

/*---------------------------  Class CWPanel  ----------------------------*/

class CWPanel extends Panel
{
  public int      blockWidth   = 50;
  public int      blockHeight  = 50;
  public int      mortarWidth  =  1;
  public double   offset       = 0.5;
  public boolean  square       = true;

  public final int  minGray        =   0;
  public final int  maxGray        = 255;
  public final int  minBlockWidth  =  10;
  public final int  maxBlockWidth  = 200;
  public final int  defDarkGray    =   0;
  public final int  defLightGray   = 255;
  public final int  defMortarGray  = 128;

  private Color    darkGray;
  private Color    lightGray;
  private Color    mortarGray;

  // For double buffering
  Dimension  offDimension;
  Image      offImage;
  Graphics   offGraphics = null;

    /*-----------------------  constructor  ------------------------*/

    CWPanel()
    {
        darkGray   = new Color( defDarkGray, defDarkGray, defDarkGray );
        lightGray  = new Color( defLightGray, defLightGray, defLightGray );
        mortarGray = new Color( defMortarGray, defMortarGray, defMortarGray );
    }

    /*--------------------------  paint  ---------------------------*/

    public void paint( Graphics g )
    {
        update( g );
    }

    /*--------------------------  update  --------------------------*/

    public void update( Graphics g )
    {
      Dimension  d = size();
      int        rowHeight, rows, cols, r, c, x, y, dx;

        // Create off-screen image for double buffering
        if ( ( offGraphics == null ) ||
             ( d.width  != offDimension.width  ) ||
             ( d.height != offDimension.height ) )
        {
            offDimension = d;
            offImage = createImage( d.width, d.height );
            offGraphics = offImage.getGraphics();
        }

        // Draw background in light colour
        offGraphics.setColor( lightGray );
        offGraphics.fillRect( 0, 0, d.width - 1, d.height - 1 );

        // Determine number of rows and columns
        rowHeight = blockHeight + mortarWidth;
        rows = ( d.height / rowHeight ) + 1;
        cols = ( d.width / blockWidth ) + ( ( offset < 0 ) ? 2 : 1 );

        // Draw dark blocks row by row
        offGraphics.setColor( darkGray );
        dx = (int)( offset * blockWidth );
        y = d.height - blockHeight;
        for ( r = 0; r < rows; r++ )
        {
            // Draw this row, with horizontal offset if it's an odd row
            x = ( ( r % 2 ) == 1 ) ? dx : 0;
            for ( c = 0; c < cols; c += 2 )
            {
                // Draw a block here
                offGraphics.fillRect( x, y, blockWidth, blockHeight );
                x += blockWidth + blockWidth;
            }
            y -= rowHeight;
        }

        // Draw mortar if necessary
        if ( mortarWidth == 1 )
        {
            offGraphics.setColor( mortarGray );
            x = d.width - 1;
            y = d.height - rowHeight;
            for ( r = 0; r < rows; r++ )
            {
                offGraphics.drawLine( 0, y, x, y );
                y -= rowHeight;
            }
        }
        else if ( mortarWidth > 1 )
        {
            offGraphics.setColor( mortarGray );
            x = d.width - 1;
            y = d.height - rowHeight;
            for ( r = 0; r < rows; r++ )
            {
                offGraphics.fillRect( 0, y, x, mortarWidth );
                y -= rowHeight;
            }
        }

        // Draw a black border
        offGraphics.setColor( Color.black );
        offGraphics.drawRect( 0, 0, d.width - 1, d.height - 1 );

        // Finally, copy the off-screen image to the screen
        g.drawImage( offImage, 0, 0, this );
    }

    /*-----------------------  setDarkGray  ------------------------*/

    public void setDarkGray( int g ){
        darkGray = new Color( g, g, g );
    }

    /*-----------------------  setLightGray  -----------------------*/

    public void setLightGray( int g ) {
        lightGray = new Color( g, g, g );
    }

    /*----------------------  setMortarGray  -----------------------*/

    public void setMortarGray( int g ) {
        mortarGray = new Color( g, g, g );
    }

    /*------------------------  setSquare  -------------------------*/

    public void setSquare( boolean square )
    {
        this.square = square;
        if ( square )
        {
            // Increase smaller dimension to make square
            if ( blockWidth < blockHeight )
                blockWidth = blockHeight;
            else if ( blockHeight < blockWidth )
                blockHeight = blockWidth;
        }
        update( getGraphics() );
    }
}

/*--------------------------  Class CWControls  --------------------------*/

class CWControls extends Panel
{
  Scrollbar  wSlider;        // Controls block width
  Scrollbar  hSlider;        // Controls block height
  Scrollbar  mSlider;        // Controls mortar thickness
  Scrollbar  darkSlider;     // Controls gary level of dark blocks
  Scrollbar  lightSlider;    // Controls gray level of light blocks
  Scrollbar  mgSlider;       // Controls gray level of mortar
  Scrollbar  offSlider;      // Controls row offset
  Checkbox   square;         // Constrain blocks to be square if checked
  CWPanel    canvas;         // The main drawing canvas

    /*-----------------------  constructor  ------------------------*/

    CWControls( CWPanel canvas )
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;

        // Record id of drawing canvas
        this.canvas = canvas;

        // Use a GridBag to lay things out
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout( gridbag );

        // Create sliders and check boxes
        wSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.blockWidth,
                ( canvas.maxBlockWidth - canvas.minBlockWidth ) / 10,
                canvas.minBlockWidth, canvas.maxBlockWidth );
        hSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.blockHeight,
                ( canvas.maxBlockWidth - canvas.minBlockWidth ) / 10,
                canvas.minBlockWidth, canvas.maxBlockWidth );
        mSlider = new Scrollbar( Scrollbar.HORIZONTAL, 1, 1, 0, 15 );

        darkSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.defDarkGray,
                ( canvas.maxGray - canvas.minGray ) / 10,
                canvas.minGray, canvas.maxGray );
        lightSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.defLightGray,
                ( canvas.maxGray - canvas.minGray ) / 10,
                canvas.minGray, canvas.maxGray );
        mgSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL, canvas.defMortarGray,
                ( canvas.maxGray - canvas.minGray ) / 10,
                canvas.minGray, canvas.maxGray );

        offSlider = new Scrollbar( Scrollbar.HORIZONTAL, 150, 20, 0, 200 );
        square    = new Checkbox( "Square" );
        square.setState( true );

        // Add block width and height sliders
        addControl( (Component)(new Label( "Tile width", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)wSlider, 1.0,
            GridBagConstraints.HORIZONTAL, 1, gridbag, c );
        addControl( (Component)(new Label( "Tile height", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)hSlider, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        // Add block gray level sliders
        addControl( (Component)(new Label( "Dark gray", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)darkSlider, 1.0,
            GridBagConstraints.HORIZONTAL, 1, gridbag, c );
        addControl( (Component)(new Label( "Light gray", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)lightSlider, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        // Add mortar thickness and gray level sliders
        addControl( (Component)(new Label( "Mortar width", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)mSlider, 1.0,
            GridBagConstraints.HORIZONTAL, 1, gridbag, c );
        addControl( (Component)(new Label( "Mortar gray", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)mgSlider, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        //  Add offset slider
        addControl( (Component)(new Label( "Row offset", Label.RIGHT )),
            0.0, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)offSlider, 1.0,
            GridBagConstraints.HORIZONTAL, 1, gridbag, c );

        // Add blank space then square check box
        addControl( (Component)(new Label( "" )), 0.0,
            GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)square, 1.0, GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );
    }

    /*------------------------  addControl  ------------------------*/

    private void
    addControl( Component component, double wx, int fill, int gw,
        GridBagLayout gb, GridBagConstraints c )
    {
        c.weightx   = (float)wx;
        c.weighty   = 1.0;
        c.fill      = fill;
        c.gridwidth = gw;
        gb.setConstraints( component, c );
        add( component );
    }

    /*--------------------------  insets  --------------------------*/

    public Insets insets()
    {
        return( new Insets( 5, 5, 5, 5 ) );
    }

    /*--------------------------  action  --------------------------*/

    public boolean action( Event event, Object obj )
    {
        if ( event.target == square )
        {
            // Toggle square flag of canvas
            canvas.setSquare( square.getState() );
            wSlider.setValue( canvas.blockWidth );
            hSlider.setValue( canvas.blockHeight );
            return true;
        }
        return false;
    }

    /*-----------------------  handleEvent  ------------------------*/

    public boolean handleEvent( Event event )
    {
        if ( event.target == wSlider )
        {
            // adjust block width and redraw
            canvas.blockWidth = wSlider.getValue();
            if ( canvas.square )
            {
                canvas.blockHeight = canvas.blockWidth;
                hSlider.setValue( canvas.blockHeight );
            }
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == hSlider )
        {
            // adjust block height and redraw
            canvas.blockHeight = hSlider.getValue();
            if ( canvas.square )
            {
                canvas.blockWidth = canvas.blockHeight;
                wSlider.setValue( canvas.blockWidth );
            }
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == mSlider )
        {
            // adjust mortar thickness and redraw
            canvas.mortarWidth = mSlider.getValue();
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == lightSlider )
        {
            // adjust gray level of light blocks and redraw
            canvas.setLightGray( lightSlider.getValue() );
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == darkSlider )
        {
            // adjust gray level of dark blocks and redraw
            canvas.setDarkGray( darkSlider.getValue() );
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == mgSlider )
        {
            // adjust gray level of mortar and redraw
            canvas.setMortarGray( mgSlider.getValue() );
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else if ( event.target == offSlider )
        {
            // adjust row offset and redraw
            canvas.offset =
                (float)( offSlider.getValue() - 100 ) / (float)100.0;
            canvas.update( canvas.getGraphics() );
            return true;
        }
        else
            return super.handleEvent( event );
    }    
}

/*------------------------------------------------------------------------*/

