Un inicio
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,10 @@
|
|||||||
*.dll
|
*.dll
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
|
build/
|
||||||
|
|
||||||
|
.cache/
|
||||||
|
|
||||||
# Qt-es
|
# Qt-es
|
||||||
object_script.*.Release
|
object_script.*.Release
|
||||||
object_script.*.Debug
|
object_script.*.Debug
|
||||||
|
|||||||
44
CMakeLists.txt
Normal file
44
CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(youtube-playlist LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Gui Qml Quick Multimedia Svg QuickEffects)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
qt_standard_project_setup()
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
|
||||||
|
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/lib/*.hpp")
|
||||||
|
|
||||||
|
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/lib")
|
||||||
|
|
||||||
|
qt_add_executable(appyoutube-playlist
|
||||||
|
${SOURCES}
|
||||||
|
${HEADERS}
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_qml_module(appyoutube-playlist
|
||||||
|
URI App
|
||||||
|
VERSION 1.0
|
||||||
|
QML_FILES
|
||||||
|
ui/main.qml
|
||||||
|
ui/FadingFeedback.qml
|
||||||
|
RESOURCES
|
||||||
|
ui/icons/pause.svg
|
||||||
|
ui/icons/play.svg
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(appyoutube-playlist
|
||||||
|
PRIVATE
|
||||||
|
Qt6::Gui
|
||||||
|
Qt6::Qml
|
||||||
|
Qt6::Quick
|
||||||
|
Qt6::Multimedia
|
||||||
|
Qt6::Svg
|
||||||
|
Qt6::QuickEffects
|
||||||
|
)
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
# youtube-playlist
|
# youtube-playlist
|
||||||
|
|
||||||
|
|
||||||
|
Pues una app sencilla con Qt para reproducir canciones desde youtube sin los cortes de mierda que tiene, para disfrutar de los albums como toca
|
||||||
|
|||||||
127
lib/logger.hpp
Normal file
127
lib/logger.hpp
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <qcoreapplication.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
enum class LogLevel {
|
||||||
|
DEBUG_LEVEL_TRACE = 0,
|
||||||
|
DEBUG_LEVEL_INFO = 1,
|
||||||
|
DEBUG_LEVEL_WARN = 2,
|
||||||
|
DEBUG_LEVEL_ERROR = 3,
|
||||||
|
DEBUG_LEVEL_OFF = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
inline LogLevel getLogLevelFromEnv() {
|
||||||
|
const char *env = std::getenv("DEBUG_LEVEL");
|
||||||
|
if (!env) {
|
||||||
|
return LogLevel::DEBUG_LEVEL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string level(env);
|
||||||
|
std::transform(level.begin(), level.end(), level.begin(), ::tolower);
|
||||||
|
|
||||||
|
if (level == "trace" || level == "0")
|
||||||
|
return LogLevel::DEBUG_LEVEL_TRACE;
|
||||||
|
if (level == "info" || level == "1")
|
||||||
|
return LogLevel::DEBUG_LEVEL_INFO;
|
||||||
|
if (level == "warn" || level == "2")
|
||||||
|
return LogLevel::DEBUG_LEVEL_WARN;
|
||||||
|
if (level == "error" || level == "3")
|
||||||
|
return LogLevel::DEBUG_LEVEL_ERROR;
|
||||||
|
if (level == "off" || level == "4")
|
||||||
|
return LogLevel::DEBUG_LEVEL_OFF;
|
||||||
|
|
||||||
|
return LogLevel::DEBUG_LEVEL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Logger {
|
||||||
|
|
||||||
|
inline LogLevel getCurrentLevel() {
|
||||||
|
static const LogLevel currentLevel = getLogLevelFromEnv();
|
||||||
|
return currentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void info(std::string_view message) {
|
||||||
|
if (static_cast<int>(LogLevel::DEBUG_LEVEL_INFO) <
|
||||||
|
static_cast<int>(getCurrentLevel())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cout << "\033[32m[Info]\033[0m " << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void warn(std::string_view message) {
|
||||||
|
if (static_cast<int>(LogLevel::DEBUG_LEVEL_WARN) <
|
||||||
|
static_cast<int>(getCurrentLevel())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::clog << "\033[33m[Warn]\033[0m " << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void error(std::string_view message) {
|
||||||
|
if (static_cast<int>(LogLevel::DEBUG_LEVEL_ERROR) <
|
||||||
|
static_cast<int>(getCurrentLevel())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cerr << "\033[31m[Error] " << message << "\033[0m" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Logger
|
||||||
|
|
||||||
|
inline void MessageHandler(QtMsgType type, const QMessageLogContext &context,
|
||||||
|
const QString &msg) {
|
||||||
|
static const LogLevel currentLevel = getLogLevelFromEnv();
|
||||||
|
|
||||||
|
LogLevel msgLevel;
|
||||||
|
std::string prefix;
|
||||||
|
std::string color;
|
||||||
|
std::string reset = "\033[0m";
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
msgLevel = LogLevel::DEBUG_LEVEL_TRACE;
|
||||||
|
prefix = "[Trace]";
|
||||||
|
color = "\033[36m";
|
||||||
|
break;
|
||||||
|
case QtInfoMsg:
|
||||||
|
msgLevel = LogLevel::DEBUG_LEVEL_INFO;
|
||||||
|
prefix = "[Info]";
|
||||||
|
color = "\033[32m";
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
msgLevel = LogLevel::DEBUG_LEVEL_WARN;
|
||||||
|
prefix = "[Warn]";
|
||||||
|
color = "\033[33m";
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
msgLevel = LogLevel::DEBUG_LEVEL_ERROR;
|
||||||
|
prefix = "[Error]";
|
||||||
|
color = "\033[31m";
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
msgLevel = LogLevel::DEBUG_LEVEL_ERROR;
|
||||||
|
prefix = "[Fatal]";
|
||||||
|
color = "\033[35m";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static_cast<int>(msgLevel) < static_cast<int>(currentLevel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string context_str = " ";
|
||||||
|
if (context.file && context.line) {
|
||||||
|
context_str = std::format(" ({}:{}) ", context.file, context.line);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << color << prefix << reset << context_str << msg.toStdString() << std::endl;
|
||||||
|
|
||||||
|
if (type == QtFatalMsg) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
60
lib/playlist-controller.hpp
Normal file
60
lib/playlist-controller.hpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <format>
|
||||||
|
#include <qcoreapplication.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
class PlaylistController : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QUrl currentVideoSource READ currentVideoSource NOTIFY
|
||||||
|
currentVideoSourceChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PlaylistController(QObject *parent = nullptr) : QObject(parent) {}
|
||||||
|
|
||||||
|
QUrl currentVideoSource() const { return m_currentVideoSource; }
|
||||||
|
|
||||||
|
Q_INVOKABLE void loadNextVideo() {
|
||||||
|
if (m_queue.size() == 0) {
|
||||||
|
Logger::info("Nothing left to play");
|
||||||
|
m_playing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString video = m_queue.front();
|
||||||
|
Logger::info(std::format("Playing: {} in video {}", video.toStdString(), 1));
|
||||||
|
m_queue.erase(m_queue.begin());
|
||||||
|
|
||||||
|
QUrl newUrl = QUrl::fromLocalFile(video);
|
||||||
|
if (m_currentVideoSource != newUrl) {
|
||||||
|
m_currentVideoSource = newUrl;
|
||||||
|
emit currentVideoSourceChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tengo que rehacer esto en algun momento para que tome la url y la procese en otro thread
|
||||||
|
// Y ya que me pongo con ello usar el doble buffer y permitir cargar listas enteras
|
||||||
|
Q_INVOKABLE void addToQueue(const QString path) {
|
||||||
|
int n_elements = m_queue.size();
|
||||||
|
|
||||||
|
m_queue.push_back(path);
|
||||||
|
Logger::info(std::format("Adding: {}", path.toStdString()));
|
||||||
|
|
||||||
|
if (n_elements == 0 && !m_playing) {
|
||||||
|
m_playing = true;
|
||||||
|
loadNextVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void currentVideoSourceChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_playing = false;
|
||||||
|
std::vector<QString> m_queue = std::vector<QString>();
|
||||||
|
QUrl m_currentVideoSource = QString("");
|
||||||
|
};
|
||||||
35
src/main.cpp
Normal file
35
src/main.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "logger.hpp"
|
||||||
|
#include "playlist-controller.hpp"
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
qInstallMessageHandler(MessageHandler);
|
||||||
|
std::filesystem::path cwd = std::filesystem::current_path();
|
||||||
|
Logger::info(cwd.string());
|
||||||
|
|
||||||
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
|
PlaylistController playlist;
|
||||||
|
engine.rootContext()->setContextProperty("playlist", &playlist);
|
||||||
|
|
||||||
|
const QUrl url(QStringLiteral("qrc:/App/ui/main.qml"));
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
&engine, &QQmlApplicationEngine::objectCreationFailed, &app,
|
||||||
|
[]() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
engine.load(url);
|
||||||
|
|
||||||
|
Logger::info("Engine loaded");
|
||||||
|
|
||||||
|
playlist.addToQueue("../ui/media/video.mp4");
|
||||||
|
playlist.addToQueue("../ui/media/Puta.mp4");
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
87
ui/FadingFeedback.qml
Normal file
87
ui/FadingFeedback.qml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: 100
|
||||||
|
height: width
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
function trigger() {
|
||||||
|
hideTimer.stop();
|
||||||
|
root.state = "hidden";
|
||||||
|
root.state = "visible";
|
||||||
|
hideTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_opacity() {
|
||||||
|
return root.opacity
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
root.opacity = 0;
|
||||||
|
root.state = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
// IconImage {
|
||||||
|
// id: innerIcon
|
||||||
|
// anchors.fill: parent
|
||||||
|
// source: videoPlayer.playbackState === MediaPlayer.PlayingState ? "qrc:/App/ui/icons/play.svg" : "qrc:/App/ui/icons/pause.svg"
|
||||||
|
// color: "white"
|
||||||
|
// fillMode: Image.PreserveAspectFit
|
||||||
|
//
|
||||||
|
// layer.enabled: true
|
||||||
|
// layer.effect: MultiEffect {
|
||||||
|
// shadowEnabled: true
|
||||||
|
// shadowColor: "black"
|
||||||
|
// shadowBlur: 0.5
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "visible"
|
||||||
|
PropertyChanges {
|
||||||
|
root.opacity: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hidden"
|
||||||
|
PropertyChanges {
|
||||||
|
root.opacity: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition {
|
||||||
|
from: "hidden"
|
||||||
|
to: "visible"
|
||||||
|
NumberAnimation {
|
||||||
|
properties: "opacity"
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: "visible"
|
||||||
|
to: "hidden"
|
||||||
|
NumberAnimation {
|
||||||
|
properties: "opacity"
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.InOutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
interval: 800
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
root.state = "hidden";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
ui/icons/pause.svg
Normal file
1
ui/icons/pause.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.3.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M176 96C149.5 96 128 117.5 128 144L128 496C128 522.5 149.5 544 176 544L240 544C266.5 544 288 522.5 288 496L288 144C288 117.5 266.5 96 240 96L176 96zM400 96C373.5 96 352 117.5 352 144L352 496C352 522.5 373.5 544 400 544L464 544C490.5 544 512 522.5 512 496L512 144C512 117.5 490.5 96 464 96L400 96z"/></svg>
|
||||||
|
After Width: | Height: | Size: 527 B |
1
ui/icons/play.svg
Normal file
1
ui/icons/play.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.3.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M187.2 100.9C174.8 94.1 159.8 94.4 147.6 101.6C135.4 108.8 128 121.9 128 136L128 504C128 518.1 135.5 531.2 147.6 538.4C159.7 545.6 174.8 545.9 187.2 539.1L523.2 355.1C536 348.1 544 334.6 544 320C544 305.4 536 291.9 523.2 284.9L187.2 100.9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 470 B |
156
ui/main.qml
Normal file
156
ui/main.qml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls.impl
|
||||||
|
import QtMultimedia
|
||||||
|
import QtQuick.Effects
|
||||||
|
|
||||||
|
Window {
|
||||||
|
id: window
|
||||||
|
width: 800
|
||||||
|
height: 600
|
||||||
|
visible: true
|
||||||
|
title: "YouAlbumTube"
|
||||||
|
color: "#1e1e2e"
|
||||||
|
|
||||||
|
function togglePause() {
|
||||||
|
videoPlayer.playbackState == MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play();
|
||||||
|
|
||||||
|
seekFeedback.hide();
|
||||||
|
playFeedback.trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
property int times: 0
|
||||||
|
property int dir: 0
|
||||||
|
|
||||||
|
function seek(time) {
|
||||||
|
let opacity = seekFeedback.get_opacity();
|
||||||
|
|
||||||
|
if (opacity <= 0) {
|
||||||
|
window.times = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.times++;
|
||||||
|
|
||||||
|
if (time > 0) {
|
||||||
|
if (dir < 0) {
|
||||||
|
window.times = 1;
|
||||||
|
}
|
||||||
|
dir = 1;
|
||||||
|
|
||||||
|
videoPlayer.position = Math.min(videoPlayer.duration, videoPlayer.position + time);
|
||||||
|
innerText.text = "+" + (5 * window.times);
|
||||||
|
} else if (time <= 0) {
|
||||||
|
if (dir > 0) {
|
||||||
|
window.times = 1;
|
||||||
|
}
|
||||||
|
dir = -1;
|
||||||
|
|
||||||
|
videoPlayer.position = Math.max(0, videoPlayer.position + time);
|
||||||
|
innerText.text = "-" + (5 * window.times);
|
||||||
|
}
|
||||||
|
|
||||||
|
playFeedback.hide();
|
||||||
|
seekFeedback.trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Space"
|
||||||
|
onActivated: window.togglePause()
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Left"
|
||||||
|
onActivated: window.seek(-5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Right"
|
||||||
|
onActivated: window.seek(5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: window.togglePause()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "No content\nAdd it to playlist!"
|
||||||
|
font.pixelSize: 32
|
||||||
|
font.bold: true
|
||||||
|
color: "#cdd6f4"
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estudiar el doble buffer, parece ser no tan coñazo como podria esperar
|
||||||
|
// mañana lo intento implementar
|
||||||
|
Video {
|
||||||
|
id: videoPlayer
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: playlist.currentVideoSource
|
||||||
|
visible: true
|
||||||
|
loops: 0
|
||||||
|
playbackRate: 1
|
||||||
|
|
||||||
|
onSourceChanged: {
|
||||||
|
console.log("Now playing:", source);
|
||||||
|
if (source.toString() !== "") {
|
||||||
|
videoPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onStopped: {
|
||||||
|
playlist.loadNextVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
console.log("Cambio la visibilidad");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FadingFeedback {
|
||||||
|
id: playFeedback
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
id: innerIcon
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.fill: parent
|
||||||
|
source: videoPlayer.playbackState === MediaPlayer.PlayingState ? "qrc:/App/ui/icons/play.svg" : "qrc:/App/ui/icons/pause.svg"
|
||||||
|
color: "white"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowColor: "black"
|
||||||
|
shadowBlur: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FadingFeedback {
|
||||||
|
id: seekFeedback
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: innerText
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: ""
|
||||||
|
color: "white"
|
||||||
|
// fontSizeMode: Text.Fit
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|
||||||
|
font.pixelSize: 72
|
||||||
|
font.bold: true
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowColor: "black"
|
||||||
|
shadowBlur: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ui/media/Puta.mp4
Normal file
BIN
ui/media/Puta.mp4
Normal file
Binary file not shown.
BIN
ui/media/video.mp4
Normal file
BIN
ui/media/video.mp4
Normal file
Binary file not shown.
Reference in New Issue
Block a user