mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-21 21:55:38 +00:00
Add video view that is based on detail view but allows themes to include a video preview of the selected game along with a marquee image
This commit is contained in:
parent
79cce69771
commit
25e1067794
55
CMake/Packages/FindVLC.cmake
Normal file
55
CMake/Packages/FindVLC.cmake
Normal file
|
@ -0,0 +1,55 @@
|
|||
# - Try to find VLC library
|
||||
# Once done this will define
|
||||
#
|
||||
# VLC_FOUND - system has VLC
|
||||
# VLC_INCLUDE_DIR - The VLC include directory
|
||||
# VLC_LIBRARIES - The libraries needed to use VLC
|
||||
# VLC_DEFINITIONS - Compiler switches required for using VLC
|
||||
#
|
||||
# Copyright (C) 2008, Tanguy Krotoff <tkrotoff@gmail.com>
|
||||
# Copyright (C) 2008, Lukas Durfina <lukas.durfina@gmail.com>
|
||||
# Copyright (c) 2009, Fathi Boudra <fboudra@gmail.com>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
#
|
||||
|
||||
if(VLC_INCLUDE_DIR AND VLC_LIBRARIES)
|
||||
# in cache already
|
||||
set(VLC_FIND_QUIETLY TRUE)
|
||||
endif(VLC_INCLUDE_DIR AND VLC_LIBRARIES)
|
||||
|
||||
# use pkg-config to get the directories and then use these values
|
||||
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||
if(NOT WIN32)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(VLC libvlc>=1.0.0)
|
||||
set(VLC_DEFINITIONS ${VLC_CFLAGS})
|
||||
set(VLC_LIBRARIES ${VLC_LDFLAGS})
|
||||
endif(NOT WIN32)
|
||||
|
||||
# TODO add argument support to pass version on find_package
|
||||
include(MacroEnsureVersion)
|
||||
macro_ensure_version(1.0.0 ${VLC_VERSION} VLC_VERSION_OK)
|
||||
if(VLC_VERSION_OK)
|
||||
set(VLC_FOUND TRUE)
|
||||
message(STATUS "VLC library found")
|
||||
else(VLC_VERSION_OK)
|
||||
set(VLC_FOUND FALSE)
|
||||
message(FATAL_ERROR "VLC library not found")
|
||||
endif(VLC_VERSION_OK)
|
||||
|
||||
find_path(VLC_INCLUDE_DIR
|
||||
NAMES vlc.h
|
||||
PATHS ${VLC_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES vlc)
|
||||
|
||||
find_library(VLC_LIBRARIES
|
||||
NAMES vlc
|
||||
PATHS ${VLC_LIBRARY_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(VLC DEFAULT_MSG VLC_INCLUDE_DIR VLC_LIBRARIES)
|
||||
|
||||
# show the VLC_INCLUDE_DIR and VLC_LIBRARIES variables only in the advanced view
|
||||
mark_as_advanced(VLC_INCLUDE_DIR VLC_LIBRARIES)
|
117
CMake/Packages/MacroEnsureVersion.cmake
Normal file
117
CMake/Packages/MacroEnsureVersion.cmake
Normal file
|
@ -0,0 +1,117 @@
|
|||
# This file defines the following macros for developers to use in ensuring
|
||||
# that installed software is of the right version:
|
||||
#
|
||||
# MACRO_ENSURE_VERSION - test that a version number is greater than
|
||||
# or equal to some minimum
|
||||
# MACRO_ENSURE_VERSION_RANGE - test that a version number is greater than
|
||||
# or equal to some minimum and less than some
|
||||
# maximum
|
||||
# MACRO_ENSURE_VERSION2 - deprecated, do not use in new code
|
||||
#
|
||||
|
||||
# MACRO_ENSURE_VERSION
|
||||
# This macro compares version numbers of the form "x.y.z" or "x.y"
|
||||
# MACRO_ENSURE_VERSION( FOO_MIN_VERSION FOO_VERSION_FOUND FOO_VERSION_OK)
|
||||
# will set FOO_VERSION_OK to true if FOO_VERSION_FOUND >= FOO_MIN_VERSION
|
||||
# Leading and trailing text is ok, e.g.
|
||||
# MACRO_ENSURE_VERSION( "2.5.31" "flex 2.5.4a" VERSION_OK)
|
||||
# which means 2.5.31 is required and "flex 2.5.4a" is what was found on the system
|
||||
|
||||
# Copyright (c) 2006, David Faure, <faure@kde.org>
|
||||
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
# MACRO_ENSURE_VERSION_RANGE
|
||||
# This macro ensures that a version number of the form
|
||||
# "x.y.z" or "x.y" falls within a range defined by
|
||||
# min_version <= found_version < max_version.
|
||||
# If this expression holds, FOO_VERSION_OK will be set TRUE
|
||||
#
|
||||
# Example: MACRO_ENSURE_VERSION_RANGE3( "0.1.0" ${FOOCODE_VERSION} "0.7.0" FOO_VERSION_OK )
|
||||
#
|
||||
# This macro will break silently if any of x,y,z are greater than 100.
|
||||
#
|
||||
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
# NORMALIZE_VERSION
|
||||
# Helper macro to convert version numbers of the form "x.y.z"
|
||||
# to an integer equal to 10^4 * x + 10^2 * y + z
|
||||
#
|
||||
# This macro will break silently if any of x,y,z are greater than 100.
|
||||
#
|
||||
# Copyright (c) 2006, David Faure, <faure@kde.org>
|
||||
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
# CHECK_RANGE_INCLUSIVE_LOWER
|
||||
# Helper macro to check whether x <= y < z
|
||||
#
|
||||
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
|
||||
MACRO(NORMALIZE_VERSION _requested_version _normalized_version)
|
||||
STRING(REGEX MATCH "[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" _threePartMatch "${_requested_version}")
|
||||
if (_threePartMatch)
|
||||
# parse the parts of the version string
|
||||
STRING(REGEX REPLACE "[^0-9]*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major_vers "${_requested_version}")
|
||||
STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" _minor_vers "${_requested_version}")
|
||||
STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _patch_vers "${_requested_version}")
|
||||
else (_threePartMatch)
|
||||
STRING(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" _major_vers "${_requested_version}")
|
||||
STRING(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" _minor_vers "${_requested_version}")
|
||||
set(_patch_vers "0")
|
||||
endif (_threePartMatch)
|
||||
|
||||
# compute an overall version number which can be compared at once
|
||||
MATH(EXPR ${_normalized_version} "${_major_vers}*10000 + ${_minor_vers}*100 + ${_patch_vers}")
|
||||
ENDMACRO(NORMALIZE_VERSION)
|
||||
|
||||
MACRO(MACRO_CHECK_RANGE_INCLUSIVE_LOWER _lower_limit _value _upper_limit _ok)
|
||||
if (${_value} LESS ${_lower_limit})
|
||||
set( ${_ok} FALSE )
|
||||
elseif (${_value} EQUAL ${_lower_limit})
|
||||
set( ${_ok} TRUE )
|
||||
elseif (${_value} EQUAL ${_upper_limit})
|
||||
set( ${_ok} FALSE )
|
||||
elseif (${_value} GREATER ${_upper_limit})
|
||||
set( ${_ok} FALSE )
|
||||
else (${_value} LESS ${_lower_limit})
|
||||
set( ${_ok} TRUE )
|
||||
endif (${_value} LESS ${_lower_limit})
|
||||
ENDMACRO(MACRO_CHECK_RANGE_INCLUSIVE_LOWER)
|
||||
|
||||
MACRO(MACRO_ENSURE_VERSION requested_version found_version var_too_old)
|
||||
NORMALIZE_VERSION( ${requested_version} req_vers_num )
|
||||
NORMALIZE_VERSION( ${found_version} found_vers_num )
|
||||
|
||||
if (found_vers_num LESS req_vers_num)
|
||||
set( ${var_too_old} FALSE )
|
||||
else (found_vers_num LESS req_vers_num)
|
||||
set( ${var_too_old} TRUE )
|
||||
endif (found_vers_num LESS req_vers_num)
|
||||
|
||||
ENDMACRO(MACRO_ENSURE_VERSION)
|
||||
|
||||
MACRO(MACRO_ENSURE_VERSION2 requested_version2 found_version2 var_too_old2)
|
||||
MACRO_ENSURE_VERSION( ${requested_version2} ${found_version2} ${var_too_old2})
|
||||
ENDMACRO(MACRO_ENSURE_VERSION2)
|
||||
|
||||
MACRO(MACRO_ENSURE_VERSION_RANGE min_version found_version max_version var_ok)
|
||||
NORMALIZE_VERSION( ${min_version} req_vers_num )
|
||||
NORMALIZE_VERSION( ${found_version} found_vers_num )
|
||||
NORMALIZE_VERSION( ${max_version} max_vers_num )
|
||||
|
||||
MACRO_CHECK_RANGE_INCLUSIVE_LOWER( ${req_vers_num} ${found_vers_num} ${max_vers_num} ${var_ok})
|
||||
ENDMACRO(MACRO_ENSURE_VERSION_RANGE)
|
||||
|
||||
|
|
@ -48,6 +48,7 @@ find_package(SDL2 REQUIRED)
|
|||
find_package(Boost REQUIRED COMPONENTS system filesystem date_time locale)
|
||||
find_package(Eigen3 REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
find_package(VLC REQUIRED)
|
||||
|
||||
#add ALSA for Linux
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
|
@ -99,6 +100,7 @@ set(COMMON_INCLUDE_DIRS
|
|||
${Boost_INCLUDE_DIRS}
|
||||
${EIGEN3_INCLUDE_DIR}
|
||||
${CURL_INCLUDE_DIR}
|
||||
${VLC_INCLUDE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/external
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/es-core/src
|
||||
)
|
||||
|
@ -148,6 +150,7 @@ set(COMMON_LIBRARIES
|
|||
${FreeImage_LIBRARIES}
|
||||
${SDL2_LIBRARY}
|
||||
${CURL_LIBRARIES}
|
||||
${VLC_LIBRARIES}
|
||||
pugixml
|
||||
nanosvg
|
||||
)
|
||||
|
|
|
@ -41,7 +41,10 @@ EmulationStation has a few dependencies. For building, you'll need CMake, SDL2,
|
|||
**On Debian/Ubuntu:**
|
||||
All of this be easily installed with apt-get:
|
||||
```bash
|
||||
sudo apt-get install libsdl2-dev libboost-system-dev libboost-filesystem-dev libboost-date-time-dev libboost-locale-dev libfreeimage-dev libfreetype6-dev libeigen3-dev libcurl4-openssl-dev libasound2-dev libgl1-mesa-dev build-essential cmake fonts-droid
|
||||
sudo apt-get install libsdl2-dev libboost-system-dev libboost-filesystem-dev libboost-date-time-dev \
|
||||
libboost-locale-dev libfreeimage-dev libfreetype6-dev libeigen3-dev libcurl4-openssl-dev \
|
||||
libasound2-dev libgl1-mesa-dev build-essential cmake fonts-droid \
|
||||
libvlc-dev libvlccore-dev vlc-nox
|
||||
```
|
||||
|
||||
Then, generate and build the Makefile with CMake:
|
||||
|
|
|
@ -38,6 +38,7 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h
|
||||
|
||||
|
@ -84,6 +85,7 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
|
||||
)
|
||||
|
|
|
@ -81,6 +81,32 @@ const std::string& FileData::getThumbnailPath() const
|
|||
return metadata.get("image");
|
||||
}
|
||||
|
||||
const std::string& FileData::getVideoPath() const
|
||||
{
|
||||
if (mType == GAME)
|
||||
{
|
||||
return metadata.get("video");
|
||||
}
|
||||
else
|
||||
{
|
||||
static std::string empty;
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& FileData::getMarqueePath() const
|
||||
{
|
||||
if (mType == GAME)
|
||||
{
|
||||
return metadata.get("marquee");
|
||||
}
|
||||
else
|
||||
{
|
||||
static std::string empty;
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const
|
||||
{
|
||||
|
|
|
@ -45,6 +45,8 @@ public:
|
|||
inline SystemData* getSystem() const { return mSystem; }
|
||||
|
||||
virtual const std::string& getThumbnailPath() const;
|
||||
virtual const std::string& getVideoPath() const;
|
||||
virtual const std::string& getMarqueePath() const;
|
||||
|
||||
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@ MetaDataDecl gameDecls[] = {
|
|||
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
|
||||
{"name", MD_STRING, "", false, "name", "enter game name"},
|
||||
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
|
||||
{"image", MD_IMAGE_PATH, "", false, "image", "enter path to image"},
|
||||
{"thumbnail", MD_IMAGE_PATH, "", false, "thumbnail", "enter path to thumbnail"},
|
||||
{"image", MD_PATH, "", false, "image", "enter path to image"},
|
||||
{"video", MD_PATH , "", false, "video", "enter path to video"},
|
||||
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
|
||||
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
|
||||
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
|
||||
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
||||
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
||||
|
@ -25,8 +27,8 @@ const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls)
|
|||
MetaDataDecl folderDecls[] = {
|
||||
{"name", MD_STRING, "", false},
|
||||
{"desc", MD_MULTILINE_STRING, "", false},
|
||||
{"image", MD_IMAGE_PATH, "", false},
|
||||
{"thumbnail", MD_IMAGE_PATH, "", false},
|
||||
{"image", MD_PATH, "", false},
|
||||
{"thumbnail", MD_PATH, "", false},
|
||||
};
|
||||
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));
|
||||
|
||||
|
@ -68,9 +70,10 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node n
|
|||
{
|
||||
// if it's a path, resolve relative paths
|
||||
std::string value = md.text().get();
|
||||
if(iter->type == MD_IMAGE_PATH)
|
||||
if (iter->type == MD_PATH)
|
||||
{
|
||||
value = resolvePath(value, relativeTo, true).generic_string();
|
||||
|
||||
}
|
||||
mdl.set(iter->key, value);
|
||||
}else{
|
||||
mdl.set(iter->key, iter->defaultValue);
|
||||
|
@ -96,7 +99,7 @@ void MetaDataList::appendToXML(pugi::xml_node parent, bool ignoreDefaults, const
|
|||
|
||||
// try and make paths relative if we can
|
||||
std::string value = mapIter->second;
|
||||
if(mddIter->type == MD_IMAGE_PATH)
|
||||
if (mddIter->type == MD_PATH)
|
||||
value = makeRelativePath(value, relativeTo, true).generic_string();
|
||||
|
||||
parent.append_child(mapIter->first.c_str()).text().set(value.c_str());
|
||||
|
|
|
@ -16,7 +16,7 @@ enum MetaDataType
|
|||
|
||||
//specialized types
|
||||
MD_MULTILINE_STRING,
|
||||
MD_IMAGE_PATH,
|
||||
MD_PATH,
|
||||
MD_RATING,
|
||||
MD_DATE,
|
||||
MD_TIME //used for lastplayed
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
|
||||
#include "views/gamelist/BasicGameListView.h"
|
||||
#include "views/gamelist/DetailedGameListView.h"
|
||||
#include "views/gamelist/VideoGameListView.h"
|
||||
#include "views/gamelist/GridGameListView.h"
|
||||
#include "guis/GuiMenu.h"
|
||||
#include "guis/GuiMsgBox.h"
|
||||
#include "animations/LaunchAnimation.h"
|
||||
#include "animations/MoveCameraAnimation.h"
|
||||
#include "animations/LambdaAnimation.h"
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
ViewController* ViewController::sInstance = NULL;
|
||||
|
||||
|
@ -55,6 +57,12 @@ int ViewController::getSystemId(SystemData* system)
|
|||
|
||||
void ViewController::goToSystemView(SystemData* system)
|
||||
{
|
||||
// Tell any current view it's about to be hidden
|
||||
if (mCurrentView)
|
||||
{
|
||||
mCurrentView->onHide();
|
||||
}
|
||||
|
||||
mState.viewing = SYSTEM_SELECT;
|
||||
mState.system = system;
|
||||
|
||||
|
@ -99,7 +107,15 @@ void ViewController::goToGameList(SystemData* system)
|
|||
mState.viewing = GAME_LIST;
|
||||
mState.system = system;
|
||||
|
||||
if (mCurrentView)
|
||||
{
|
||||
mCurrentView->onHide();
|
||||
}
|
||||
mCurrentView = getGameListView(system);
|
||||
if (mCurrentView)
|
||||
{
|
||||
mCurrentView->onShow();
|
||||
}
|
||||
playViewTransition();
|
||||
}
|
||||
|
||||
|
@ -163,6 +179,10 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center)
|
|||
return;
|
||||
}
|
||||
|
||||
// Hide the current view
|
||||
if (mCurrentView)
|
||||
mCurrentView->onHide();
|
||||
|
||||
Eigen::Affine3f origCamera = mCamera;
|
||||
origCamera.translation() = -mCurrentView->getPosition();
|
||||
|
||||
|
@ -210,17 +230,26 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
|
|||
|
||||
//decide type
|
||||
bool detailed = false;
|
||||
bool video = false;
|
||||
std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
|
||||
for(auto it = files.begin(); it != files.end(); it++)
|
||||
{
|
||||
if(!(*it)->getThumbnailPath().empty())
|
||||
if(!(*it)->getVideoPath().empty())
|
||||
{
|
||||
video = true;
|
||||
break;
|
||||
}
|
||||
else if(!(*it)->getThumbnailPath().empty())
|
||||
{
|
||||
detailed = true;
|
||||
break;
|
||||
// Don't break out in case any subsequent files have video
|
||||
}
|
||||
}
|
||||
|
||||
if(detailed)
|
||||
if (video)
|
||||
// Create the view
|
||||
view = std::shared_ptr<IGameListView>(new VideoGameListView(mWindow, system->getRootFolder()));
|
||||
else if(detailed)
|
||||
view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
|
||||
else
|
||||
view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
|
||||
|
@ -347,6 +376,10 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
|||
break;
|
||||
}
|
||||
}
|
||||
// Redisplay the current view
|
||||
if (mCurrentView)
|
||||
mCurrentView->onShow();
|
||||
|
||||
}
|
||||
|
||||
void ViewController::reloadAll()
|
||||
|
|
345
es-app/src/views/gamelist/VideoGameListView.cpp
Normal file
345
es-app/src/views/gamelist/VideoGameListView.cpp
Normal file
|
@ -0,0 +1,345 @@
|
|||
#include "views/gamelist/VideoGameListView.h"
|
||||
#include "views/ViewController.h"
|
||||
#include "Window.h"
|
||||
#include "animations/LambdaAnimation.h"
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
VideoGameListView::VideoGameListView(Window* window, FileData* root) :
|
||||
BasicGameListView(window, root),
|
||||
mDescContainer(window), mDescription(window),
|
||||
mMarquee(window),
|
||||
mImage(window),
|
||||
mVideo(window),
|
||||
mVideoPlaying(false),
|
||||
|
||||
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
|
||||
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window),
|
||||
|
||||
mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window),
|
||||
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window)
|
||||
{
|
||||
const float padding = 0.01f;
|
||||
|
||||
mList.setPosition(mSize.x() * (0.50f + padding), mList.getPosition().y());
|
||||
mList.setSize(mSize.x() * (0.50f - padding), mList.getSize().y());
|
||||
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
|
||||
mList.setCursorChangedCallback([&](const CursorState& state) { updateInfoPanel(); });
|
||||
|
||||
// Marquee
|
||||
mMarquee.setOrigin(0.5f, 0.5f);
|
||||
mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f);
|
||||
mMarquee.setMaxSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.18f);
|
||||
addChild(&mMarquee);
|
||||
|
||||
// Image
|
||||
mImage.setOrigin(0.5f, 0.5f);
|
||||
// Default to off the screen
|
||||
mImage.setPosition(2.0f, 2.0f);
|
||||
mImage.setMaxSize(1.0f, 1.0f);
|
||||
addChild(&mImage);
|
||||
|
||||
// video
|
||||
mVideo.setOrigin(0.5f, 0.5f);
|
||||
mVideo.setPosition(mSize.x() * 0.25f, mSize.y() * 0.4f);
|
||||
mVideo.setSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.4f);
|
||||
addChild(&mVideo);
|
||||
|
||||
// We want the video to be in front of the background but behind any 'extra' images
|
||||
for (std::vector<GuiComponent*>::iterator it = mChildren.begin(); it != mChildren.end(); ++it)
|
||||
{
|
||||
if (*it == &mThemeExtras)
|
||||
{
|
||||
mChildren.insert(it, &mVideo);
|
||||
mChildren.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// metadata labels + values
|
||||
mLblRating.setText("Rating: ");
|
||||
addChild(&mLblRating);
|
||||
addChild(&mRating);
|
||||
mLblReleaseDate.setText("Released: ");
|
||||
addChild(&mLblReleaseDate);
|
||||
addChild(&mReleaseDate);
|
||||
mLblDeveloper.setText("Developer: ");
|
||||
addChild(&mLblDeveloper);
|
||||
addChild(&mDeveloper);
|
||||
mLblPublisher.setText("Publisher: ");
|
||||
addChild(&mLblPublisher);
|
||||
addChild(&mPublisher);
|
||||
mLblGenre.setText("Genre: ");
|
||||
addChild(&mLblGenre);
|
||||
addChild(&mGenre);
|
||||
mLblPlayers.setText("Players: ");
|
||||
addChild(&mLblPlayers);
|
||||
addChild(&mPlayers);
|
||||
mLblLastPlayed.setText("Last played: ");
|
||||
addChild(&mLblLastPlayed);
|
||||
mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW);
|
||||
addChild(&mLastPlayed);
|
||||
mLblPlayCount.setText("Times played: ");
|
||||
addChild(&mLblPlayCount);
|
||||
addChild(&mPlayCount);
|
||||
|
||||
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
|
||||
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y());
|
||||
mDescContainer.setAutoScroll(true);
|
||||
addChild(&mDescContainer);
|
||||
|
||||
mDescription.setFont(Font::get(FONT_SIZE_SMALL));
|
||||
mDescription.setSize(mDescContainer.getSize().x(), 0);
|
||||
mDescContainer.addChild(&mDescription);
|
||||
|
||||
initMDLabels();
|
||||
initMDValues();
|
||||
updateInfoPanel();
|
||||
}
|
||||
|
||||
VideoGameListView::~VideoGameListView()
|
||||
{
|
||||
}
|
||||
|
||||
void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||
{
|
||||
BasicGameListView::onThemeChanged(theme);
|
||||
|
||||
using namespace ThemeFlags;
|
||||
mMarquee.applyTheme(theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE);
|
||||
mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE);
|
||||
mVideo.applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY);
|
||||
|
||||
initMDLabels();
|
||||
std::vector<TextComponent*> labels = getMDLabels();
|
||||
assert(labels.size() == 8);
|
||||
const char* lblElements[8] = {
|
||||
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
|
||||
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"
|
||||
};
|
||||
|
||||
for(unsigned int i = 0; i < labels.size(); i++)
|
||||
{
|
||||
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
|
||||
}
|
||||
|
||||
|
||||
initMDValues();
|
||||
std::vector<GuiComponent*> values = getMDValues();
|
||||
assert(values.size() == 8);
|
||||
const char* valElements[8] = {
|
||||
"md_rating", "md_releasedate", "md_developer", "md_publisher",
|
||||
"md_genre", "md_players", "md_lastplayed", "md_playcount"
|
||||
};
|
||||
|
||||
for(unsigned int i = 0; i < values.size(); i++)
|
||||
{
|
||||
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
|
||||
}
|
||||
|
||||
mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE);
|
||||
mDescription.setSize(mDescContainer.getSize().x(), 0);
|
||||
mDescription.applyTheme(theme, getName(), "md_description", ALL ^ (POSITION | ThemeFlags::SIZE | TEXT));
|
||||
}
|
||||
|
||||
void VideoGameListView::initMDLabels()
|
||||
{
|
||||
using namespace Eigen;
|
||||
|
||||
std::vector<TextComponent*> components = getMDLabels();
|
||||
|
||||
const unsigned int colCount = 2;
|
||||
const unsigned int rowCount = components.size() / 2;
|
||||
|
||||
Vector3f start(mSize.x() * 0.01f, mSize.y() * 0.625f, 0.0f);
|
||||
|
||||
const float colSize = (mSize.x() * 0.48f) / colCount;
|
||||
const float rowPadding = 0.01f * mSize.y();
|
||||
|
||||
for(unsigned int i = 0; i < components.size(); i++)
|
||||
{
|
||||
const unsigned int row = i % rowCount;
|
||||
Vector3f pos(0.0f, 0.0f, 0.0f);
|
||||
if(row == 0)
|
||||
{
|
||||
pos = start + Vector3f(colSize * (i / rowCount), 0, 0);
|
||||
}else{
|
||||
// work from the last component
|
||||
GuiComponent* lc = components[i-1];
|
||||
pos = lc->getPosition() + Vector3f(0, lc->getSize().y() + rowPadding, 0);
|
||||
}
|
||||
|
||||
components[i]->setFont(Font::get(FONT_SIZE_SMALL));
|
||||
components[i]->setPosition(pos);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoGameListView::initMDValues()
|
||||
{
|
||||
using namespace Eigen;
|
||||
|
||||
std::vector<TextComponent*> labels = getMDLabels();
|
||||
std::vector<GuiComponent*> values = getMDValues();
|
||||
|
||||
std::shared_ptr<Font> defaultFont = Font::get(FONT_SIZE_SMALL);
|
||||
mRating.setSize(defaultFont->getHeight() * 5.0f, (float)defaultFont->getHeight());
|
||||
mReleaseDate.setFont(defaultFont);
|
||||
mDeveloper.setFont(defaultFont);
|
||||
mPublisher.setFont(defaultFont);
|
||||
mGenre.setFont(defaultFont);
|
||||
mPlayers.setFont(defaultFont);
|
||||
mLastPlayed.setFont(defaultFont);
|
||||
mPlayCount.setFont(defaultFont);
|
||||
|
||||
float bottom = 0.0f;
|
||||
|
||||
const float colSize = (mSize.x() * 0.48f) / 2;
|
||||
for(unsigned int i = 0; i < labels.size(); i++)
|
||||
{
|
||||
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2;
|
||||
values[i]->setPosition(labels[i]->getPosition() + Vector3f(labels[i]->getSize().x(), heightDiff, 0));
|
||||
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
|
||||
|
||||
float testBot = values[i]->getPosition().y() + values[i]->getSize().y();
|
||||
if(testBot > bottom)
|
||||
bottom = testBot;
|
||||
}
|
||||
|
||||
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
|
||||
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VideoGameListView::updateInfoPanel()
|
||||
{
|
||||
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? NULL : mList.getSelected();
|
||||
|
||||
bool fadingOut;
|
||||
if(file == NULL)
|
||||
{
|
||||
mVideo.setVideo("");
|
||||
mVideo.setImage("");
|
||||
mVideoPlaying = false;
|
||||
//mMarquee.setImage("");
|
||||
//mDescription.setText("");
|
||||
fadingOut = true;
|
||||
|
||||
}else{
|
||||
std::string video_path;
|
||||
std::string marquee_path;
|
||||
std::string thumbnail_path;
|
||||
video_path = file->getVideoPath();
|
||||
marquee_path = file->getMarqueePath();
|
||||
thumbnail_path = file->getThumbnailPath();
|
||||
|
||||
if (!video_path.empty() && (video_path[0] == '~'))
|
||||
{
|
||||
video_path.erase(0, 1);
|
||||
video_path.insert(0, getHomePath());
|
||||
}
|
||||
if (!marquee_path.empty() && (marquee_path[0] == '~'))
|
||||
{
|
||||
marquee_path.erase(0, 1);
|
||||
marquee_path.insert(0, getHomePath());
|
||||
}
|
||||
if (!thumbnail_path.empty() && (thumbnail_path[0] == '~'))
|
||||
{
|
||||
thumbnail_path.erase(0, 1);
|
||||
thumbnail_path.insert(0, getHomePath());
|
||||
}
|
||||
if (!mVideo.setVideo(video_path))
|
||||
mVideo.setDefaultVideo();
|
||||
mVideoPlaying = true;
|
||||
|
||||
mVideo.setImage(thumbnail_path);
|
||||
mMarquee.setImage(marquee_path);
|
||||
mImage.setImage(thumbnail_path);
|
||||
|
||||
mDescription.setText(file->metadata.get("desc"));
|
||||
mDescContainer.reset();
|
||||
|
||||
if(file->getType() == GAME)
|
||||
{
|
||||
mRating.setValue(file->metadata.get("rating"));
|
||||
mReleaseDate.setValue(file->metadata.get("releasedate"));
|
||||
mDeveloper.setValue(file->metadata.get("developer"));
|
||||
mPublisher.setValue(file->metadata.get("publisher"));
|
||||
mGenre.setValue(file->metadata.get("genre"));
|
||||
mPlayers.setValue(file->metadata.get("players"));
|
||||
mLastPlayed.setValue(file->metadata.get("lastplayed"));
|
||||
mPlayCount.setValue(file->metadata.get("playcount"));
|
||||
}
|
||||
|
||||
fadingOut = false;
|
||||
}
|
||||
|
||||
std::vector<GuiComponent*> comps = getMDValues();
|
||||
comps.push_back(&mMarquee);
|
||||
comps.push_back(&mVideo);
|
||||
comps.push_back(&mDescription);
|
||||
comps.push_back(&mImage);
|
||||
std::vector<TextComponent*> labels = getMDLabels();
|
||||
comps.insert(comps.end(), labels.begin(), labels.end());
|
||||
|
||||
for(auto it = comps.begin(); it != comps.end(); it++)
|
||||
{
|
||||
GuiComponent* comp = *it;
|
||||
// an animation is playing
|
||||
// then animate if reverse != fadingOut
|
||||
// an animation is not playing
|
||||
// then animate if opacity != our target opacity
|
||||
if((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
|
||||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255)))
|
||||
{
|
||||
auto func = [comp](float t)
|
||||
{
|
||||
comp->setOpacity((unsigned char)(lerp<float>(0.0f, 1.0f, t)*255));
|
||||
};
|
||||
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoGameListView::launch(FileData* game)
|
||||
{
|
||||
Eigen::Vector3f target(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0);
|
||||
if(mMarquee.hasImage())
|
||||
target << mVideo.getCenter().x(), mVideo.getCenter().y(), 0;
|
||||
|
||||
ViewController::get()->launch(game, target);
|
||||
}
|
||||
|
||||
std::vector<TextComponent*> VideoGameListView::getMDLabels()
|
||||
{
|
||||
std::vector<TextComponent*> ret;
|
||||
ret.push_back(&mLblRating);
|
||||
ret.push_back(&mLblReleaseDate);
|
||||
ret.push_back(&mLblDeveloper);
|
||||
ret.push_back(&mLblPublisher);
|
||||
ret.push_back(&mLblGenre);
|
||||
ret.push_back(&mLblPlayers);
|
||||
ret.push_back(&mLblLastPlayed);
|
||||
ret.push_back(&mLblPlayCount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<GuiComponent*> VideoGameListView::getMDValues()
|
||||
{
|
||||
std::vector<GuiComponent*> ret;
|
||||
ret.push_back(&mRating);
|
||||
ret.push_back(&mReleaseDate);
|
||||
ret.push_back(&mDeveloper);
|
||||
ret.push_back(&mPublisher);
|
||||
ret.push_back(&mGenre);
|
||||
ret.push_back(&mPlayers);
|
||||
ret.push_back(&mLastPlayed);
|
||||
ret.push_back(&mPlayCount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VideoGameListView::update(int deltaTime)
|
||||
{
|
||||
BasicGameListView::update(deltaTime);
|
||||
mVideo.update(deltaTime);
|
||||
}
|
53
es-app/src/views/gamelist/VideoGameListView.h
Normal file
53
es-app/src/views/gamelist/VideoGameListView.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include "views/gamelist/BasicGameListView.h"
|
||||
#include "components/ScrollableContainer.h"
|
||||
#include "components/RatingComponent.h"
|
||||
#include "components/DateTimeComponent.h"
|
||||
#include "components/VideoComponent.h"
|
||||
|
||||
class VideoGameListView : public BasicGameListView
|
||||
{
|
||||
public:
|
||||
VideoGameListView(Window* window, FileData* root);
|
||||
virtual ~VideoGameListView();
|
||||
|
||||
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
|
||||
|
||||
virtual const char* getName() const override { return "video"; }
|
||||
|
||||
protected:
|
||||
virtual void launch(FileData* game) override;
|
||||
|
||||
virtual void update(int deltaTime) override;
|
||||
|
||||
private:
|
||||
void updateInfoPanel();
|
||||
|
||||
void initMDLabels();
|
||||
void initMDValues();
|
||||
|
||||
ImageComponent mMarquee;
|
||||
VideoComponent mVideo;
|
||||
ImageComponent mImage;
|
||||
|
||||
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount;
|
||||
|
||||
RatingComponent mRating;
|
||||
DateTimeComponent mReleaseDate;
|
||||
TextComponent mDeveloper;
|
||||
TextComponent mPublisher;
|
||||
TextComponent mGenre;
|
||||
TextComponent mPlayers;
|
||||
DateTimeComponent mLastPlayed;
|
||||
TextComponent mPlayCount;
|
||||
|
||||
std::vector<TextComponent*> getMDLabels();
|
||||
std::vector<GuiComponent*> getMDValues();
|
||||
|
||||
ScrollableContainer mDescContainer;
|
||||
TextComponent mDescription;
|
||||
|
||||
bool mVideoPlaying;
|
||||
|
||||
};
|
|
@ -42,6 +42,7 @@ set(CORE_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h
|
||||
|
||||
# Guis
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h
|
||||
|
@ -96,6 +97,7 @@ set(CORE_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp
|
||||
|
||||
# Guis
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp
|
||||
|
|
|
@ -345,3 +345,17 @@ bool GuiComponent::isProcessing() const
|
|||
{
|
||||
return mIsProcessing;
|
||||
}
|
||||
|
||||
void GuiComponent::onShow()
|
||||
{
|
||||
for(unsigned int i = 0; i < getChildCount(); i++)
|
||||
getChild(i)->onShow();
|
||||
}
|
||||
|
||||
void GuiComponent::onHide()
|
||||
{
|
||||
for(unsigned int i = 0; i < getChildCount(); i++)
|
||||
getChild(i)->onHide();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -77,6 +77,9 @@ public:
|
|||
|
||||
virtual void onFocusGained() {};
|
||||
virtual void onFocusLost() {};
|
||||
|
||||
virtual void onShow();
|
||||
virtual void onHide();
|
||||
|
||||
// Default implementation just handles <pos> and <size> tags as normalized float pairs.
|
||||
// You probably want to keep this behavior for any derived classes as well as add your own.
|
||||
|
|
|
@ -82,7 +82,15 @@ std::map< std::string, ElementMapType > ThemeData::sElementMap = boost::assign::
|
|||
("textColor", COLOR)
|
||||
("iconColor", COLOR)
|
||||
("fontPath", PATH)
|
||||
("fontSize", FLOAT)));
|
||||
("fontSize", FLOAT)))
|
||||
("video", makeMap(boost::assign::map_list_of
|
||||
("pos", NORMALIZED_PAIR)
|
||||
("size", NORMALIZED_PAIR)
|
||||
("origin", NORMALIZED_PAIR)
|
||||
("default", PATH)
|
||||
("delay", FLOAT)
|
||||
("showSnapshotNoVideo", BOOLEAN)
|
||||
("showSnapshotDelay", BOOLEAN)));
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ namespace ThemeFlags
|
|||
TEXT = 512,
|
||||
FORCE_UPPERCASE = 1024,
|
||||
LINE_SPACING = 2048,
|
||||
DELAY = 4096,
|
||||
|
||||
ALL = 0xFFFFFFFF
|
||||
};
|
||||
|
|
|
@ -90,6 +90,11 @@ bool Window::init(unsigned int width, unsigned int height)
|
|||
|
||||
void Window::deinit()
|
||||
{
|
||||
// Hide all GUI elements on uninitialisation - this disable
|
||||
for(auto i = mGuiStack.begin(); i != mGuiStack.end(); i++)
|
||||
{
|
||||
(*i)->onHide();
|
||||
}
|
||||
InputManager::getInstance()->deinit();
|
||||
ResourceManager::getInstance()->unloadAll();
|
||||
Renderer::deinit();
|
||||
|
|
518
es-core/src/components/VideoComponent.cpp
Normal file
518
es-core/src/components/VideoComponent.cpp
Normal file
|
@ -0,0 +1,518 @@
|
|||
#include "components/VideoComponent.h"
|
||||
#include "Renderer.h"
|
||||
#include "ThemeData.h"
|
||||
#include "Util.h"
|
||||
#ifdef WIN32
|
||||
#include <codecvt>
|
||||
#endif
|
||||
|
||||
#define FADE_TIME_MS 200
|
||||
|
||||
libvlc_instance_t* VideoComponent::mVLC = NULL;
|
||||
|
||||
// VLC prepares to render a video frame.
|
||||
static void *lock(void *data, void **p_pixels) {
|
||||
struct VideoContext *c = (struct VideoContext *)data;
|
||||
SDL_LockMutex(c->mutex);
|
||||
SDL_LockSurface(c->surface);
|
||||
*p_pixels = c->surface->pixels;
|
||||
return NULL; // Picture identifier, not needed here.
|
||||
}
|
||||
|
||||
// VLC just rendered a video frame.
|
||||
static void unlock(void *data, void *id, void *const *p_pixels) {
|
||||
struct VideoContext *c = (struct VideoContext *)data;
|
||||
SDL_UnlockSurface(c->surface);
|
||||
SDL_UnlockMutex(c->mutex);
|
||||
}
|
||||
|
||||
// VLC wants to display a video frame.
|
||||
static void display(void *data, void *id) {
|
||||
//Data to be displayed
|
||||
}
|
||||
|
||||
VideoComponent::VideoComponent(Window* window) :
|
||||
GuiComponent(window),
|
||||
mStaticImage(window),
|
||||
mMediaPlayer(nullptr),
|
||||
mVideoHeight(0),
|
||||
mVideoWidth(0),
|
||||
mStartDelayed(false),
|
||||
mIsPlaying(false),
|
||||
mShowing(false)
|
||||
{
|
||||
memset(&mContext, 0, sizeof(mContext));
|
||||
|
||||
// Setup the default configuration
|
||||
mConfig.showSnapshotDelay = false;
|
||||
mConfig.showSnapshotNoVideo = false;
|
||||
mConfig.startDelay = 0;
|
||||
|
||||
// Get an empty texture for rendering the video
|
||||
mTexture = TextureResource::get("");
|
||||
|
||||
// Make sure VLC has been initialised
|
||||
setupVLC();
|
||||
}
|
||||
|
||||
VideoComponent::~VideoComponent()
|
||||
{
|
||||
// Stop any currently running video
|
||||
stopVideo();
|
||||
}
|
||||
|
||||
void VideoComponent::setOrigin(float originX, float originY)
|
||||
{
|
||||
mOrigin << originX, originY;
|
||||
|
||||
// Update the embeded static image
|
||||
mStaticImage.setOrigin(originX, originY);
|
||||
}
|
||||
|
||||
Eigen::Vector2f VideoComponent::getCenter() const
|
||||
{
|
||||
return Eigen::Vector2f(mPosition.x() - (getSize().x() * mOrigin.x()) + getSize().x() / 2,
|
||||
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2);
|
||||
}
|
||||
|
||||
void VideoComponent::onSizeChanged()
|
||||
{
|
||||
// Update the embeded static image
|
||||
mStaticImage.onSizeChanged();
|
||||
}
|
||||
|
||||
bool VideoComponent::setVideo(std::string path)
|
||||
{
|
||||
// Convert the path into a format VLC can understand
|
||||
boost::filesystem::path fullPath = getCanonicalPath(path);
|
||||
fullPath.make_preferred().native();
|
||||
|
||||
// Check that it's changed
|
||||
if (fullPath == mVideoPath)
|
||||
return !path.empty();
|
||||
|
||||
// Store the path
|
||||
mVideoPath = fullPath;
|
||||
|
||||
// If the file exists then set the new video
|
||||
if (!fullPath.empty() && ResourceManager::getInstance()->fileExists(fullPath.generic_string()))
|
||||
{
|
||||
// Return true to show that we are going to attempt to play a video
|
||||
return true;
|
||||
}
|
||||
// Return false to show that no video will be displayed
|
||||
return false;
|
||||
}
|
||||
|
||||
void VideoComponent::setImage(std::string path)
|
||||
{
|
||||
// Check that the image has changed
|
||||
if (path == mStaticImagePath)
|
||||
return;
|
||||
|
||||
mStaticImage.setImage(path);
|
||||
// Make the image stretch to fill the video region
|
||||
mStaticImage.setSize(getSize());
|
||||
mFadeIn = 0.0f;
|
||||
mStaticImagePath = path;
|
||||
}
|
||||
|
||||
void VideoComponent::setDefaultVideo()
|
||||
{
|
||||
setVideo(mConfig.defaultVideoPath);
|
||||
}
|
||||
|
||||
void VideoComponent::setOpacity(unsigned char opacity)
|
||||
{
|
||||
mOpacity = opacity;
|
||||
// Update the embeded static image
|
||||
mStaticImage.setOpacity(opacity);
|
||||
}
|
||||
|
||||
void VideoComponent::render(const Eigen::Affine3f& parentTrans)
|
||||
{
|
||||
float x, y;
|
||||
|
||||
Eigen::Affine3f trans = parentTrans * getTransform();
|
||||
GuiComponent::renderChildren(trans);
|
||||
|
||||
Renderer::setMatrix(trans);
|
||||
|
||||
// Handle the case where the video is delayed
|
||||
handleStartDelay();
|
||||
|
||||
// Handle looping of the video
|
||||
handleLooping();
|
||||
|
||||
if (mIsPlaying && mContext.valid)
|
||||
{
|
||||
float tex_offs_x = 0.0f;
|
||||
float tex_offs_y = 0.0f;
|
||||
float x2;
|
||||
float y2;
|
||||
|
||||
x = -(float)mSize.x() * mOrigin.x();
|
||||
y = -(float)mSize.y() * mOrigin.y();
|
||||
x2 = x+mSize.x();
|
||||
y2 = y+mSize.y();
|
||||
|
||||
// Define a structure to contain the data for each vertex
|
||||
struct Vertex
|
||||
{
|
||||
Eigen::Vector2f pos;
|
||||
Eigen::Vector2f tex;
|
||||
Eigen::Vector4f colour;
|
||||
} vertices[6];
|
||||
|
||||
// We need two triangles to cover the rectangular area
|
||||
vertices[0].pos[0] = x; vertices[0].pos[1] = y;
|
||||
vertices[1].pos[0] = x; vertices[1].pos[1] = y2;
|
||||
vertices[2].pos[0] = x2; vertices[2].pos[1] = y;
|
||||
|
||||
vertices[3].pos[0] = x2; vertices[3].pos[1] = y;
|
||||
vertices[4].pos[0] = x; vertices[4].pos[1] = y2;
|
||||
vertices[5].pos[0] = x2; vertices[5].pos[1] = y2;
|
||||
|
||||
// Texture coordinates
|
||||
vertices[0].tex[0] = -tex_offs_x; vertices[0].tex[1] = -tex_offs_y;
|
||||
vertices[1].tex[0] = -tex_offs_x; vertices[1].tex[1] = 1.0f + tex_offs_y;
|
||||
vertices[2].tex[0] = 1.0f + tex_offs_x; vertices[2].tex[1] = -tex_offs_y;
|
||||
|
||||
vertices[3].tex[0] = 1.0f + tex_offs_x; vertices[3].tex[1] = -tex_offs_y;
|
||||
vertices[4].tex[0] = -tex_offs_x; vertices[4].tex[1] = 1.0f + tex_offs_y;
|
||||
vertices[5].tex[0] = 1.0f + tex_offs_x; vertices[5].tex[1] = 1.0f + tex_offs_y;
|
||||
|
||||
// Colours - use this to fade the video in and out
|
||||
for (int i = 0; i < (4 * 6); ++i) {
|
||||
if ((i%4) < 3)
|
||||
vertices[i / 4].colour[i % 4] = mFadeIn;
|
||||
else
|
||||
vertices[i / 4].colour[i % 4] = 1.0f;
|
||||
}
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
// Build a texture for the video frame
|
||||
mTexture->initFromPixels((unsigned char*)mContext.surface->pixels, mContext.surface->w, mContext.surface->h);
|
||||
mTexture->bind();
|
||||
|
||||
// Render it
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glColorPointer(4, GL_FLOAT, sizeof(Vertex), &vertices[0].colour);
|
||||
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &vertices[0].pos);
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &vertices[0].tex);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the case where the video is not currently being displayed. Work out
|
||||
// if we need to display a static image
|
||||
if ((mConfig.showSnapshotNoVideo && mVideoPath.empty()) || (mStartDelayed && mConfig.showSnapshotDelay))
|
||||
{
|
||||
// Display the static image instead
|
||||
mStaticImage.setOpacity((unsigned char)(mFadeIn * 255.0f));
|
||||
mStaticImage.render(parentTrans);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
|
||||
{
|
||||
using namespace ThemeFlags;
|
||||
|
||||
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "video");
|
||||
if(!elem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Eigen::Vector2f scale = getParent() ? getParent()->getSize() : Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
|
||||
|
||||
if ((properties & POSITION) && elem->has("pos"))
|
||||
{
|
||||
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
|
||||
setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
|
||||
}
|
||||
|
||||
if ((properties & ThemeFlags::SIZE) && elem->has("size"))
|
||||
{
|
||||
setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
|
||||
}
|
||||
|
||||
// position + size also implies origin
|
||||
if (((properties & ORIGIN) || ((properties & POSITION) && (properties & ThemeFlags::SIZE))) && elem->has("origin"))
|
||||
setOrigin(elem->get<Eigen::Vector2f>("origin"));
|
||||
|
||||
if(elem->has("default"))
|
||||
mConfig.defaultVideoPath = elem->get<std::string>("default");
|
||||
|
||||
if((properties & ThemeFlags::DELAY) && elem->has("delay"))
|
||||
mConfig.startDelay = (unsigned)(elem->get<float>("delay") * 1000.0f);
|
||||
|
||||
if (elem->has("showSnapshotNoVideo"))
|
||||
mConfig.showSnapshotNoVideo = elem->get<bool>("showSnapshotNoVideo");
|
||||
|
||||
if (elem->has("showSnapshotDelay"))
|
||||
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay");
|
||||
|
||||
// Update the embeded static image
|
||||
mStaticImage.setPosition(getPosition());
|
||||
mStaticImage.setMaxSize(getSize());
|
||||
mStaticImage.setSize(getSize());
|
||||
}
|
||||
|
||||
std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
|
||||
{
|
||||
std::vector<HelpPrompt> ret;
|
||||
ret.push_back(HelpPrompt("a", "select"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VideoComponent::setupContext()
|
||||
{
|
||||
if (!mContext.valid)
|
||||
{
|
||||
// Create an RGBA surface to render the video into
|
||||
mContext.surface = SDL_CreateRGBSurface(SDL_SWSURFACE, (int)mVideoWidth, (int)mVideoHeight, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
|
||||
mContext.mutex = SDL_CreateMutex();
|
||||
mContext.valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::freeContext()
|
||||
{
|
||||
if (mContext.valid)
|
||||
{
|
||||
SDL_FreeSurface(mContext.surface);
|
||||
SDL_DestroyMutex(mContext.mutex);
|
||||
mContext.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::setupVLC()
|
||||
{
|
||||
// If VLC hasn't been initialised yet then do it now
|
||||
if (!mVLC)
|
||||
{
|
||||
const char* args[] = { "--quiet" };
|
||||
mVLC = libvlc_new(sizeof(args) / sizeof(args[0]), args);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::handleStartDelay()
|
||||
{
|
||||
// Only play if any delay has timed out
|
||||
if (mStartDelayed)
|
||||
{
|
||||
if (mStartTime > SDL_GetTicks())
|
||||
{
|
||||
// Timeout not yet completed
|
||||
return;
|
||||
}
|
||||
// Completed
|
||||
mStartDelayed = false;
|
||||
// Clear the playing flag so startVideo works
|
||||
mIsPlaying = false;
|
||||
startVideo();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::handleLooping()
|
||||
{
|
||||
if (mIsPlaying && mMediaPlayer)
|
||||
{
|
||||
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
|
||||
if (state == libvlc_Ended)
|
||||
{
|
||||
//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
|
||||
libvlc_media_player_set_media(mMediaPlayer, mMedia);
|
||||
libvlc_media_player_play(mMediaPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::startVideo()
|
||||
{
|
||||
if (!mIsPlaying) {
|
||||
mVideoWidth = 0;
|
||||
mVideoHeight = 0;
|
||||
|
||||
#ifdef WIN32
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> wton;
|
||||
std::string path = wton.to_bytes(mVideoPath.c_str());
|
||||
#else
|
||||
std::string path(mVideoPath.c_str());
|
||||
#endif
|
||||
// Make sure we have a video path
|
||||
if (mVLC && (path.size() > 0))
|
||||
{
|
||||
// Set the video that we are going to be playing so we don't attempt to restart it
|
||||
mPlayingVideoPath = mVideoPath;
|
||||
|
||||
// Open the media
|
||||
mMedia = libvlc_media_new_path(mVLC, path.c_str());
|
||||
if (mMedia)
|
||||
{
|
||||
unsigned track_count;
|
||||
// Get the media metadata so we can find the aspect ratio
|
||||
libvlc_media_parse(mMedia);
|
||||
libvlc_media_track_t** tracks;
|
||||
track_count = libvlc_media_tracks_get(mMedia, &tracks);
|
||||
for (unsigned track = 0; track < track_count; ++track)
|
||||
{
|
||||
if (tracks[track]->i_type == libvlc_track_video)
|
||||
{
|
||||
mVideoWidth = tracks[track]->video->i_width;
|
||||
mVideoHeight = tracks[track]->video->i_height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
libvlc_media_tracks_release(tracks, track_count);
|
||||
|
||||
// Make sure we found a valid video track
|
||||
if ((mVideoWidth > 0) && (mVideoHeight > 0))
|
||||
{
|
||||
setupContext();
|
||||
|
||||
// Setup the media player
|
||||
mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
|
||||
libvlc_media_player_play(mMediaPlayer);
|
||||
libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display, (void*)&mContext);
|
||||
libvlc_video_set_format(mMediaPlayer, "RGBA", (int)mVideoWidth, (int)mVideoHeight, (int)mVideoWidth * 4);
|
||||
|
||||
// Update the playing state
|
||||
mIsPlaying = true;
|
||||
mFadeIn = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::startVideoWithDelay()
|
||||
{
|
||||
// If not playing then either start the video or initiate the delay
|
||||
if (!mIsPlaying)
|
||||
{
|
||||
// Set the video that we are going to be playing so we don't attempt to restart it
|
||||
mPlayingVideoPath = mVideoPath;
|
||||
|
||||
if (mConfig.startDelay == 0)
|
||||
{
|
||||
// No delay. Just start the video
|
||||
mStartDelayed = false;
|
||||
startVideo();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Configure the start delay
|
||||
mStartDelayed = true;
|
||||
mFadeIn = 0.0f;
|
||||
mStartTime = SDL_GetTicks() + mConfig.startDelay;
|
||||
}
|
||||
mIsPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::stopVideo()
|
||||
{
|
||||
mIsPlaying = false;
|
||||
mStartDelayed = false;
|
||||
// Release the media player so it stops calling back to us
|
||||
if (mMediaPlayer)
|
||||
{
|
||||
libvlc_media_player_stop(mMediaPlayer);
|
||||
libvlc_media_player_release(mMediaPlayer);
|
||||
libvlc_media_release(mMedia);
|
||||
mMediaPlayer = NULL;
|
||||
freeContext();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::update(int deltaTime)
|
||||
{
|
||||
manageState();
|
||||
|
||||
// If the video start is delayed and there is less than the fade time then set the image fade
|
||||
// accordingly
|
||||
if (mStartDelayed)
|
||||
{
|
||||
Uint32 ticks = SDL_GetTicks();
|
||||
if (mStartTime > ticks)
|
||||
{
|
||||
Uint32 diff = mStartTime - ticks;
|
||||
if (diff < FADE_TIME_MS)
|
||||
{
|
||||
mFadeIn = (float)diff / (float)FADE_TIME_MS;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the fade in is less than 1 then increment it
|
||||
if (mFadeIn < 1.0f)
|
||||
{
|
||||
mFadeIn += deltaTime / (float)FADE_TIME_MS;
|
||||
if (mFadeIn > 1.0f)
|
||||
mFadeIn = 1.0f;
|
||||
}
|
||||
GuiComponent::update(deltaTime);
|
||||
}
|
||||
|
||||
void VideoComponent::manageState()
|
||||
{
|
||||
// We will only show if the component is on display
|
||||
bool show = mShowing;
|
||||
|
||||
// See if we're already playing
|
||||
if (mIsPlaying)
|
||||
{
|
||||
// If we are not on display then stop the video from playing
|
||||
if (!show)
|
||||
{
|
||||
stopVideo();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mVideoPath != mPlayingVideoPath)
|
||||
{
|
||||
// Path changed. Stop the video. We will start it again below because
|
||||
// mIsPlaying will be modified by stopVideo to be false
|
||||
stopVideo();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Need to recheck variable rather than 'else' because it may be modified above
|
||||
if (!mIsPlaying)
|
||||
{
|
||||
// If we are on display then see if we should start the video
|
||||
if (show && !mVideoPath.empty())
|
||||
{
|
||||
startVideoWithDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoComponent::onShow()
|
||||
{
|
||||
mShowing = true;
|
||||
manageState();
|
||||
}
|
||||
|
||||
void VideoComponent::onHide()
|
||||
{
|
||||
mShowing = false;
|
||||
manageState();
|
||||
}
|
||||
|
||||
|
112
es-core/src/components/VideoComponent.h
Normal file
112
es-core/src/components/VideoComponent.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
#ifndef _VIDEOCOMPONENT_H_
|
||||
#define _VIDEOCOMPONENT_H_
|
||||
|
||||
#include "platform.h"
|
||||
#include GLHEADER
|
||||
|
||||
#include "GuiComponent.h"
|
||||
#include "ImageComponent.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "resources/TextureResource.h"
|
||||
#include <vlc/vlc.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL_mutex.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
struct VideoContext {
|
||||
SDL_Surface* surface;
|
||||
SDL_mutex* mutex;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
class VideoComponent : public GuiComponent
|
||||
{
|
||||
// Structure that groups together the configuration of the video component
|
||||
struct Configuration
|
||||
{
|
||||
unsigned startDelay;
|
||||
bool showSnapshotNoVideo;
|
||||
bool showSnapshotDelay;
|
||||
std::string defaultVideoPath;
|
||||
};
|
||||
|
||||
public:
|
||||
static void setupVLC();
|
||||
|
||||
VideoComponent(Window* window);
|
||||
virtual ~VideoComponent();
|
||||
|
||||
// Loads the video at the given filepath
|
||||
bool setVideo(std::string path);
|
||||
// Loads a static image that is displayed if the video cannot be played
|
||||
void setImage(std::string path);
|
||||
|
||||
// Configures the component to show the default video
|
||||
void setDefaultVideo();
|
||||
|
||||
virtual void onShow() override;
|
||||
virtual void onHide() override;
|
||||
|
||||
//Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
|
||||
void setOrigin(float originX, float originY);
|
||||
inline void setOrigin(Eigen::Vector2f origin) { setOrigin(origin.x(), origin.y()); }
|
||||
|
||||
void onSizeChanged() override;
|
||||
void setOpacity(unsigned char opacity) override;
|
||||
|
||||
void render(const Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
|
||||
|
||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
// Returns the center point of the video (takes origin into account).
|
||||
Eigen::Vector2f getCenter() const;
|
||||
|
||||
virtual void update(int deltaTime);
|
||||
|
||||
private:
|
||||
// Start the video Immediately
|
||||
void startVideo();
|
||||
// Start the video after any configured delay
|
||||
void startVideoWithDelay();
|
||||
// Stop the video
|
||||
void stopVideo();
|
||||
|
||||
void setupContext();
|
||||
void freeContext();
|
||||
|
||||
// Handle any delay to the start of playing the video clip. Must be called periodically
|
||||
void handleStartDelay();
|
||||
|
||||
// Handle looping the video. Must be called periodically
|
||||
void handleLooping();
|
||||
|
||||
// Manage the playing state of the component
|
||||
void manageState();
|
||||
|
||||
private:
|
||||
static libvlc_instance_t* mVLC;
|
||||
libvlc_media_t* mMedia;
|
||||
libvlc_media_player_t* mMediaPlayer;
|
||||
VideoContext mContext;
|
||||
unsigned mVideoWidth;
|
||||
unsigned mVideoHeight;
|
||||
Eigen::Vector2f mOrigin;
|
||||
std::shared_ptr<TextureResource> mTexture;
|
||||
float mFadeIn;
|
||||
std::string mStaticImagePath;
|
||||
ImageComponent mStaticImage;
|
||||
|
||||
boost::filesystem::path mVideoPath;
|
||||
boost::filesystem::path mPlayingVideoPath;
|
||||
bool mStartDelayed;
|
||||
unsigned mStartTime;
|
||||
bool mIsPlaying;
|
||||
bool mShowing;
|
||||
|
||||
Configuration mConfig;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue