Subversion Repositories AndroidProjects

Rev

Blame | Last modification | View Log | RSS feed

#include "Board.h"
#include "GameApp.h"

// Contains all the resources from the resources.xml file in our
// properties directory. See that file for more information.
#include "Res.h"

// You should remember these files from the previous demos
#include "SexyAppFramework/Graphics.h"
#include "SexyAppFramework/Color.h"
#include "SexyAppFramework/Rect.h"
#include "SexyAppFramework/ButtonWidget.h"
#include "SexyAppFramework/WidgetManager.h"
#include "SexyAppFramework/ImageFont.h"

// The following are needed for some new widgets we're going to learn about:
#include "SexyAppFramework/EditWidget.h"
#include "SexyAppFramework/Checkbox.h"
#include "SexyAppFramework/ListWidget.h"
#include "SexyAppFramework/ScrollbarWidget.h"
#include "SexyAppFramework/ScrollListener.h"

// As part of our pitch shifted sound example, we'll need to
// access the sound manager in our GameApp class as well as
// individual sound instances:
#include "SexyAppFramework/SoundManager.h"
#include "SexyAppFramework/SoundInstance.h"

// Used for file I/O:
#include "SexyAppFramework/Buffer.h"

// The SexyAppFramework resides in the "Sexy" namespace. As a convenience,
// you'll see in all the .cpp files "using namespace Sexy" to avoid
// having to prefix everything with Sexy::
using namespace Sexy;

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::Board(GameApp* theApp)
{
        mApp = theApp;


        mButton1 = NULL;
        mButton2 = NULL;
        mEditWidget = NULL;
        mCheckboxWidget = NULL;
        mListWidget = NULL;
        mScrollbarWidget = NULL;

        mMotionX = mUpdateFMotionX = 0;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::~Board()
{
        delete mButton1;
        delete mButton2;
        delete mEditWidget;
        delete mCheckboxWidget;
        delete mListWidget;
        delete mScrollbarWidget;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Update()
{

        Widget::Update();

        // As an example to illustrate the difference between smooth motion via UpdateF
        // and the traditional motion via Update, we'll udpate the two MotionX variables
        // separately.
        if ((mMotionX += 5.0f) >= mWidth)
                mMotionX = (float) -IMAGE_ROBOTROBOT->GetWidth();
       
        MarkDirty();
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::UpdateF(float theFrac)
{
        // This is very similar to Update, except that since this function
        // gets called a different number of times depending on the refresh
        // rate of the monitor, "theFrac" can be a variable number, and we
        // need to multiply our normal motion change of 5.0 pixels per
        // update by "theFrac" to get our smooth motion. See the header
        // comment for this function in Board.h for more details.
        if ((mUpdateFMotionX += 5.0f * theFrac) >= mWidth)
                mUpdateFMotionX = (float) -IMAGE_ROBOTROBOT->GetWidth();

        // No need to MarkDirty: We're doing it in Board::Update already.
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Draw(Graphics* g)
{
        // Clear the screen to black
        g->SetColor(Color(0, 0, 0));
        g->FillRect(0, 0, mWidth, mHeight);

        // Draw the image first using the standard method, with the coordinate
        // updated via Board::Update as an example of non-smooth motion.
        // Let's also print some text too.
        g->SetFont(FONT_DEFAULT);
        g->SetColor(Color(255, 255, 255));
        g->DrawString(_S("Non smooth motion is jerky"), 10, 100);

        // What's this? A new DrawImage function? Yes. Believe it. It is true.
        // DrawImageF is just like it's best friend, DrawImage except that
        // it takes floating point values instead of integer ones. This is
        // slower than DrawImage, as the resulting image is anti-aliased to
        // give the illusion of moving at sub-pixel increments. A common
        // optimization technique at PopCap is to use DrawImageF
        // for motion when the user has a supported 3D card and DrawImage if
        // the user has to run in software mode.
        g->DrawImageF(IMAGE_ROBOTROBOT, mMotionX, 120.0f);

        // Now let's draw the image but using the smooth motion amounts:
        g->DrawString(_S("Smooth motion is silky smoothness"), 10, 200);
        g->DrawImageF(IMAGE_ROBOTROBOT, mUpdateFMotionX, 220.0f);

        // Let's draw the currently selected list item:
        g->DrawString(mText, mListWidget->mX, mListWidget->mY + mListWidget->mHeight + 20);
}



//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::AddedToManager(WidgetManager* theWidgetManager)
{
        // At this point, the Board class has already been added to the
        // widget manager. We should call our parent class' method
        // so that it can be sure to perform any needed tasks, first.
        Widget::AddedToManager(theWidgetManager);

        // You should remember how to create buttons from Demo3. If not,
        // go back and review. We're going to make two buttons which we'll use
        // to do some sound playing when clicked.
        mButton1 = new ButtonWidget(1, this);
        mButton1->Resize(5, 5, 100, 50);
        mButton1->SetFont(FONT_DEFAULT);
        mButton1->mLabel = _S("Sound Left");
        theWidgetManager->AddWidget(mButton1);

        mButton2 = new ButtonWidget(2, this);
        mButton2->Resize(106, 5, 100, 50);
        mButton2->SetFont(FONT_DEFAULT);
        mButton2->mLabel = _S("Sound Right");
        theWidgetManager->AddWidget(mButton2);

        // Let's set up our edit widget, shall we? The constructor is
        // just like it is with all widgets that emit messages: first parameter
        // is the ID to use, second is the class that will listen to its events.
        // We're reusing some IDs here because an edit widget isn't a button
        // widget and thus the IDs won't be confused. In your app however, you
        // will most likely just want to use unique enums or numbers to assign
        // to each specific widget.
        mEditWidget = new EditWidget(1, this);

        // We can't type in text if we don't have a font! Set one:
        mEditWidget->SetFont(FONT_DEFAULT);

        // Let's also set a maximum number of characters allowed.
        // How about 15:
        mEditWidget->mMaxChars = 15;

        // If we wanted to emulate a password edit box, we could set
        // mEditWidget->mPasswordChar. Any character typed in would
        // then be visually replaced by mPasswordChar. We don't
        // want to do that in this demo though.
       
        mEditWidget->Resize(10, 300, 100, 15);
        theWidgetManager->AddWidget(mEditWidget);

        // Let's create a checkbox now. The checkbox widget does NOT
        // have any default drawing methods, unlike the other widgets.
        // You have to specify two images when creating a new one:
        // an unchecked and a checked image. There is another option though:
        // at PopCap it's common to place both the checked and unchecked image
        // into one image file. Observe how we handle this mind warp:
        mCheckboxWidget = new Checkbox(IMAGE_CHECKBOX, IMAGE_CHECKBOX, 1, this);

        // No way! The check AND unchecked images are the same! Yes. Indeed.
        // But we're not done. Now, we need to tell the checkbox that
        // it has to use a different part of the image for checked and a different
        // part for unchecked. This is just like drawing a chunk of an image
        // by specifying a Rect. For this particular image, the first cel
        // contains the unchecked state and the second contains the checked one:
        int checkWidth = IMAGE_CHECKBOX->GetWidth() / 2;
        mCheckboxWidget->mUncheckedRect = Rect(0, 0, checkWidth, IMAGE_CHECKBOX->GetHeight());
        mCheckboxWidget->mCheckedRect = Rect(checkWidth, 0, checkWidth, IMAGE_CHECKBOX->GetHeight());

        // Now add it and resize it
        mCheckboxWidget->Resize(200, 300, checkWidth, IMAGE_CHECKBOX->GetHeight());
        theWidgetManager->AddWidget(mCheckboxWidget);

        // Let's create our list box. The first parameter is the id, second
        // is the font to use, and third is who's going to listen to its events:
        mListWidget = new ListWidget(1, FONT_DEFAULT, this);

        // This next line instructs the list box to draw an outline around itself
        mListWidget->mDrawOutline = true;

        // Now we need to set up the colors to use for the listbox. We create
        // a 2D array containing RGB values, which we pass via SetColors.
        // An explanation of each of the colors is below:
        int listWidgetColors[][3] =
        {
                {255, 255, 255},        //Background color
                {255, 0, 0},            //Outline color
                {0, 0, 0},                      //Plain text not hilited
                {0, 0, 255},            //Text color when mouse is over text
                {128, 128, 128},        //Color of the bar displayed when an item is selected
                {190, 0, 80}            //Color of the text if it has the bar behind it and mouse is not over it
        };

        // Now we set the colors, and tell it that we're setting 6 of them
        mListWidget->SetColors(listWidgetColors, 6);

        // Let's create a scrollbar for the list widget.
        // The list widget is already a scrollbar listener, so we will
        // specify it as the widget responsible for dealing with the
        // scrollbar's messages.
        mScrollbarWidget = new ScrollbarWidget(1, mListWidget);

        // We also need to let the list widget know that it actually has a scrollbar.
        // That way, it will automatically resize it as items are added/removed.
        // IMPORTANT: You should do this before resizing the widgets.
        mListWidget->mScrollbar = mScrollbarWidget;

        // Now resize the list widget
        mListWidget->Resize(300, 300, 100, 100);
       

        // Let's resize the scrollbar to the right of the list box. Instead of using
        // Resize(...), we use ResizeScrollbar to indicate that the entire scrollbar,
        // arrows and thumb included, are to be evenly resized. This will appropriately
        // scale all the items in the scrollbar.
        mScrollbarWidget->ResizeScrollbar(mListWidget->mX + mListWidget->mWidth,
                                                                                mListWidget->mY,
                                                                                25,                                             // an arbitrary width for the bar itself
                                                                                mListWidget->mHeight);

        // Let's make the scrollbar invisible until there are enough items in the list box
        // to make them all not fit
        mScrollbarWidget->SetInvisIfNoScroll(true);

        theWidgetManager->AddWidget(mListWidget);
        theWidgetManager->AddWidget(mScrollbarWidget);

        // If you check out Board::EditWidgetText you'll see that we write out the
        // contents of the list box to disk every time something is added to it, or
        // it is cleared. For files that were created from a buffer, we can read
        // the data back into a buffer for ease of use.
        //
        //      IMPORTANT: You can NOT load files into a buffer if they were not
        //      created from a buffer in the first place. Thus, loading a random
        //      text or binary file is not a good idea, since the buffer will
        //      have no way to identify the datatypes beyond just a standard byte.
        //      Plase read Board::EditWidgetText for an explanation of writing
        //      with buffers.
        //
        // Let's load in the contents of that file, if it exists,
        // and repopulate the list box with it. The first step is to create
        // a Buffer object. The Buffer object will contain all the data
        // in the file:
        Buffer buffer;

        // Check if the file exists:
        if (mApp->FileExists("list_items.dat"))
        {
                // Read in all the data from the file. It will be stored in a format
                // unique to the Buffer object:
                if (!mApp->ReadBufferFromFile("list_items.dat", &buffer))
                {
                        // error, the file was corrupted or some other error occurred
                        mApp->Popup("Could not read contents of list_items.txt");
                }

                // In Board::EditWidgetText we wrote out all the strings
                // via buffer.WriteString. The order in which data is written
                // to the buffer is the order in which it is read. Thus, if
                // you wrote out 2 integers, a string, and 10 booleans, when
                // reading from the buffer you'd first ask for the 2 integers,
                // then the string, then the 10 booleans. This is important:
                // the order DOES matter. If you asked to read a string when
                // a long was actually the next element in the file, you'd
                // get an error. Buffers are very useful for userprofiles or
                // data files like that where you can guarantee an explicit
                // file format.

                while (!buffer.AtEnd())
                        mListWidget->AddLine(StringToSexyStringFast(buffer.ReadString()), true);
        }

        // If you read Board::ListClicked, you'll see that we
        // wrote the last selected item from the list box to the registry.
        // Let's read that value in, if it exists, and set the mText
        // variable to it so that it displays on screen. We use the
        // RegsitryRead... functions to grab values from the regsitry.
        // The values are assumed to be in the registry location that you
        // set by setting the mRegKey variable in GameApp's constructor.
        // The functions return false if there was an error reading the
        // key or the key doesn't exist:
        mApp->RegistryReadString("ListItem", &SexyStringToStringFast(mText));
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::RemovedFromManager(WidgetManager* theWidgetManager)
{
        // This is called after we've been removed from the widget manager.
        // Again, we should let our base class do anything it needs to, first.
        Widget::RemovedFromManager(theWidgetManager);

        // We should now also remove any widgets we are responsible for.
        theWidgetManager->RemoveWidget(mButton1);
        theWidgetManager->RemoveWidget(mButton2);
        theWidgetManager->RemoveWidget(mEditWidget);
        theWidgetManager->RemoveWidget(mCheckboxWidget);
        theWidgetManager->RemoveWidget(mListWidget);
        theWidgetManager->RemoveWidget(mScrollbarWidget);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::ButtonDepress(int theId)
{
        if (theId == 1)
        {
                // Our "left" button was clicked. Let's play a sound
                // in the left speaker with a slight pitch shift.
                // In order to play a pitch shifted sample, we have to do more
                // than just say "PlaySample." We have to get a pointer to the sound
                // instance that represents our sound effect. We do that by asking the
                // app's sound manager to return us a sound instance, and we tell it
                // the ID of the sound file that we want. Let's do that now,
                // using the sound "SOUND_MUTATOR" which we set up in properties/resources.xml:
                SoundInstance* sample = mApp->mSoundManager->GetSoundInstance(SOUND_MUTATOR);

                // It's good to make sure the sample isn't NULL. It would be NULL if you
                // specified an invalid sound id.
                if (sample != NULL)
                {
                        //Now we actually adjust the pitch. Specify the number of
                        //steps to raise (positive) or lower (negative) the original sound by.
                        //We'll just arbitrarily choose 13.
                        sample->AdjustPitch(13);

                        //Let's make it play on the left speaker only. We set a panning value
                        //in decibels, which for DirectX range from -10000 to +10000, where
                        //-10000 is fully left and +10000 is fully right:
                        sample->SetPan(-10000);
                       
                        // Now we have the sample play. This is again slightly different than
                        // our PlaySample from previous demos. The first parameter indicates
                        // whether or not we want the sample to loop (in this case, no), and
                        // the second indicates whether the memory taken up by this special
                        // sound instance pointer should be reclaimed when the sample is done
                        // playing. If true, then the pointer will be invalid once its
                        // done playing, sine the memory will be reclaimed. If false, the
                        // memory won't be reclaimed and you'll have to do it yourself.
                        sample->Play(false, true);
                }
        }
        else if (theId == 2)
        {
                // Let's do the same as we did for the left button, except make it
                // play on the right speaker and pitch shift it down
                SoundInstance* sample = mApp->mSoundManager->GetSoundInstance(SOUND_MUTATOR);
                if (sample != NULL)
                {
                        sample->AdjustPitch(-5);
                        sample->SetPan(10000);
                        sample->Play(false, true);
                }
        }


}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::EditWidgetText(int theId, const std::string& theString)
{
        // This is called when enter is pressed. Let's add the string
        // to the list box and tell it to sort it alphabetically, if the string
        // isn't blank.

        if (theString.length() > 0)
        {
                // Let's also clear everything out of the list box if the user types
                // in the word "CLEAR". StringToUpper is found in Common.h and returns
                // the uppercase version of the string.
                if (StringToUpper(theString) == "CLEAR")
                        mListWidget->RemoveAll();
                else
                        mListWidget->AddLine(StringToSexyStringFast(theString), true);

                // Now clear the edit box
                mEditWidget->SetText(_S(""));

                // As an example of saving data to files, let's write out the contents
                // of the list box. Instead of using the C or C++ way of
                // file I/O, we use buffers. As you'll see in a later demo,
                // buffers are useful for the demo playback system and are a powerful
                // feature of the framework. Plus, they make file I/O easy. The first
                // step is to make a buffer:
                Buffer buffer;
               
                // And now all we do is say WriteString. There are other methods
                // for writing other data types too:
                for (unsigned int i = 0; i < mListWidget->mLines.size(); i++)
                        buffer.WriteString(SexyStringToStringFast(mListWidget->mLines.at(i)));

                // Now we need to write the buffer to disk. GameApp can help us out with
                // that. This data is saved in a special binary format that allows
                // us to read the data easily back into another buffer later.
                mApp->WriteBufferToFile("list_items.dat", &buffer);

                // That's it!
               
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
bool Board::AllowChar(int theId, char theChar)
{
        // As an example of denying input, let's prevent the user
        // from typing in the following: :-+.@#$%^&*()
        switch (theChar)
        {
                case ':':
                case '-':
                case '+':
                case '.':
                case '@':
                case '#':
                case '$':
                case '%':
                case '^':
                case '&':
                case '*':
                case '(':
                case ')':
                        return false;

                default:
                        return true;
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::ListClicked(int theId, int theIdx, int theClickCount)
{
        if (theId == 1)
        {
                // Actually select the index. This is done to allow us to
                // block the selection, if we chose to.
                mListWidget->SetSelect(theIdx);

                // And update the text that's displaying on screen...
                // The strings are stored in the list widget's mLines variable
                // and the one clicked can be indexed via theIdx.
                mText = mListWidget->mLines[theIdx];

                // As an example of writing to the registry, let's write this value
                // and later on load it in when we restart the app. You'll notice
                // a bunch of RegistryWrite... functions in SexyAppBase. Each one
                // of these takes as first argument the name of the value to
                // add to the registry. The second argument is the actual data
                // to store in that value. The value is saved under the registry
                // location that you set in GameApp's constructor when you
                // set the mRegKey variable. The function returns false
                // if there was an error, such as a lack of permission:
                if (!mApp->RegistryWriteString("ListItem", SexyStringToStringFast(mText)))
                        mApp->Popup("Couldn't save \"ListItem\" to registry");
        }
}