diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 25ed932..0000000 --- a/.clang-format +++ /dev/null @@ -1,2 +0,0 @@ -BasedOnStyle: Google -IndentWidth: 4 \ No newline at end of file diff --git a/.clangd b/.clangd deleted file mode 100644 index bfcb0ba..0000000 --- a/.clangd +++ /dev/null @@ -1,3 +0,0 @@ -CompileFlags: - # C++20 - Add: [-std=c++20] diff --git a/.gitignore b/.gitignore index 897cb7e..fdb5907 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ # Build artifacts build/ *.plasmoid -package/contents/ui/cppbridge/ # Temporary files *~ diff --git a/Makefile b/Makefile index f62880b..b0fb4bc 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,6 @@ METADATA_FILE := $(PACKAGE_DIR)/metadata.json WIDGET_ID := $(shell grep -oP '"Id": "\K[^"]+' $(METADATA_FILE)) BUILD_DIR := build PLASMOID_FILE := $(WIDGET_ID).plasmoid -CPP_DIR := cpp -CPP_BUILD_DIR := $(BUILD_DIR)/cpp -CPP_MODULE_DIR := $(PACKAGE_DIR)/contents/ui/cppbridge # Default target .PHONY: help @@ -18,13 +15,11 @@ help: @echo "Usage:" @echo " make dev Run widget in plasmoidviewer from local package (MODE=panel|desktop|hidpi)" @echo " make watch Start Development Mode with Hot Reload" - @echo " make cpp-build Build the C++ QML bridge module" - @echo " make cpp-clean Remove C++ bridge build artifacts" @echo " make install Install/upgrade widget in local Plasma package store" @echo " make uninstall Remove widget from local system" @echo " make package Build .plasmoid file for distribution" @echo " make clean Remove build artifacts" - @echo " make format Format QML, C++, JSON, YAML, and Markdown files" + @echo " make format Format QML, JSON, YAML, and Markdown files" @echo " make lint Run non-mutating lint and formatting checks" @echo " make hooks-install Configure Git to use versioned hooks from .githooks/" @@ -35,28 +30,11 @@ hooks-install: git config core.hooksPath .githooks @echo "Configured core.hooksPath=.githooks" -# C++ Bridge Build Targets -# Generate CMake build files for the C++ QML bridge. -.PHONY: cpp-configure -cpp-configure: - mkdir -p $(CPP_BUILD_DIR) - cd $(CPP_DIR) && cmake --preset clang-default - -# Build the C++ QML bridge module binaries. -.PHONY: cpp-build -cpp-build: cpp-configure - cmake --build $(CPP_BUILD_DIR) - -# Remove C++ bridge build output and copied module artifacts. -.PHONY: cpp-clean -cpp-clean: - rm -rf $(CPP_BUILD_DIR) $(CPP_MODULE_DIR) - # Testing Targets # Run plasmoidviewer from the local package directory. # Optional MODE values: panel, desktop, hidpi (omit MODE for default). .PHONY: dev -dev: cpp-build +dev: @mode="$(MODE)"; \ case "$$mode" in \ ""|default) \ @@ -75,7 +53,7 @@ dev: cpp-build # Development Tools # Start file watcher for iterative development and reload workflow. .PHONY: watch -watch: cpp-build +watch: ./scripts/watch.sh # Format all QML files in the package directory in-place. @@ -84,12 +62,6 @@ format-qml: @command -v qmlformat >/dev/null 2>&1 || { echo "Missing required tool: qmlformat"; exit 1; } find $(PACKAGE_DIR) -name "*.qml" -type f -exec qmlformat -i {} + -# Format C/C++ source and header files with clang-format. -.PHONY: format-cpp -format-cpp: - @command -v clang-format >/dev/null 2>&1 || { echo "Missing required tool: clang-format"; exit 1; } - find $(CPP_DIR) -type f \( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hh" -o -name "*.hpp" \) -exec clang-format -i {} + - # Format JSON/YAML/Markdown files with prettier. .PHONY: format-prettier format-prettier: @@ -102,7 +74,7 @@ format-prettier: # Run all formatting passes. .PHONY: format -format: format-qml format-cpp format-prettier +format: format-qml format-prettier # Linting # Check QML formatting without changing files. @@ -119,16 +91,10 @@ lint-qml-format: # Run semantic QML lint checks. .PHONY: lint-qml -lint-qml: cpp-build +lint-qml: @command -v qmllint >/dev/null 2>&1 || { echo "Missing required tool: qmllint"; exit 1; } find $(PACKAGE_DIR) -name "*.qml" -type f -print0 | xargs -0 -r qmllint -# Check C/C++ formatting without changing files. -.PHONY: lint-cpp -lint-cpp: - @command -v clang-format >/dev/null 2>&1 || { echo "Missing required tool: clang-format"; exit 1; } - find $(CPP_DIR) -type f \( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hh" -o -name "*.hpp" \) -print0 | xargs -0 -r clang-format --dry-run --Werror - # Check JSON/YAML/Markdown formatting without changing files. .PHONY: lint-prettier lint-prettier: @@ -141,12 +107,12 @@ lint-prettier: # Run all lint and formatting checks. .PHONY: lint -lint: lint-qml-format lint-qml lint-cpp lint-prettier +lint: lint-qml-format lint-qml lint-prettier # Installation # Install/upgrade widget in local Plasma package store. .PHONY: install -install: cpp-build +install: @if kpackagetool6 --type Plasma/Applet --show $(WIDGET_ID) >/dev/null 2>&1; then \ echo "Upgrading existing package: $(WIDGET_ID)"; \ kpackagetool6 --type Plasma/Applet --upgrade $(PACKAGE_DIR); \ @@ -157,7 +123,7 @@ install: cpp-build # Upgrade an already installed widget package with local changes. .PHONY: upgrade -upgrade: cpp-build +upgrade: kpackagetool6 --type Plasma/Applet --upgrade $(PACKAGE_DIR) # Remove installed widget package from local system. @@ -173,7 +139,7 @@ uninstall: # Packaging # Create distributable .plasmoid archive from current package contents. .PHONY: package -package: cpp-build +package: mkdir -p $(BUILD_DIR) rm -f $(BUILD_DIR)/$(PLASMOID_FILE) @@ -186,4 +152,4 @@ package: cpp-build # Delete all generated build artifacts for a clean workspace. .PHONY: clean clean: - rm -rf $(BUILD_DIR) $(CPP_MODULE_DIR) + rm -rf $(BUILD_DIR) diff --git a/README.md b/README.md index f617cfc..f6eb750 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # katoclock -Just a cat with a clock. Bootstrapped from my [kde-6-widget-starter](https://github.com/ruinivist/kde-6-widget-starter) +Just a QML-only cat clock widget for KDE Plasma 6. Bootstrapped from my [kde-6-widget-starter](https://github.com/ruinivist/kde-6-widget-starter) [Pling page](https://www.pling.com/p/2349620/) @@ -10,3 +10,7 @@ Just a cat with a clock. Bootstrapped from my [kde-6-widget-starter](https://git - clone the repo - `make install` from repo root + +# Package + +- `make package` builds a `.plasmoid` archive from the QML package and bundled image assets diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt deleted file mode 100644 index 1dcee43..0000000 --- a/cpp/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -cmake_minimum_required(VERSION 3.23) -project(cppbridge LANGUAGES CXX) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(Qt6 REQUIRED COMPONENTS Core Qml Quick) -find_package(OpenCV 4 REQUIRED COMPONENTS core imgcodecs imgproc) -qt_standard_project_setup(REQUIRES 6.5) - -qt_add_qml_module(cppbridgeplugin - URI cppbridge - VERSION 1.0 - OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../package/contents/ui/cppbridge" - SOURCES - src/clockimageprovider.hpp - src/clockimageprovider.cpp - src/config.hpp - src/renderer.hpp - src/renderer.cpp - src/timebridge.h - src/timebridge.cpp -) - -target_include_directories(cppbridgeplugin PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/src" - ${OpenCV_INCLUDE_DIRS} -) -target_link_libraries(cppbridgeplugin PRIVATE - Qt6::Core - Qt6::Qml - Qt6::Quick - opencv_core - opencv_imgcodecs - opencv_imgproc -) -set_target_properties(cppbridgeplugin PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON -) diff --git a/cpp/CMakePresets.json b/cpp/CMakePresets.json deleted file mode 100644 index ba467d7..0000000 --- a/cpp/CMakePresets.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 6, - "cmakeMinimumRequired": { - "major": 3, - "minor": 23, - "patch": 0 - }, - "configurePresets": [ - { - "name": "clang-default", - "displayName": "Clang Default", - "description": "Configure build/cpp with clang/clang++ and export compile commands.", - "generator": "Unix Makefiles", - "binaryDir": "${sourceDir}/../build/cpp", - "cacheVariables": { - "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++", - "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" - } - } - ], - "buildPresets": [ - { - "name": "clang-default", - "configurePreset": "clang-default" - } - ] -} diff --git a/cpp/src/clockimageprovider.cpp b/cpp/src/clockimageprovider.cpp deleted file mode 100644 index dbeedd6..0000000 --- a/cpp/src/clockimageprovider.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "clockimageprovider.hpp" - -#include - -ClockImageProvider::ClockImageProvider() - : QQuickImageProvider(QQuickImageProvider::Image) {} - -QImage ClockImageProvider::requestImage(const QString&, QSize* size, - const QSize& requestedSize) { - QImage frame; - { - QMutexLocker locker(&mutex_); - frame = image_; - } - - if (size != nullptr) { - *size = frame.size(); - } - - if (!frame.isNull() && requestedSize.width() > 0 && - requestedSize.height() > 0) { - return frame.scaled(requestedSize, Qt::KeepAspectRatio, - Qt::SmoothTransformation); - } - - return frame; -} - -void ClockImageProvider::setImage(const QImage& image) { - QMutexLocker locker(&mutex_); - image_ = image; -} diff --git a/cpp/src/clockimageprovider.hpp b/cpp/src/clockimageprovider.hpp deleted file mode 100644 index 1a1a9e2..0000000 --- a/cpp/src/clockimageprovider.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include - -class ClockImageProvider : public QQuickImageProvider { - public: - ClockImageProvider(); - - QImage requestImage(const QString& id, QSize* size, - const QSize& requestedSize) override; - - void setImage(const QImage& image); - - private: - QMutex mutex_; - QImage image_; -}; diff --git a/cpp/src/config.hpp b/cpp/src/config.hpp deleted file mode 100644 index 043d4ff..0000000 --- a/cpp/src/config.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -// file full of magic numbers -// most are hand tuned based on the cat image I have - -namespace Config { -const cv::Point2f CLOCK_CENTER(300.0f, 286.0f); - -const double HAND_SCALE = 0.22; -const double HOUR_HAND_SCALE = HAND_SCALE * 0.7; -const double MINUTE_HAND_SCALE = HAND_SCALE * 0.7; - -const cv::Point2f HOUR_HAND_PIVOT_FRACTION(0.126f, 0.502f); -const cv::Point2f MINUTE_HAND_PIVOT_FRACTION(0.101f, 0.488f); -// Asset convention: unrotated hand points right (+X). -// Clock convention: 12:00 points up (-Y), hence +90 degrees. -const double HAND_BASE_ANGLE_OFFSET = 90.0; - -} // namespace Config diff --git a/cpp/src/renderer.cpp b/cpp/src/renderer.cpp deleted file mode 100644 index 688d4e3..0000000 --- a/cpp/src/renderer.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "renderer.hpp" - -#include -#include -#include - -#include "config.hpp" - -Renderer::Renderer() {} - -bool Renderer::load_assets(const std::string& bg_path, - const std::string& hour_hand_path, - const std::string& minute_hand_path) { - background_ = cv::imread(bg_path, cv::IMREAD_UNCHANGED); - if (background_.empty()) { - std::cerr << "failed to load background: " << bg_path << std::endl; - return false; - } - if (background_.channels() != 4) { - std::cerr << "expected rgba png for background"; - return false; - } - - // Load hands with alpha channel. - hour_hand_ = cv::imread(hour_hand_path, cv::IMREAD_UNCHANGED); - if (hour_hand_.empty()) { - std::cerr << "failed to load hour hand: " << hour_hand_path - << std::endl; - return false; - } - - minute_hand_ = cv::imread(minute_hand_path, cv::IMREAD_UNCHANGED); - if (minute_hand_.empty()) { - std::cerr << "failed to load minute hand: " << minute_hand_path - << std::endl; - return false; - } - - if (hour_hand_.channels() != 4 || minute_hand_.channels() != 4) { - std::cerr << "expected rgba png for hands" << std::endl; - return false; - } - - return true; -} - -cv::Mat Renderer::render(double hour_angle, double minute_angle) { - auto clock_to_cv_rotation = [](double clock_angle) { - return Config::HAND_BASE_ANGLE_OFFSET - clock_angle; - }; - - cv::Mat warped_hour = transform_hand( - hour_hand_, clock_to_cv_rotation(hour_angle), Config::HOUR_HAND_SCALE, - Config::HOUR_HAND_PIVOT_FRACTION); - cv::Mat warped_minute = transform_hand( - minute_hand_, clock_to_cv_rotation(minute_angle), - Config::MINUTE_HAND_SCALE, Config::MINUTE_HAND_PIVOT_FRACTION); - - cv::Mat keyframe = background_.clone(); - - // Layer order matters: hour underneath minute. - blend_overlay(keyframe, warped_hour); - blend_overlay(keyframe, warped_minute); - - return keyframe; -} - -cv::Mat Renderer::transform_hand(const cv::Mat& hand_sprite, double angle_deg, - double scale, - const cv::Point2f& pivot_fraction) { - cv::Mat oriented_sprite; - cv::flip(hand_sprite, oriented_sprite, 0); - - if (scale <= 0.0) { - return cv::Mat::zeros(background_.size(), CV_8UC4); - } - - cv::Point2f pivot(oriented_sprite.cols * pivot_fraction.x, - oriented_sprite.rows * pivot_fraction.y); - - cv::Mat rot_mat = cv::getRotationMatrix2D(pivot, angle_deg, scale); - rot_mat.at(0, 2) += Config::CLOCK_CENTER.x - pivot.x; - rot_mat.at(1, 2) += Config::CLOCK_CENTER.y - pivot.y; - - cv::Mat overlay = cv::Mat::zeros(background_.size(), CV_8UC4); - cv::warpAffine(oriented_sprite, overlay, rot_mat, background_.size(), - cv::INTER_CUBIC, cv::BORDER_CONSTANT, - cv::Scalar(0, 0, 0, 0)); - - return overlay; -} - -void Renderer::blend_overlay(cv::Mat& bg, const cv::Mat& fg) { - // Blend hand RGB onto background RGB using hand alpha - for (int y = 0; y < fg.rows; ++y) { - const cv::Vec4b* fg_ptr = fg.ptr(y); - cv::Vec4b* bg_ptr = bg.ptr(y); - - for (int x = 0; x < fg.cols; ++x) { - const cv::Vec4b& fg_pixel = fg_ptr[x]; - const double alpha = fg_pixel[3] / 255.0; - - if (alpha > 0.0) { - const cv::Vec4b bg_pixel = bg_ptr[x]; - for (int c = 0; c < 3; ++c) { - bg_ptr[x][c] = static_cast( - fg_pixel[c] * alpha + bg_pixel[c] * (1.0 - alpha)); - } - } - } - } -} diff --git a/cpp/src/renderer.hpp b/cpp/src/renderer.hpp deleted file mode 100644 index 8fb06a9..0000000 --- a/cpp/src/renderer.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -class Renderer { - public: - Renderer(); - - // Load assets from disk. Returns false if any fail to load. - bool load_assets(const std::string& bg_path, - const std::string& hour_hand_path, - const std::string& minute_hand_path); - - // Composites the hands onto the background at the given angles (degrees). - // Returns the result image. - cv::Mat render(double hour_angle, double minute_angle); - - private: - cv::Mat background_; - cv::Mat hour_hand_; - cv::Mat minute_hand_; - - // Helpers - cv::Mat transform_hand(const cv::Mat& hand_sprite, double angle_deg, - double scale, const cv::Point2f& pivot_fraction); - void blend_overlay(cv::Mat& bg, const cv::Mat& fg); -}; diff --git a/cpp/src/timebridge.cpp b/cpp/src/timebridge.cpp deleted file mode 100644 index 3037db3..0000000 --- a/cpp/src/timebridge.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include "timebridge.h" - -#include -#include -#include -#include - -#include "clockimageprovider.hpp" -#include "renderer.hpp" - -namespace { -constexpr int kMillisPerMinute = 60 * 1000; -} - -ClockBridge::ClockBridge(QObject* parent) : QObject(parent) { - updateTimer_.setSingleShot(true); - connect(&updateTimer_, &QTimer::timeout, this, - &ClockBridge::onUpdateTimerTimeout); -} - -ClockBridge::~ClockBridge() { - updateTimer_.stop(); - if (renderer_ != nullptr) { - delete renderer_; - renderer_ = nullptr; - } -} - -QString ClockBridge::imageSource() const { return imageSource_; } - -bool ClockBridge::ready() const { return ready_; } - -QString ClockBridge::errorString() const { return errorString_; } - -bool ClockBridge::initialize(const QUrl& bg, const QUrl& hour, - const QUrl& minute) { - updateTimer_.stop(); - initialized_ = false; - setReady(false); - - const QString bgPath = toLocalPath(bg); - const QString hourPath = toLocalPath(hour); - const QString minutePath = toLocalPath(minute); - if (bgPath.isEmpty() || hourPath.isEmpty() || minutePath.isEmpty()) { - setErrorString(QStringLiteral("Clock assets must be local file URLs.")); - return false; - } - - if (!ensureImageProvider()) { - return false; - } - - if (renderer_ == nullptr) { - renderer_ = new Renderer(); - } - - if (!renderer_->load_assets(bgPath.toStdString(), hourPath.toStdString(), - minutePath.toStdString())) { - setErrorString(QStringLiteral("Failed to load clock assets.")); - return false; - } - - initialized_ = true; - if (!renderForCurrentTime()) { - initialized_ = false; - return false; - } - - scheduleNextMinuteTick(); - return true; -} - -void ClockBridge::refreshNow() { - if (!initialized_) { - setErrorString(QStringLiteral("ClockBridge is not initialized.")); - setReady(false); - return; - } - - renderForCurrentTime(); - scheduleNextMinuteTick(); -} - -void ClockBridge::onUpdateTimerTimeout() { - if (!initialized_) { - return; - } - - renderForCurrentTime(); - scheduleNextMinuteTick(); -} - -bool ClockBridge::ensureImageProvider() { - if (imageProvider_ != nullptr) { - return true; - } - - QQmlEngine* engine = qmlEngine(this); - if (engine == nullptr) { - setErrorString(QStringLiteral("ClockBridge has no active QQmlEngine.")); - return false; - } - - providerId_ = QStringLiteral("catclock_%1") - .arg(QUuid::createUuid().toString(QUuid::Id128)); - if (engine->imageProvider(providerId_) != nullptr) { - setErrorString(QStringLiteral("Image provider ID collision.")); - return false; - } - - imageProvider_ = new ClockImageProvider(); - engine->addImageProvider(providerId_, imageProvider_); - return true; -} - -bool ClockBridge::renderForCurrentTime() { - if (renderer_ == nullptr || imageProvider_ == nullptr) { - setErrorString(QStringLiteral("ClockBridge is not initialized.")); - setReady(false); - return false; - } - - const QTime now = QTime::currentTime(); - const int minute = now.minute(); - const int hour = now.hour(); - - const double minuteAngle = minute * 6.0; - const double hourAngle = (hour % 12) * 30.0 + minute * 0.5; - - cv::Mat frame = renderer_->render(hourAngle, minuteAngle); - if (frame.empty()) { - setErrorString(QStringLiteral("Renderer returned an empty frame.")); - setReady(false); - return false; - } - - cv::Mat rgbaFrame; - cv::cvtColor(frame, rgbaFrame, cv::COLOR_BGRA2RGBA); - QImage image(rgbaFrame.data, rgbaFrame.cols, rgbaFrame.rows, - static_cast(rgbaFrame.step), QImage::Format_RGBA8888); - imageProvider_->setImage(image.copy()); - - setErrorString(QString()); - setReady(true); - bumpFrameRevision(); - return true; -} - -void ClockBridge::scheduleNextMinuteTick() { - if (!initialized_) { - return; - } - - const QTime now = QTime::currentTime(); - int delayMs = kMillisPerMinute - (now.second() * 1000 + now.msec()); - if (delayMs <= 0 || delayMs > kMillisPerMinute) { - delayMs = kMillisPerMinute; - } - - updateTimer_.start(delayMs); -} - -void ClockBridge::setReady(bool value) { - if (ready_ == value) { - return; - } - ready_ = value; - emit readyChanged(); -} - -void ClockBridge::setErrorString(const QString& value) { - if (errorString_ == value) { - return; - } - errorString_ = value; - emit errorStringChanged(); -} - -QString ClockBridge::toLocalPath(const QUrl& url) const { - if (url.isEmpty()) { - return QString(); - } - - if (url.isLocalFile()) { - return url.toLocalFile(); - } - - if (url.scheme().isEmpty()) { - return url.toString(); - } - - return QString(); -} - -QString ClockBridge::buildImageSource() const { - if (providerId_.isEmpty()) { - return QString(); - } - - return QStringLiteral("image://%1/frame?rev=%2") - .arg(providerId_) - .arg(frameRevision_); -} - -// to signal image inddeed changed even if the path is same -// we update revision -void ClockBridge::bumpFrameRevision() { - ++frameRevision_; - const QString nextSource = buildImageSource(); - if (imageSource_ == nextSource) { - return; - } - - imageSource_ = nextSource; - emit imageSourceChanged(); -} diff --git a/cpp/src/timebridge.h b/cpp/src/timebridge.h deleted file mode 100644 index 9ef18ae..0000000 --- a/cpp/src/timebridge.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -class ClockImageProvider; -class Renderer; - -class ClockBridge : public QObject { - Q_OBJECT - QML_ELEMENT - Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged) - Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) - Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) - - public: - explicit ClockBridge(QObject* parent = nullptr); - ~ClockBridge() override; - - QString imageSource() const; - bool ready() const; - QString errorString() const; - - Q_INVOKABLE bool initialize(const QUrl& bg, const QUrl& hour, - const QUrl& minute); - Q_INVOKABLE void refreshNow(); - - signals: - void imageSourceChanged(); - void readyChanged(); - void errorStringChanged(); - - private slots: - void onUpdateTimerTimeout(); - - private: - bool ensureImageProvider(); - bool renderForCurrentTime(); - void scheduleNextMinuteTick(); - void setReady(bool value); - void setErrorString(const QString& value); - QString toLocalPath(const QUrl& url) const; - QString buildImageSource() const; - void bumpFrameRevision(); - - Renderer* renderer_ = nullptr; - ClockImageProvider* imageProvider_ = nullptr; - QString providerId_; - QString imageSource_; - QString errorString_; - bool ready_ = false; - bool initialized_ = false; - int frameRevision_ = 0; - QTimer updateTimer_; -}; diff --git a/package/contents/ui/main.qml b/package/contents/ui/main.qml index 95f9c83..d0a95da 100644 --- a/package/contents/ui/main.qml +++ b/package/contents/ui/main.qml @@ -1,56 +1,125 @@ import QtQuick import QtQuick.Layouts -import "cppbridge" as CppBridge import org.kde.kirigami as Kirigami -import org.kde.plasma.components as PlasmaComponents import org.kde.plasma.core as PlasmaCore import org.kde.plasma.plasmoid PlasmoidItem { id: root - // Widget setup + readonly property real clockWidth: 421 + readonly property real clockHeight: 600 + readonly property real clockCenterX: 300 + readonly property real clockCenterY: 286 + readonly property real handScale: 0.154 + property real hourAngle: 0 + property real minuteAngle: 0 + + function refreshTime() { + const now = new Date(); + const minute = now.getMinutes(); + const hour = now.getHours(); + root.minuteAngle = minute * 6; + root.hourAngle = (hour % 12) * 30 + minute * 0.5; + let nextTick = 60000 - (now.getSeconds() * 1000 + now.getMilliseconds()); + if (nextTick <= 0 || nextTick > 60000) + nextTick = 60000; + + minuteTimer.interval = nextTick; + minuteTimer.restart(); + } + width: Kirigami.Units.gridUnit * 20 height: Kirigami.Units.gridUnit * 20 Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground toolTipMainText: "katoclock" - Component.onCompleted: { - const initialized = clockBridge.initialize(Qt.resolvedUrl("assets/background.png"), Qt.resolvedUrl("assets/hand_hour.png"), Qt.resolvedUrl("assets/hand_minute.png")); - if (!initialized) - console.warn(clockBridge.errorString); + Component.onCompleted: refreshTime() + Timer { + id: minuteTimer + + repeat: false + onTriggered: root.refreshTime() } - CppBridge.ClockBridge { - id: clockBridge + component ClockHand: Item { + required property url imageSource + required property real sourceWidth + required property real sourceHeight + required property real pivotFractionX + required property real pivotFractionY + required property real clockAngle + readonly property real pivotX: width * pivotFractionX + readonly property real pivotY: height * pivotFractionY + + width: sourceWidth * root.handScale + height: sourceHeight * root.handScale + x: root.clockCenterX - pivotX + y: root.clockCenterY - pivotY + + Image { + id: handImage + + anchors.fill: parent + source: imageSource + smooth: true + mipmap: true + + transform: Scale { + origin.x: handImage.width / 2 + origin.y: handImage.height / 2 + yScale: -1 + } + + } + + transform: Rotation { + origin.x: pivotX + origin.y: pivotY + angle: clockAngle - 90 + } + } component ClockImageView: Item { - // asset image size -> 421x600, 1 is cropping artifact but I'll live with it - implicitWidth: 421 - implicitHeight: 600 + implicitWidth: root.clockWidth + implicitHeight: root.clockHeight - Image { - anchors.fill: parent - visible: clockBridge.ready - source: clockBridge.imageSource - fillMode: Image.PreserveAspectFit - asynchronous: true - cache: false - } + Item { + width: root.clockWidth + height: root.clockHeight + anchors.centerIn: parent + scale: Math.min(parent.width / width, parent.height / height) + + Image { + anchors.fill: parent + source: Qt.resolvedUrl("assets/background.png") + smooth: true + mipmap: true + } + + ClockHand { + imageSource: Qt.resolvedUrl("assets/hand_hour.png") + sourceWidth: 227 + sourceHeight: 76 + pivotFractionX: 0.126 + pivotFractionY: 0.502 + clockAngle: root.hourAngle + } + + ClockHand { + imageSource: Qt.resolvedUrl("assets/hand_minute.png") + sourceWidth: 316 + sourceHeight: 78 + pivotFractionX: 0.101 + pivotFractionY: 0.488 + clockAngle: root.minuteAngle + } - PlasmaComponents.Label { - anchors.fill: parent - visible: !clockBridge.ready - text: clockBridge.errorString.length > 0 ? clockBridge.errorString : "Loading..." - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap } } - // full representation (popup or desktop widget) fullRepresentation: Item { Layout.minimumWidth: Kirigami.Units.gridUnit * 12 Layout.minimumHeight: Kirigami.Units.gridUnit * 12 @@ -64,7 +133,6 @@ PlasmoidItem { } - // compact representation (icon in panel) compactRepresentation: Item { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 Layout.minimumHeight: Kirigami.Units.gridUnit * 6