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(Boost REQUIRED COMPONENTS system filesystem date_time locale)
|
||||||
find_package(Eigen3 REQUIRED)
|
find_package(Eigen3 REQUIRED)
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
find_package(VLC REQUIRED)
|
||||||
|
|
||||||
#add ALSA for Linux
|
#add ALSA for Linux
|
||||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||||
|
@ -99,6 +100,7 @@ set(COMMON_INCLUDE_DIRS
|
||||||
${Boost_INCLUDE_DIRS}
|
${Boost_INCLUDE_DIRS}
|
||||||
${EIGEN3_INCLUDE_DIR}
|
${EIGEN3_INCLUDE_DIR}
|
||||||
${CURL_INCLUDE_DIR}
|
${CURL_INCLUDE_DIR}
|
||||||
|
${VLC_INCLUDE_DIR}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/external
|
${CMAKE_CURRENT_SOURCE_DIR}/external
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/es-core/src
|
${CMAKE_CURRENT_SOURCE_DIR}/es-core/src
|
||||||
)
|
)
|
||||||
|
@ -148,6 +150,7 @@ set(COMMON_LIBRARIES
|
||||||
${FreeImage_LIBRARIES}
|
${FreeImage_LIBRARIES}
|
||||||
${SDL2_LIBRARY}
|
${SDL2_LIBRARY}
|
||||||
${CURL_LIBRARIES}
|
${CURL_LIBRARIES}
|
||||||
|
${VLC_LIBRARIES}
|
||||||
pugixml
|
pugixml
|
||||||
nanosvg
|
nanosvg
|
||||||
)
|
)
|
||||||
|
|
|
@ -41,7 +41,10 @@ EmulationStation has a few dependencies. For building, you'll need CMake, SDL2,
|
||||||
**On Debian/Ubuntu:**
|
**On Debian/Ubuntu:**
|
||||||
All of this be easily installed with apt-get:
|
All of this be easily installed with apt-get:
|
||||||
```bash
|
```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:
|
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/IGameListView.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.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/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/SystemView.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.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/IGameListView.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.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/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/SystemView.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,6 +81,32 @@ const std::string& FileData::getThumbnailPath() const
|
||||||
return metadata.get("image");
|
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
|
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,6 +45,8 @@ public:
|
||||||
inline SystemData* getSystem() const { return mSystem; }
|
inline SystemData* getSystem() const { return mSystem; }
|
||||||
|
|
||||||
virtual const std::string& getThumbnailPath() const;
|
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;
|
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,10 @@ MetaDataDecl gameDecls[] = {
|
||||||
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
|
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
|
||||||
{"name", MD_STRING, "", false, "name", "enter game name"},
|
{"name", MD_STRING, "", false, "name", "enter game name"},
|
||||||
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
|
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
|
||||||
{"image", MD_IMAGE_PATH, "", false, "image", "enter path to image"},
|
{"image", MD_PATH, "", false, "image", "enter path to image"},
|
||||||
{"thumbnail", MD_IMAGE_PATH, "", false, "thumbnail", "enter path to thumbnail"},
|
{"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"},
|
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
|
||||||
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
||||||
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
||||||
|
@ -25,8 +27,8 @@ const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls)
|
||||||
MetaDataDecl folderDecls[] = {
|
MetaDataDecl folderDecls[] = {
|
||||||
{"name", MD_STRING, "", false},
|
{"name", MD_STRING, "", false},
|
||||||
{"desc", MD_MULTILINE_STRING, "", false},
|
{"desc", MD_MULTILINE_STRING, "", false},
|
||||||
{"image", MD_IMAGE_PATH, "", false},
|
{"image", MD_PATH, "", false},
|
||||||
{"thumbnail", MD_IMAGE_PATH, "", false},
|
{"thumbnail", MD_PATH, "", false},
|
||||||
};
|
};
|
||||||
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));
|
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
|
// if it's a path, resolve relative paths
|
||||||
std::string value = md.text().get();
|
std::string value = md.text().get();
|
||||||
if(iter->type == MD_IMAGE_PATH)
|
if (iter->type == MD_PATH)
|
||||||
|
{
|
||||||
value = resolvePath(value, relativeTo, true).generic_string();
|
value = resolvePath(value, relativeTo, true).generic_string();
|
||||||
|
}
|
||||||
mdl.set(iter->key, value);
|
mdl.set(iter->key, value);
|
||||||
}else{
|
}else{
|
||||||
mdl.set(iter->key, iter->defaultValue);
|
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
|
// try and make paths relative if we can
|
||||||
std::string value = mapIter->second;
|
std::string value = mapIter->second;
|
||||||
if(mddIter->type == MD_IMAGE_PATH)
|
if (mddIter->type == MD_PATH)
|
||||||
value = makeRelativePath(value, relativeTo, true).generic_string();
|
value = makeRelativePath(value, relativeTo, true).generic_string();
|
||||||
|
|
||||||
parent.append_child(mapIter->first.c_str()).text().set(value.c_str());
|
parent.append_child(mapIter->first.c_str()).text().set(value.c_str());
|
||||||
|
|
|
@ -16,7 +16,7 @@ enum MetaDataType
|
||||||
|
|
||||||
//specialized types
|
//specialized types
|
||||||
MD_MULTILINE_STRING,
|
MD_MULTILINE_STRING,
|
||||||
MD_IMAGE_PATH,
|
MD_PATH,
|
||||||
MD_RATING,
|
MD_RATING,
|
||||||
MD_DATE,
|
MD_DATE,
|
||||||
MD_TIME //used for lastplayed
|
MD_TIME //used for lastplayed
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
|
|
||||||
#include "views/gamelist/BasicGameListView.h"
|
#include "views/gamelist/BasicGameListView.h"
|
||||||
#include "views/gamelist/DetailedGameListView.h"
|
#include "views/gamelist/DetailedGameListView.h"
|
||||||
|
#include "views/gamelist/VideoGameListView.h"
|
||||||
#include "views/gamelist/GridGameListView.h"
|
#include "views/gamelist/GridGameListView.h"
|
||||||
#include "guis/GuiMenu.h"
|
#include "guis/GuiMenu.h"
|
||||||
#include "guis/GuiMsgBox.h"
|
#include "guis/GuiMsgBox.h"
|
||||||
#include "animations/LaunchAnimation.h"
|
#include "animations/LaunchAnimation.h"
|
||||||
#include "animations/MoveCameraAnimation.h"
|
#include "animations/MoveCameraAnimation.h"
|
||||||
#include "animations/LambdaAnimation.h"
|
#include "animations/LambdaAnimation.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
ViewController* ViewController::sInstance = NULL;
|
ViewController* ViewController::sInstance = NULL;
|
||||||
|
|
||||||
|
@ -55,6 +57,12 @@ int ViewController::getSystemId(SystemData* system)
|
||||||
|
|
||||||
void ViewController::goToSystemView(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.viewing = SYSTEM_SELECT;
|
||||||
mState.system = system;
|
mState.system = system;
|
||||||
|
|
||||||
|
@ -99,7 +107,15 @@ void ViewController::goToGameList(SystemData* system)
|
||||||
mState.viewing = GAME_LIST;
|
mState.viewing = GAME_LIST;
|
||||||
mState.system = system;
|
mState.system = system;
|
||||||
|
|
||||||
|
if (mCurrentView)
|
||||||
|
{
|
||||||
|
mCurrentView->onHide();
|
||||||
|
}
|
||||||
mCurrentView = getGameListView(system);
|
mCurrentView = getGameListView(system);
|
||||||
|
if (mCurrentView)
|
||||||
|
{
|
||||||
|
mCurrentView->onShow();
|
||||||
|
}
|
||||||
playViewTransition();
|
playViewTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +179,10 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the current view
|
||||||
|
if (mCurrentView)
|
||||||
|
mCurrentView->onHide();
|
||||||
|
|
||||||
Eigen::Affine3f origCamera = mCamera;
|
Eigen::Affine3f origCamera = mCamera;
|
||||||
origCamera.translation() = -mCurrentView->getPosition();
|
origCamera.translation() = -mCurrentView->getPosition();
|
||||||
|
|
||||||
|
@ -210,17 +230,26 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
|
||||||
|
|
||||||
//decide type
|
//decide type
|
||||||
bool detailed = false;
|
bool detailed = false;
|
||||||
|
bool video = false;
|
||||||
std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
|
std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
|
||||||
for(auto it = files.begin(); it != files.end(); it++)
|
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;
|
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()));
|
view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
|
||||||
else
|
else
|
||||||
view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
|
view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
|
||||||
|
@ -347,6 +376,10 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Redisplay the current view
|
||||||
|
if (mCurrentView)
|
||||||
|
mCurrentView->onShow();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewController::reloadAll()
|
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/SwitchComponent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h
|
||||||
|
|
||||||
# Guis
|
# Guis
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h
|
${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/SwitchComponent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp
|
||||||
|
|
||||||
# Guis
|
# Guis
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp
|
||||||
|
|
|
@ -345,3 +345,17 @@ bool GuiComponent::isProcessing() const
|
||||||
{
|
{
|
||||||
return mIsProcessing;
|
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 onFocusGained() {};
|
||||||
virtual void onFocusLost() {};
|
virtual void onFocusLost() {};
|
||||||
|
|
||||||
|
virtual void onShow();
|
||||||
|
virtual void onHide();
|
||||||
|
|
||||||
// Default implementation just handles <pos> and <size> tags as normalized float pairs.
|
// 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.
|
// 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)
|
("textColor", COLOR)
|
||||||
("iconColor", COLOR)
|
("iconColor", COLOR)
|
||||||
("fontPath", PATH)
|
("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;
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ namespace ThemeFlags
|
||||||
TEXT = 512,
|
TEXT = 512,
|
||||||
FORCE_UPPERCASE = 1024,
|
FORCE_UPPERCASE = 1024,
|
||||||
LINE_SPACING = 2048,
|
LINE_SPACING = 2048,
|
||||||
|
DELAY = 4096,
|
||||||
|
|
||||||
ALL = 0xFFFFFFFF
|
ALL = 0xFFFFFFFF
|
||||||
};
|
};
|
||||||
|
|
|
@ -90,6 +90,11 @@ bool Window::init(unsigned int width, unsigned int height)
|
||||||
|
|
||||||
void Window::deinit()
|
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();
|
InputManager::getInstance()->deinit();
|
||||||
ResourceManager::getInstance()->unloadAll();
|
ResourceManager::getInstance()->unloadAll();
|
||||||
Renderer::deinit();
|
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