summaryrefslogblamecommitdiff
path: root/src/gui/skin.cpp
blob: 2215372012fdf5b504d171088dd2b337f4d9865a (plain) (tree)
1
2
3
4
5
6
7
8
  

                                                                
                                                
                                                       
                                                
  
                                         











                                                                        
                                                                         

   

                     
                   


                           
 
                            
                               
                                      
 
                       
                              
                      
 

                   
                    
 
                                   
                                      
 

                                                


















                                                    
  
 
 
                                                                            

                                       
                 
                        
                



                                






                                
                               
 


                            

 
                                                   
 

                                                                    
 
                                            
                                                                  



                                      

 
                             
 

                                                            

 
                              
 

                                                             

 
                        

                                                        

 
 




                               
 













                                   









                                                      
 
                                    
 
              
     




                                                                            
 

                                         
 
                  
         


                                                                               


         

                                       
 

                
 







                                                        


                                                                          
                                                   
 
 






                                                        
                                                  







                                                                             
     


                                                                         

     


                                                               
                                                                  


                         




                                                              

                                                                



                                       
                                                                             





                                                                  

                                                                      






                                                                           
                                                                                      
                                                
                                                                                      
                                                        
                                                                                      


                                                 
                                                                                      
                                               
                                                                                      
                                                  
                                                                                      


                                                          
                                                                                      
                                                   
                                                                                      
                                                           
                                                                                      
 
                    

                                                                                 

             

            

                                                                           

         
 
                       
 
                                          
 
                                                                                   

                                                                          

                                                                



                                                                             
                                       

                
































































                                                                   
/*
 *  Gui Skinning
 *  Copyright (C) 2008  The Legend of Mazzeroth Development Team
 *  Copyright (C) 2009  Aethyra Development Team
 *  Copyright (C) 2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
 *
 *  This file is part of The Mana Client.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "gui/skin.h"

#include "client.h"
#include "configuration.h"
#include "configlistener.h"
#include "log.h"

#include "resources/image.h"
#include "resources/imageset.h"
#include "resources/resourcemanager.h"

#include "utils/dtor.h"
#include "utils/stringutils.h"
#include "utils/xml.h"

#include <physfs.h>

#include <algorithm>

std::string SkinLoader::mThemePath;
SkinLoader *SkinLoader::mInstance = 0;

class SkinConfigListener : public ConfigListener
{
    public:
        SkinConfigListener(SkinLoader *skinLoader)
            : mSkinLoader(skinLoader)
        {
            config.addListener("guialpha", this);
        }

        ~SkinConfigListener()
        {
            config.removeListener("guialpha", this);
        }

        void optionChanged(const std::string &)
        {
            mSkinLoader->updateAlpha();
        }

    private:
        SkinLoader *mSkinLoader;
};


Skin::Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown,
           const std::string &filePath,
           const std::string &name):
    instances(0),
    mFilePath(filePath),
    mName(name),
    mBorder(skin),
    mCloseImage(close),
    mStickyImageUp(stickyUp),
    mStickyImageDown(stickyDown)
{
}

Skin::~Skin()
{
    // Clean up static resources
    for (int i = 0; i < 9; i++)
        delete mBorder.grid[i];

    mCloseImage->decRef();
    delete mStickyImageUp;
    delete mStickyImageDown;
}

void Skin::updateAlpha(float minimumOpacityAllowed)
{
    const float alpha = std::max((double)minimumOpacityAllowed,
                                 config.getValue("guialpha", 0.8f));

    for_each(mBorder.grid, mBorder.grid + 9,
             std::bind2nd(std::mem_fun(&Image::setAlpha), alpha));

    mCloseImage->setAlpha(alpha);
    mStickyImageUp->setAlpha(alpha);
    mStickyImageDown->setAlpha(alpha);
}

int Skin::getMinWidth() const
{
    return mBorder.grid[ImageRect::UPPER_LEFT]->getWidth() +
           mBorder.grid[ImageRect::UPPER_RIGHT]->getWidth();
}

int Skin::getMinHeight() const
{
    return mBorder.grid[ImageRect::UPPER_LEFT]->getHeight() +
           mBorder.grid[ImageRect::LOWER_LEFT]->getHeight();
}

SkinLoader::SkinLoader()
    : mSkinConfigListener(new SkinConfigListener(this)),
    mMinimumOpacity(-1.0f)
{
}

SkinLoader::~SkinLoader()
{
    delete_all(mSkins);
    delete mSkinConfigListener;
}

SkinLoader *SkinLoader::instance()
{
    if (!mInstance)
        mInstance = new SkinLoader;

    return mInstance;
}

void SkinLoader::deleteInstance()
{
    delete mInstance;
    mInstance = 0;
}

Skin *SkinLoader::load(const std::string &filename,
                       const std::string &defaultPath)
{
    // Check if this skin was already loaded
    SkinIterator skinIterator = mSkins.find(filename);
    if (mSkins.end() != skinIterator)
    {
        skinIterator->second->instances++;
        return skinIterator->second;
    }

    Skin *skin = readSkin(filename);

    if (!skin)
    {
        // Try falling back on the defaultPath if this makes sense
        if (filename != defaultPath)
        {
            logger->log("Error loading skin '%s', falling back on default.",
                        filename.c_str());

            skin = readSkin(defaultPath);
        }

        if (!skin)
        {
            logger->error(strprintf("Error: Loading default skin '%s' failed. "
                                    "Make sure the skin file is valid.",
                                    defaultPath.c_str()));
        }
    }

    // Add the skin to the loaded skins
    mSkins[filename] = skin;

    return skin;
}

void SkinLoader::setMinimumOpacity(float minimumOpacity)
{
    if (minimumOpacity > 1.0f) return;

    mMinimumOpacity = minimumOpacity;
    updateAlpha();
}

void SkinLoader::updateAlpha()
{
    for (SkinIterator iter = mSkins.begin(); iter != mSkins.end(); ++iter)
        iter->second->updateAlpha(mMinimumOpacity);
}

Skin *SkinLoader::readSkin(const std::string &filename)
{
    if (filename.empty())
        return 0;

    logger->log("Loading skin '%s'.", filename.c_str());

    XML::Document doc(resolveThemePath(filename));
    xmlNodePtr rootNode = doc.rootNode();

    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "skinset"))
        return 0;

    const std::string skinSetImage = XML::getProperty(rootNode, "image", "");

    if (skinSetImage.empty())
    {
        logger->log("SkinLoader::readSkin(): Skinset does not define an "
                    "image!");
        return 0;
    }

    logger->log("SkinLoader::load(): <skinset> defines "
                "'%s' as a skin image.", skinSetImage.c_str());

    Image *dBorders = SkinLoader::getImageFromTheme(skinSetImage);
    ImageRect border;

    // iterate <widget>'s
    for_each_xml_child_node(widgetNode, rootNode)
    {
        if (!xmlStrEqual(widgetNode->name, BAD_CAST "widget"))
            continue;

        const std::string widgetType =
                XML::getProperty(widgetNode, "type", "unknown");
        if (widgetType == "Window")
        {
            // Iterate through <part>'s
            // LEEOR / TODO:
            // We need to make provisions to load in a CloseButton image. For
            // now it can just be hard-coded.
            for_each_xml_child_node(partNode, widgetNode)
            {
                if (!xmlStrEqual(partNode->name, BAD_CAST "part"))
                    continue;

                const std::string partType =
                        XML::getProperty(partNode, "type", "unknown");
                // TOP ROW
                const int xPos = XML::getProperty(partNode, "xpos", 0);
                const int yPos = XML::getProperty(partNode, "ypos", 0);
                const int width = XML::getProperty(partNode, "width", 1);
                const int height = XML::getProperty(partNode, "height", 1);

                if (partType == "top-left-corner")
                    border.grid[0] = dBorders->getSubImage(xPos, yPos, width, height);
                else if (partType == "top-edge")
                    border.grid[1] = dBorders->getSubImage(xPos, yPos, width, height);
                else if (partType == "top-right-corner")
                    border.grid[2] = dBorders->getSubImage(xPos, yPos, width, height);

                // MIDDLE ROW
                else if (partType == "left-edge")
                    border.grid[3] = dBorders->getSubImage(xPos, yPos, width, height);
                else if (partType == "bg-quad")
                    border.grid[4] = dBorders->getSubImage(xPos, yPos, width, height);
                else if (partType == "right-edge")
                    border.grid[5] = dBorders->getSubImage(xPos, yPos, width, height);

                // BOTTOM ROW
                else if (partType == "bottom-left-corner")
                    border.grid[6] = dBorders->getSubImage(xPos, yPos, width, height);
                else if (partType == "bottom-edge")
                    border.grid[7] = dBorders->getSubImage(xPos, yPos, width, height);
                else if (partType == "bottom-right-corner")
                    border.grid[8] = dBorders->getSubImage(xPos, yPos, width, height);

                else
                    logger->log("SkinLoader::readSkin(): Unknown part type '%s'",
                                partType.c_str());
            }
        }
        else
        {
            logger->log("SkinLoader::readSkin(): Unknown widget type '%s'",
                        widgetType.c_str());
        }
    }

    dBorders->decRef();

    logger->log("Finished loading skin.");

    // Hard-coded for now until we update the above code to look for window buttons
    Image *closeImage = SkinLoader::getImageFromTheme("close_button.png");
    Image *sticky = SkinLoader::getImageFromTheme("sticky_button.png");
    Image *stickyImageUp = sticky->getSubImage(0, 0, 15, 15);
    Image *stickyImageDown = sticky->getSubImage(15, 0, 15, 15);
    sticky->decRef();

    Skin *skin = new Skin(border, closeImage, stickyImageUp, stickyImageDown,
                          filename);
    skin->updateAlpha(mMinimumOpacity);
    return skin;
}

bool SkinLoader::tryThemePath(std::string themePath)
{
    if (!themePath.empty())
    {
        themePath = "graphics/gui/" + themePath;
        if (PHYSFS_exists(themePath.c_str()))
        {
            mThemePath = themePath;
            return true;
        }
    }

    return false;
}

void SkinLoader::prepareThemePath()
{
    // Try theme from settings
    if (tryThemePath(config.getValue("theme", "")))
        return;

    // Try theme from branding
    if (tryThemePath(branding.getValue("theme", "")))
        return;

    // Use default
    mThemePath = "graphics/gui";
}

std::string SkinLoader::resolveThemePath(const std::string &path)
{
    // Need to strip off any dye info for the existence tests
    int pos = path.find('|');
    std::string file;
    if (pos > 0)
        file = path.substr(0, pos);
    else
        file = path;

    // Might be a valid path already
    if (PHYSFS_exists(file.c_str()))
        return path;

    // Try the theme
    file = getThemePath() + "/" + file;
    if (PHYSFS_exists(file.c_str()))
        return getThemePath() + "/" + path;

    // Backup
    return "graphics/gui/" + path;
}

Image *SkinLoader::getImageFromTheme(const std::string &path)
{
    ResourceManager *resman = ResourceManager::getInstance();
    return resman->getImage(resolveThemePath(path));
}

ImageSet *SkinLoader::getImageSetFromTheme(const std::string &path,
                                        int w, int h)
{
    ResourceManager *resman = ResourceManager::getInstance();
    return resman->getImageSet(resolveThemePath(path), w, h);
}