diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3bc204 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +build/ +cmake-build-*/ +.vs/ +.vscode/ +*.user +*.suo +*.VC.db +*.VC.VC.opendb +*.autosave + +# Build outputs +*.exe +*.dll +*.pdb +*.ilk +*.obj +*.o +*.a +*.lib + +# Runtime/config files generated during development +config/ +logs/ +*.broken.json + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2754048 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.20) + +project(QtDesktopPet VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_AUTOMOC OFF) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +qt_add_executable(QtDesktopPet + main.cpp + src/ui/PetWindow.h + src/ui/PetWindow.cpp +) + +target_compile_definitions(QtDesktopPet + PRIVATE + PET_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) + +target_link_libraries(QtDesktopPet + PRIVATE + Qt6::Widgets +) + +if (WIN32) + target_compile_definitions(QtDesktopPet PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) +endif() diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0a693ef --- /dev/null +++ b/main.cpp @@ -0,0 +1,16 @@ +#include + +#include "src/ui/PetWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QApplication::setApplicationName("QtDesktopPet"); + QApplication::setOrganizationName("QtDesktopPet"); + + PetWindow window; + window.show(); + + return app.exec(); +} + diff --git a/resources/characters/.gitkeep b/resources/characters/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/resources/characters/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/ui/PetWindow.cpp b/src/ui/PetWindow.cpp new file mode 100644 index 0000000..900ce97 --- /dev/null +++ b/src/ui/PetWindow.cpp @@ -0,0 +1,141 @@ +#include "PetWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +QString previewImagePath() +{ + return QStringLiteral(PET_SOURCE_DIR) + QStringLiteral("/shiroko/preview.png"); +} +} + +PetWindow::PetWindow(QWidget *parent) + : QWidget(parent) + , m_imageLabel(new QLabel(this)) + , m_dragging(false) + , m_alwaysOnTop(true) +{ + setAttribute(Qt::WA_TranslucentBackground); + setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint); + setMouseTracking(true); + + m_imageLabel->setAlignment(Qt::AlignCenter); + m_imageLabel->setAttribute(Qt::WA_TranslucentBackground); + + auto *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_imageLabel); + + loadPreviewImage(); +} + +void PetWindow::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + + QAction *topAction = menu.addAction(QStringLiteral("取消置顶")); + topAction->setCheckable(true); + topAction->setChecked(m_alwaysOnTop); + + menu.addSeparator(); + QAction *exitAction = menu.addAction(QStringLiteral("退出")); + + QAction *selectedAction = menu.exec(event->globalPos()); + if (selectedAction == topAction) + { + setAlwaysOnTop(!m_alwaysOnTop); + } + else if (selectedAction == exitAction) + { + close(); + } +} + +void PetWindow::mouseMoveEvent(QMouseEvent *event) +{ + if (m_dragging && (event->buttons() & Qt::LeftButton)) + { + move(event->globalPosition().toPoint() - m_dragOffset); + event->accept(); + return; + } + + QWidget::mouseMoveEvent(event); +} + +void PetWindow::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + m_dragging = true; + m_dragOffset = event->globalPosition().toPoint() - frameGeometry().topLeft(); + event->accept(); + return; + } + + QWidget::mousePressEvent(event); +} + +void PetWindow::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + m_dragging = false; + event->accept(); + return; + } + + QWidget::mouseReleaseEvent(event); +} + +void PetWindow::loadPreviewImage() +{ + const QString imagePath = previewImagePath(); + QPixmap pixmap(imagePath); + if (pixmap.isNull()) + { + m_imageLabel->setText(QStringLiteral("QtDesktopPet")); + resize(240, 160); + return; + } + + const QSize targetSize(320, 320); + const QPixmap scaled = pixmap.scaled(targetSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_imageLabel->setPixmap(scaled); + resize(scaled.size()); + + if (const QScreen *screen = QGuiApplication::primaryScreen()) + { + const QRect available = screen->availableGeometry(); + move(available.center() - rect().center()); + } +} + +void PetWindow::setAlwaysOnTop(bool enabled) +{ + m_alwaysOnTop = enabled; + + Qt::WindowFlags flags = windowFlags(); + if (enabled) + { + flags |= Qt::WindowStaysOnTopHint; + } + else + { + flags &= ~Qt::WindowStaysOnTopHint; + } + + setWindowFlags(flags); + show(); +} + diff --git a/src/ui/PetWindow.h b/src/ui/PetWindow.h new file mode 100644 index 0000000..1c58c67 --- /dev/null +++ b/src/ui/PetWindow.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +class PetWindow : public QWidget +{ +public: + explicit PetWindow(QWidget *parent = nullptr); + +protected: + void contextMenuEvent(QContextMenuEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private: + void loadPreviewImage(); + void setAlwaysOnTop(bool enabled); + + QLabel *m_imageLabel; + QPoint m_dragOffset; + bool m_dragging; + bool m_alwaysOnTop; +};