Compare commits
7
Commits
v0.1.0
..
4023a2fb01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4023a2fb01 | ||
|
|
3eecbc16bc | ||
|
|
b939224e9d | ||
|
|
644a1e0c78 | ||
|
|
bc9d8af0fe | ||
|
|
7d65578a5f | ||
|
|
f883b3d7fe |
+10
-19
@@ -2,29 +2,20 @@
|
|||||||
Checks: >
|
Checks: >
|
||||||
-*,
|
-*,
|
||||||
bugprone-*,
|
bugprone-*,
|
||||||
|
-bugprone-easily-swappable-parameters,
|
||||||
|
-bugprone-narrowing-conversions,
|
||||||
|
-bugprone-implicit-widening-of-multiplication-result,
|
||||||
clang-analyzer-*,
|
clang-analyzer-*,
|
||||||
modernize-*,
|
cppcoreguidelines-init-variables,
|
||||||
-modernize-use-trailing-return-type,
|
misc-no-recursion,
|
||||||
performance-*,
|
concurrency-mt-unsafe,
|
||||||
readability-braces-around-statements,
|
|
||||||
readability-else-after-return,
|
readability-else-after-return,
|
||||||
readability-identifier-naming,
|
readability-make-member-function-const,
|
||||||
readability-redundant-*,
|
readability-redundant-*,
|
||||||
readability-simplify-*,
|
readability-simplify-*,
|
||||||
CheckOptions:
|
modernize-use-override,
|
||||||
readability-identifier-naming.ClassCase: CamelCase
|
modernize-use-nullptr,
|
||||||
readability-identifier-naming.EnumCase: CamelCase
|
modernize-deprecated-headers,
|
||||||
readability-identifier-naming.FunctionCase: camelBack
|
|
||||||
readability-identifier-naming.MemberCase: camelBack
|
|
||||||
readability-identifier-naming.MemberPrefix: m_
|
|
||||||
readability-identifier-naming.MethodCase: camelBack
|
|
||||||
readability-identifier-naming.NamespaceCase: CamelCase
|
|
||||||
readability-identifier-naming.ParameterCase: camelBack
|
|
||||||
readability-identifier-naming.PrivateMemberPrefix: m_
|
|
||||||
readability-identifier-naming.StaticConstantCase: UPPER_CASE
|
|
||||||
readability-identifier-naming.StaticConstantPrefix: k
|
|
||||||
readability-identifier-naming.VariableCase: camelBack
|
|
||||||
WarningsAsErrors: "*"
|
|
||||||
HeaderFilterRegex: ".*"
|
HeaderFilterRegex: ".*"
|
||||||
FormatStyle: file
|
FormatStyle: file
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: Rebuild CI Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * 1'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: alpine
|
||||||
|
container:
|
||||||
|
image: node:26-alpine
|
||||||
|
env:
|
||||||
|
IMAGE: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Docker CLI
|
||||||
|
run: apk add --no-cache docker-cli
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --username aramjonghu --password-stdin
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: docker build -t "$IMAGE" -f ci/Dockerfile .
|
||||||
|
|
||||||
|
- name: Push image
|
||||||
|
run: docker push "$IMAGE"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: C++
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: alpine
|
||||||
|
container:
|
||||||
|
image: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: ninja -C build
|
||||||
|
|
||||||
|
- name: clang-tidy
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \
|
||||||
|
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct'
|
||||||
+37
-8
@@ -1,6 +1,35 @@
|
|||||||
cmake_minimum_required(VERSION 3.19)
|
cmake_minimum_required(VERSION 3.19)
|
||||||
|
|
||||||
project(ZShell)
|
if(NOT DEFINED VERSION)
|
||||||
|
execute_process(
|
||||||
|
COMMAND git describe --tags --abbrev=0
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE GIT_LAST_TAG
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_QUIET
|
||||||
|
)
|
||||||
|
|
||||||
|
if (GIT_LAST_TAG)
|
||||||
|
string(REGEX REPLACE "^v" "" GIT_LAST_TAG "${GIT_LAST_TAG}")
|
||||||
|
|
||||||
|
string(REPLACE "." ";" VERSION_LIST "${GIT_LAST_TAG}")
|
||||||
|
list(GET VERSION_LIST 0 VERSION_MAJOR)
|
||||||
|
list(GET VERSION_LIST 1 VERSION_MINOR)
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND git rev-list v${VERSION_MAJOR}.${VERSION_MINOR}.0..HEAD --count
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE VERSION_PATCH
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_QUIET
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
|
||||||
|
|
||||||
|
project(ZShell VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
|
||||||
|
message(STATUS "ZShell version: ${VERSION}")
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -15,11 +44,11 @@ set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell confi
|
|||||||
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
|
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
|
||||||
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
|
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
|
||||||
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
|
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
|
||||||
-Wformat=2 -Wfloat-equal -Woverloaded-virtual
|
-Wformat=2 -Wfloat-equal -Woverloaded-virtual
|
||||||
-Wsign-conversion -Wredundant-decls -Wswitch
|
-Wsign-conversion -Wredundant-decls -Wswitch
|
||||||
-Wunreachable-code
|
-Wunreachable-code
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -30,8 +59,8 @@ endif()
|
|||||||
|
|
||||||
if("shell" IN_LIST ENABLE_MODULES)
|
if("shell" IN_LIST ENABLE_MODULES)
|
||||||
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
|
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
|
||||||
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
|
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Disable watching for changes
|
# Disable watching for changes
|
||||||
file(READ shell.qml SHELL_QML)
|
file(READ shell.qml SHELL_QML)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pkg_check_modules(Cava IMPORTED_TARGET libcava QUIET)
|
|||||||
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
|
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
|
||||||
|
|
||||||
if(NOT Cava_FOUND)
|
if(NOT Cava_FOUND)
|
||||||
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
|
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT TARGET Sensors::Sensors)
|
if(NOT TARGET Sensors::Sensors)
|
||||||
@@ -24,48 +24,50 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
|
|||||||
qt_standard_project_setup(REQUIRES 6.9)
|
qt_standard_project_setup(REQUIRES 6.9)
|
||||||
|
|
||||||
function(qml_module arg_TARGET)
|
function(qml_module arg_TARGET)
|
||||||
cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES")
|
cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES")
|
||||||
qt_add_qml_module(${arg_TARGET}
|
qt_add_qml_module(${arg_TARGET}
|
||||||
URI ${arg_URI}
|
URI ${arg_URI}
|
||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
SOURCES ${arg_SOURCES}
|
SOURCES ${arg_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_query_qml_module(${arg_TARGET}
|
qt_query_qml_module(${arg_TARGET}
|
||||||
URI module_uri
|
URI module_uri
|
||||||
VERSION module_version
|
VERSION module_version
|
||||||
PLUGIN_TARGET module_plugin_target
|
PLUGIN_TARGET module_plugin_target
|
||||||
TARGET_PATH module_target_path
|
TARGET_PATH module_target_path
|
||||||
QMLDIR module_qmldir
|
QMLDIR module_qmldir
|
||||||
TYPEINFO module_typeinfo
|
TYPEINFO module_typeinfo
|
||||||
)
|
)
|
||||||
set(module_dir "${INSTALL_QMLDIR}/${module_target_path}")
|
set(module_dir "${INSTALL_QMLDIR}/${module_target_path}")
|
||||||
install(TARGETS ${arg_TARGET} LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}")
|
install(TARGETS ${arg_TARGET} LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}")
|
||||||
install(TARGETS "${module_plugin_target}" LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}")
|
install(TARGETS "${module_plugin_target}" LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}")
|
||||||
install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
|
install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
|
||||||
install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
|
install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
|
||||||
target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES})
|
target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
qml_module(ZShell
|
qml_module(ZShell
|
||||||
URI ZShell
|
URI ZShell
|
||||||
SOURCES
|
SOURCES
|
||||||
writefile.hpp writefile.cpp
|
writefile.hpp writefile.cpp
|
||||||
appdb.hpp appdb.cpp
|
appdb.hpp appdb.cpp
|
||||||
imageanalyser.hpp imageanalyser.cpp
|
imageanalyser.hpp imageanalyser.cpp
|
||||||
requests.hpp requests.cpp
|
requests.hpp requests.cpp
|
||||||
toaster.hpp toaster.cpp
|
toaster.hpp toaster.cpp
|
||||||
qalculator.hpp qalculator.cpp
|
qalculator.hpp qalculator.cpp
|
||||||
zutils.hpp zutils.cpp
|
zutils.hpp zutils.cpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
Qt::Concurrent
|
Qt::Concurrent
|
||||||
Qt::Sql
|
Qt::Sql
|
||||||
Qt::DBus
|
Qt::DBus
|
||||||
PkgConfig::Qalculate
|
PkgConfig::Qalculate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(ZShell PRIVATE ZSHELL_VERSION="${VERSION}")
|
||||||
|
|
||||||
add_subdirectory(Models)
|
add_subdirectory(Models)
|
||||||
add_subdirectory(Internal)
|
add_subdirectory(Internal)
|
||||||
add_subdirectory(Services)
|
add_subdirectory(Services)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "lazylistview.hpp"
|
#include "lazylistview.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <qqmlcontext.h>
|
#include <qqmlcontext.h>
|
||||||
#include <qtimer.h>
|
#include <qtimer.h>
|
||||||
|
|
||||||
@@ -13,11 +14,7 @@ constexpr int ASYNC_BATCH_DESTROY = 4;
|
|||||||
|
|
||||||
namespace ZShell::components {
|
namespace ZShell::components {
|
||||||
|
|
||||||
// --- LazyListViewAttached ---
|
LazyListViewAttached::LazyListViewAttached(QObject* parent) : QObject(parent) {}
|
||||||
|
|
||||||
LazyListViewAttached::LazyListViewAttached(QObject* parent)
|
|
||||||
: QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal LazyListViewAttached::preferredHeight() const {
|
qreal LazyListViewAttached::preferredHeight() const {
|
||||||
return m_preferredHeight;
|
return m_preferredHeight;
|
||||||
@@ -85,10 +82,7 @@ void LazyListViewAttached::setTrackViewport(bool track) {
|
|||||||
emit trackViewportChanged();
|
emit trackViewportChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- LazyListView ---
|
LazyListView::LazyListView(QQuickItem* parent) : QQuickItem(parent) {
|
||||||
|
|
||||||
LazyListView::LazyListView(QQuickItem* parent)
|
|
||||||
: QQuickItem(parent) {
|
|
||||||
setFlag(ItemHasContents, false);
|
setFlag(ItemHasContents, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +97,6 @@ LazyListView::~LazyListView() {
|
|||||||
destroyDelegate(entry);
|
destroyDelegate(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Model & Delegate ---
|
|
||||||
|
|
||||||
QAbstractItemModel* LazyListView::model() const {
|
QAbstractItemModel* LazyListView::model() const {
|
||||||
return m_model;
|
return m_model;
|
||||||
}
|
}
|
||||||
@@ -138,8 +130,6 @@ void LazyListView::setDelegate(QQmlComponent* delegate) {
|
|||||||
emit delegateChanged();
|
emit delegateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Layout ---
|
|
||||||
|
|
||||||
qreal LazyListView::spacing() const {
|
qreal LazyListView::spacing() const {
|
||||||
return m_spacing;
|
return m_spacing;
|
||||||
}
|
}
|
||||||
@@ -172,8 +162,6 @@ void LazyListView::setContentY(qreal contentY) {
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Viewport ---
|
|
||||||
|
|
||||||
QRectF LazyListView::viewport() const {
|
QRectF LazyListView::viewport() const {
|
||||||
return m_viewport;
|
return m_viewport;
|
||||||
}
|
}
|
||||||
@@ -211,8 +199,6 @@ void LazyListView::setCacheBuffer(qreal buffer) {
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Sizing ---
|
|
||||||
|
|
||||||
qreal LazyListView::estimatedHeight() const {
|
qreal LazyListView::estimatedHeight() const {
|
||||||
return m_estimatedHeight;
|
return m_estimatedHeight;
|
||||||
}
|
}
|
||||||
@@ -258,7 +244,8 @@ qreal LazyListView::delegateHeight(QQuickItem* item) {
|
|||||||
if (!item)
|
if (!item)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false));
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
if (attached && attached->preferredHeight() >= 0)
|
if (attached && attached->preferredHeight() >= 0)
|
||||||
return attached->preferredHeight();
|
return attached->preferredHeight();
|
||||||
|
|
||||||
@@ -269,7 +256,8 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
|
|||||||
if (!item)
|
if (!item)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false));
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
if (attached) {
|
if (attached) {
|
||||||
if (attached->visibleHeight() >= 0)
|
if (attached->visibleHeight() >= 0)
|
||||||
return attached->visibleHeight();
|
return attached->visibleHeight();
|
||||||
@@ -283,12 +271,11 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
|
|||||||
bool LazyListView::isDelegateReady(QQuickItem* item) {
|
bool LazyListView::isDelegateReady(QQuickItem* item) {
|
||||||
if (!item)
|
if (!item)
|
||||||
return false;
|
return false;
|
||||||
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false));
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
return !att || att->ready();
|
return !att || att->ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Animation Durations ---
|
|
||||||
|
|
||||||
int LazyListView::removeDuration() const {
|
int LazyListView::removeDuration() const {
|
||||||
return m_removeDuration;
|
return m_removeDuration;
|
||||||
}
|
}
|
||||||
@@ -311,21 +298,18 @@ void LazyListView::setReadyDelay(int delay) {
|
|||||||
emit readyDelayChanged();
|
emit readyDelayChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- State ---
|
|
||||||
|
|
||||||
int LazyListView::count() const {
|
int LazyListView::count() const {
|
||||||
return m_model ? m_model->rowCount() : 0;
|
return m_model ? m_model->rowCount() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- QQuickItem Overrides ---
|
|
||||||
|
|
||||||
void LazyListView::componentComplete() {
|
void LazyListView::componentComplete() {
|
||||||
QQuickItem::componentComplete();
|
QQuickItem::componentComplete();
|
||||||
m_componentComplete = true;
|
m_componentComplete = true;
|
||||||
resetContent();
|
resetContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) {
|
void LazyListView::geometryChange(const QRectF& newGeometry,
|
||||||
|
const QRectF& oldGeometry) {
|
||||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||||
|
|
||||||
if (!m_componentComplete)
|
if (!m_componentComplete)
|
||||||
@@ -348,71 +332,84 @@ void LazyListView::updatePolish() {
|
|||||||
// Flush pending inserts — make items visible and clear the adding flag
|
// Flush pending inserts — make items visible and clear the adding flag
|
||||||
// so enter animations begin. When readyDelay > 0 the entire insert is
|
// so enter animations begin. When readyDelay > 0 the entire insert is
|
||||||
// deferred so delegates have time to lay out before appearing.
|
// deferred so delegates have time to lay out before appearing.
|
||||||
for (auto& entry : m_delegates) {
|
|
||||||
if (!entry.pendingInsert || !entry.item)
|
// Collect pending indices first — avoids scanning the entire hash each frame
|
||||||
continue;
|
QList<int> pendingIndices;
|
||||||
|
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) {
|
||||||
|
if (it->pendingInsert && it->item)
|
||||||
|
pendingIndices.append(it.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int pendingIdx : pendingIndices) {
|
||||||
|
auto& entry = m_delegates[pendingIdx];
|
||||||
|
|
||||||
if (m_readyDelay > 0) {
|
if (m_readyDelay > 0) {
|
||||||
if (!entry.readyDelayStarted) {
|
if (!entry.readyDelayStarted) {
|
||||||
entry.readyDelayStarted = true;
|
entry.readyDelayStarted = true;
|
||||||
auto* item = entry.item;
|
auto* item = entry.item;
|
||||||
QTimer::singleShot(m_readyDelay, this, [this, item] {
|
QTimer::singleShot(m_readyDelay, this, [this, item] {
|
||||||
auto indexIt = m_itemToIndex.find(item);
|
auto indexIt = m_itemToIndex.find(item);
|
||||||
if (indexIt == m_itemToIndex.end())
|
if (indexIt == m_itemToIndex.end())
|
||||||
return;
|
return;
|
||||||
const int idx = indexIt.value();
|
const int idx = indexIt.value();
|
||||||
auto it = m_delegates.find(idx);
|
auto it = m_delegates.find(idx);
|
||||||
if (it == m_delegates.end() || it->item != item || !it->pendingInsert)
|
if (it == m_delegates.end() || it->item != item ||
|
||||||
return;
|
!it->pendingInsert)
|
||||||
|
return;
|
||||||
|
|
||||||
it->pendingInsert = false;
|
it->pendingInsert = false;
|
||||||
it->readyDelayStarted = false;
|
it->readyDelayStarted = false;
|
||||||
|
|
||||||
// Set initial y to visual position (based on current visible heights)
|
// Set initial y to visual position (based on current visible heights)
|
||||||
if (idx >= 0 && idx < static_cast<int>(m_layout.size())) {
|
if (idx >= 0 && idx < static_cast<int>(m_layout.size())) {
|
||||||
qreal visualY = 0;
|
qreal visualY = 0;
|
||||||
bool hasVisItem = false;
|
bool hasVisItem = false;
|
||||||
for (int i = 0; i < static_cast<int>(m_layout.size()); ++i) {
|
for (int i = 0; i < static_cast<int>(m_layout.size());
|
||||||
qreal h;
|
++i) {
|
||||||
auto dit = m_delegates.find(i);
|
qreal h = NAN;
|
||||||
if (dit != m_delegates.end() && dit->item)
|
auto dit = m_delegates.find(i);
|
||||||
h = delegateVisibleHeight(dit->item);
|
if (dit != m_delegates.end() && dit->item)
|
||||||
else
|
h = delegateVisibleHeight(dit->item);
|
||||||
h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight();
|
else
|
||||||
if (h > 0) {
|
h = m_layout[i].heightKnown
|
||||||
if (hasVisItem)
|
? m_layout[i].height
|
||||||
visualY += m_spacing;
|
: effectiveEstimatedHeight();
|
||||||
hasVisItem = true;
|
if (h > 0) {
|
||||||
}
|
if (hasVisItem)
|
||||||
if (i == idx)
|
visualY += m_spacing;
|
||||||
break;
|
hasVisItem = true;
|
||||||
if (h > 0)
|
|
||||||
visualY += h;
|
|
||||||
}
|
}
|
||||||
item->setY(visualY - m_contentY);
|
if (i == idx)
|
||||||
|
break;
|
||||||
|
if (h > 0)
|
||||||
|
visualY += h;
|
||||||
}
|
}
|
||||||
|
item->setY(visualY - m_contentY);
|
||||||
|
}
|
||||||
|
|
||||||
item->setVisible(true);
|
item->setVisible(true);
|
||||||
auto* att =
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false));
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
if (att) {
|
if (att) {
|
||||||
att->setAdding(false);
|
att->setAdding(false);
|
||||||
att->setReady(true);
|
att->setReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate from visual position to layout position
|
// Animate from visual position to layout position
|
||||||
if (idx >= 0 && idx < static_cast<int>(m_layout.size()))
|
if (idx >= 0 && idx < static_cast<int>(m_layout.size()))
|
||||||
item->setProperty("y", m_layout[idx].targetY - m_contentY);
|
item->setProperty("y",
|
||||||
|
m_layout[idx].targetY - m_contentY);
|
||||||
|
|
||||||
polish();
|
polish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.pendingInsert = false;
|
entry.pendingInsert = false;
|
||||||
entry.item->setVisible(true);
|
entry.item->setVisible(true);
|
||||||
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
||||||
if (att) {
|
if (att) {
|
||||||
att->setAdding(false);
|
att->setAdding(false);
|
||||||
att->setReady(true);
|
att->setReady(true);
|
||||||
@@ -429,12 +426,13 @@ void LazyListView::updatePolish() {
|
|||||||
record.isNew = false;
|
record.isNew = false;
|
||||||
|
|
||||||
// Position delegates — QML Behavior on y handles the animation
|
// Position delegates — QML Behavior on y handles the animation
|
||||||
|
const int layoutSize = static_cast<int>(m_layout.size());
|
||||||
for (auto& entry : m_delegates) {
|
for (auto& entry : m_delegates) {
|
||||||
if (!entry.item || entry.pendingRemoval || entry.pendingInsert)
|
if (!entry.item || entry.pendingRemoval || entry.pendingInsert)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const int idx = entry.modelIndex;
|
const int idx = entry.modelIndex;
|
||||||
if (idx < 0 || idx >= static_cast<int>(m_layout.size()))
|
if (idx < 0 || idx >= layoutSize)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height))
|
if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height))
|
||||||
@@ -454,7 +452,8 @@ void LazyListView::relayout() {
|
|||||||
qreal y = 0;
|
qreal y = 0;
|
||||||
bool hasLayoutItem = false;
|
bool hasLayoutItem = false;
|
||||||
for (auto& record : m_layout) {
|
for (auto& record : m_layout) {
|
||||||
const qreal layoutH = record.heightKnown ? record.height : effectiveEstimatedHeight();
|
const qreal layoutH = record.heightKnown ? record.height
|
||||||
|
: effectiveEstimatedHeight();
|
||||||
if (layoutH > 0) {
|
if (layoutH > 0) {
|
||||||
if (hasLayoutItem)
|
if (hasLayoutItem)
|
||||||
y += m_spacing;
|
y += m_spacing;
|
||||||
@@ -476,12 +475,13 @@ void LazyListView::relayout() {
|
|||||||
qreal visY = 0;
|
qreal visY = 0;
|
||||||
bool hasVisItem = false;
|
bool hasVisItem = false;
|
||||||
for (int i = 0; i < static_cast<int>(m_layout.size()); ++i) {
|
for (int i = 0; i < static_cast<int>(m_layout.size()); ++i) {
|
||||||
qreal h;
|
qreal h = NAN;
|
||||||
auto dit = m_delegates.find(i);
|
auto dit = m_delegates.find(i);
|
||||||
if (dit != m_delegates.end() && dit->item)
|
if (dit != m_delegates.end() && dit->item)
|
||||||
h = delegateVisibleHeight(dit->item);
|
h = delegateVisibleHeight(dit->item);
|
||||||
else
|
else
|
||||||
h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight();
|
h = m_layout[i].heightKnown ? m_layout[i].height
|
||||||
|
: effectiveEstimatedHeight();
|
||||||
if (h > 0) {
|
if (h > 0) {
|
||||||
if (hasVisItem)
|
if (hasVisItem)
|
||||||
visY += m_spacing;
|
visY += m_spacing;
|
||||||
@@ -542,11 +542,11 @@ QRectF LazyListView::effectiveViewport() const {
|
|||||||
|
|
||||||
std::pair<int, int> LazyListView::computeVisibleRange() const {
|
std::pair<int, int> LazyListView::computeVisibleRange() const {
|
||||||
if (m_layout.isEmpty())
|
if (m_layout.isEmpty())
|
||||||
return { -1, -1 };
|
return {-1, -1};
|
||||||
|
|
||||||
const auto vp = effectiveViewport();
|
const auto vp = effectiveViewport();
|
||||||
if (vp.isEmpty())
|
if (vp.isEmpty())
|
||||||
return { -1, -1 };
|
return {-1, -1};
|
||||||
|
|
||||||
const qreal vpTop = vp.y();
|
const qreal vpTop = vp.y();
|
||||||
const qreal vpBottom = vp.y() + vp.height();
|
const qreal vpBottom = vp.y() + vp.height();
|
||||||
@@ -559,7 +559,9 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
|
|||||||
while (lo <= hi) {
|
while (lo <= hi) {
|
||||||
const int mid = lo + (hi - lo) / 2;
|
const int mid = lo + (hi - lo) / 2;
|
||||||
const auto& record = m_layout[mid];
|
const auto& record = m_layout[mid];
|
||||||
const qreal itemBottom = record.targetY + (record.heightKnown ? record.height : effectiveEstimatedHeight());
|
const qreal itemBottom =
|
||||||
|
record.targetY +
|
||||||
|
(record.heightKnown ? record.height : effectiveEstimatedHeight());
|
||||||
|
|
||||||
if (itemBottom >= vpTop) {
|
if (itemBottom >= vpTop) {
|
||||||
first = mid;
|
first = mid;
|
||||||
@@ -570,7 +572,7 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (first >= static_cast<int>(m_layout.size()))
|
if (first >= static_cast<int>(m_layout.size()))
|
||||||
return { -1, -1 };
|
return {-1, -1};
|
||||||
|
|
||||||
// Linear scan for last visible item
|
// Linear scan for last visible item
|
||||||
int last = first;
|
int last = first;
|
||||||
@@ -580,7 +582,7 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
|
|||||||
last = i;
|
last = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { first, last };
|
return {first, last};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Delegate Lifecycle ---
|
// --- Delegate Lifecycle ---
|
||||||
@@ -612,9 +614,12 @@ void LazyListView::syncDelegates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Batch destroy
|
// Batch destroy
|
||||||
const int destroyBudget = m_asynchronous ? ASYNC_BATCH_DESTROY : static_cast<int>(toRemove.size());
|
const int destroyBudget = m_asynchronous
|
||||||
|
? ASYNC_BATCH_DESTROY
|
||||||
|
: static_cast<int>(toRemove.size());
|
||||||
QVector<DelegateEntry> removedEntries;
|
QVector<DelegateEntry> removedEntries;
|
||||||
removedEntries.reserve(std::min(destroyBudget, static_cast<int>(toRemove.size())));
|
removedEntries.reserve(
|
||||||
|
std::min(destroyBudget, static_cast<int>(toRemove.size())));
|
||||||
int destroyed = 0;
|
int destroyed = 0;
|
||||||
for (int idx : toRemove) {
|
for (int idx : toRemove) {
|
||||||
if (destroyed >= destroyBudget)
|
if (destroyed >= destroyBudget)
|
||||||
@@ -638,7 +643,8 @@ void LazyListView::syncDelegates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Batch create
|
// Batch create
|
||||||
const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE : static_cast<int>(toCreate.size());
|
const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE
|
||||||
|
: static_cast<int>(toCreate.size());
|
||||||
int created = 0;
|
int created = 0;
|
||||||
for (int i : toCreate) {
|
for (int i : toCreate) {
|
||||||
if (created >= createBudget)
|
if (created >= createBudget)
|
||||||
@@ -658,8 +664,9 @@ void LazyListView::syncDelegates() {
|
|||||||
|
|
||||||
// Pending inserts need to become visible on the next frame, and
|
// Pending inserts need to become visible on the next frame, and
|
||||||
// async mode may have remaining create/destroy work.
|
// async mode may have remaining create/destroy work.
|
||||||
if (created > 0 || (m_asynchronous && (destroyed < static_cast<int>(toRemove.size()) ||
|
if (created > 0 ||
|
||||||
created < static_cast<int>(toCreate.size()))))
|
(m_asynchronous && (destroyed < static_cast<int>(toRemove.size()) ||
|
||||||
|
created < static_cast<int>(toCreate.size()))))
|
||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,8 +711,10 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
initialProps.insert(QStringLiteral("index"), modelIndex);
|
initialProps.insert(QStringLiteral("index"), modelIndex);
|
||||||
|
|
||||||
if (!hasModelData) {
|
if (!hasModelData) {
|
||||||
const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key();
|
const auto role = roleNames.isEmpty() ? Qt::DisplayRole
|
||||||
initialProps.insert(QStringLiteral("modelData"), m_model->data(index, role));
|
: roleNames.constBegin().key();
|
||||||
|
initialProps.insert(QStringLiteral("modelData"),
|
||||||
|
m_model->data(index, role));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_delegate->setInitialProperties(entry.item, initialProps);
|
m_delegate->setInitialProperties(entry.item, initialProps);
|
||||||
@@ -715,9 +724,10 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
|
|
||||||
// Only set adding = true for genuinely new model items (not viewport entries).
|
// Only set adding = true for genuinely new model items (not viewport entries).
|
||||||
// Cleared on the next frame in updatePolish when the item becomes visible.
|
// Cleared on the next frame in updatePolish when the item becomes visible.
|
||||||
if (modelIndex < static_cast<int>(m_layout.size()) && m_layout[modelIndex].isNew) {
|
if (modelIndex < static_cast<int>(m_layout.size()) &&
|
||||||
auto* addingAttached =
|
m_layout[modelIndex].isNew) {
|
||||||
qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, true));
|
auto* addingAttached = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, true));
|
||||||
if (addingAttached)
|
if (addingAttached)
|
||||||
addingAttached->setAdding(true);
|
addingAttached->setAdding(true);
|
||||||
}
|
}
|
||||||
@@ -730,84 +740,103 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
// Height-change handler — uses m_itemToIndex for O(1) lookup.
|
// Height-change handler — uses m_itemToIndex for O(1) lookup.
|
||||||
// Ignored while the delegate is not yet ready.
|
// Ignored while the delegate is not yet ready.
|
||||||
auto onHeightChanged = [this, item = entry.item] {
|
auto onHeightChanged = [this, item = entry.item] {
|
||||||
if (!isDelegateReady(item))
|
if (!isDelegateReady(item))
|
||||||
return;
|
return;
|
||||||
auto indexIt = m_itemToIndex.find(item);
|
auto indexIt = m_itemToIndex.find(item);
|
||||||
if (indexIt == m_itemToIndex.end())
|
if (indexIt == m_itemToIndex.end())
|
||||||
return;
|
return;
|
||||||
const int idx = indexIt.value();
|
const int idx = indexIt.value();
|
||||||
auto delegateIt = m_delegates.find(idx);
|
auto delegateIt = m_delegates.find(idx);
|
||||||
if (delegateIt == m_delegates.end() || delegateIt->item != item)
|
if (delegateIt == m_delegates.end() || delegateIt->item != item)
|
||||||
return;
|
return;
|
||||||
const qreal h = delegateHeight(item);
|
const qreal h = delegateHeight(item);
|
||||||
if (idx < static_cast<int>(m_layout.size()) && !qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) {
|
if (idx < static_cast<int>(m_layout.size()) &&
|
||||||
const qreal oldH = m_layout[idx].height;
|
!qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) {
|
||||||
const bool wasKnown = m_layout[idx].heightKnown;
|
const qreal oldH = m_layout[idx].height;
|
||||||
m_layout[idx].height = h;
|
const bool wasKnown = m_layout[idx].heightKnown;
|
||||||
m_layout[idx].heightKnown = true;
|
m_layout[idx].height = h;
|
||||||
if (wasKnown)
|
m_layout[idx].heightKnown = true;
|
||||||
untrackHeight(oldH);
|
if (wasKnown)
|
||||||
trackHeight(h);
|
untrackHeight(oldH);
|
||||||
|
trackHeight(h);
|
||||||
|
|
||||||
// If this tracked item is above the viewport, emit a
|
// If this tracked item is above the viewport, emit a
|
||||||
// compensation delta so the consumer can adjust scroll.
|
// compensation delta so the consumer can adjust scroll.
|
||||||
if (wasKnown) {
|
if (wasKnown) {
|
||||||
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false));
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
if (att && att->trackViewport()) {
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY;
|
if (att && att->trackViewport()) {
|
||||||
if (m_layout[idx].targetY < vpTop)
|
const qreal vpTop = m_useCustomViewport ? m_viewport.y()
|
||||||
emit viewportAdjustNeeded(h - oldH);
|
: m_contentY;
|
||||||
}
|
if (m_layout[idx].targetY < vpTop)
|
||||||
}
|
emit viewportAdjustNeeded(h - oldH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_relayoutPending) {
|
if (!m_relayoutPending) {
|
||||||
m_relayoutPending = true;
|
m_relayoutPending = true;
|
||||||
QTimer::singleShot(0, this, [this] {
|
QTimer::singleShot(0, this, [this] {
|
||||||
m_relayoutPending = false;
|
m_relayoutPending = false;
|
||||||
relayout();
|
relayout();
|
||||||
polish();
|
polish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Watch implicitHeight as fallback
|
// Watch implicitHeight as fallback
|
||||||
connect(entry.item, &QQuickItem::implicitHeightChanged, this, onHeightChanged);
|
connect(entry.item,
|
||||||
|
&QQuickItem::implicitHeightChanged,
|
||||||
|
this,
|
||||||
|
onHeightChanged);
|
||||||
|
|
||||||
// Watch attached properties if the delegate uses them
|
// Watch attached properties if the delegate uses them
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
||||||
if (attached) {
|
if (attached) {
|
||||||
connect(attached, &LazyListViewAttached::preferredHeightChanged, this, onHeightChanged);
|
connect(attached,
|
||||||
connect(attached, &LazyListViewAttached::visibleHeightChanged, this, [this] {
|
&LazyListViewAttached::preferredHeightChanged,
|
||||||
polish();
|
this,
|
||||||
});
|
onHeightChanged);
|
||||||
connect(attached, &LazyListViewAttached::readyChanged, this, [this, item = entry.item] {
|
connect(attached,
|
||||||
auto indexIt = m_itemToIndex.find(item);
|
&LazyListViewAttached::visibleHeightChanged,
|
||||||
if (indexIt == m_itemToIndex.end())
|
this,
|
||||||
return;
|
[this] { polish(); });
|
||||||
const int idx = indexIt.value();
|
connect(attached,
|
||||||
if (idx >= static_cast<int>(m_layout.size()))
|
&LazyListViewAttached::readyChanged,
|
||||||
return;
|
this,
|
||||||
auto* att = qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(item, false));
|
[this, item = entry.item] {
|
||||||
if (!att || !att->ready())
|
auto indexIt = m_itemToIndex.find(item);
|
||||||
return;
|
if (indexIt == m_itemToIndex.end())
|
||||||
|
return;
|
||||||
|
const int idx = indexIt.value();
|
||||||
|
if (idx >= static_cast<int>(m_layout.size()))
|
||||||
|
return;
|
||||||
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
|
if (!att || !att->ready())
|
||||||
|
return;
|
||||||
|
|
||||||
const qreal h = delegateHeight(item);
|
const qreal h = delegateHeight(item);
|
||||||
const qreal oldLayoutH = m_layout[idx].heightKnown ? m_layout[idx].height : effectiveEstimatedHeight();
|
const qreal oldLayoutH = m_layout[idx].heightKnown
|
||||||
if (m_layout[idx].heightKnown)
|
? m_layout[idx].height
|
||||||
untrackHeight(m_layout[idx].height);
|
: effectiveEstimatedHeight();
|
||||||
m_layout[idx].height = h;
|
if (m_layout[idx].heightKnown)
|
||||||
m_layout[idx].heightKnown = true;
|
untrackHeight(m_layout[idx].height);
|
||||||
trackHeight(h);
|
m_layout[idx].height = h;
|
||||||
|
m_layout[idx].heightKnown = true;
|
||||||
|
trackHeight(h);
|
||||||
|
|
||||||
if (att->trackViewport() && !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) {
|
if (att->trackViewport() &&
|
||||||
const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY;
|
!qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) {
|
||||||
if (m_layout[idx].targetY < vpTop)
|
const qreal vpTop = m_useCustomViewport ? m_viewport.y()
|
||||||
emit viewportAdjustNeeded(h - oldLayoutH);
|
: m_contentY;
|
||||||
}
|
if (m_layout[idx].targetY < vpTop)
|
||||||
|
emit viewportAdjustNeeded(h - oldLayoutH);
|
||||||
|
}
|
||||||
|
|
||||||
polish();
|
polish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
@@ -832,7 +861,8 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) {
|
|||||||
|
|
||||||
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
|
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
|
||||||
const auto name = QString::fromUtf8(it.value());
|
const auto name = QString::fromUtf8(it.value());
|
||||||
entry.item->setProperty(name.toUtf8().constData(), m_model->data(index, it.key()));
|
entry.item->setProperty(name.toUtf8().constData(),
|
||||||
|
m_model->data(index, it.key()));
|
||||||
if (name == QStringLiteral("modelData"))
|
if (name == QStringLiteral("modelData"))
|
||||||
hasModelData = true;
|
hasModelData = true;
|
||||||
}
|
}
|
||||||
@@ -840,7 +870,8 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) {
|
|||||||
entry.item->setProperty("index", entry.modelIndex);
|
entry.item->setProperty("index", entry.modelIndex);
|
||||||
|
|
||||||
if (!hasModelData) {
|
if (!hasModelData) {
|
||||||
const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key();
|
const auto role = roleNames.isEmpty() ? Qt::DisplayRole
|
||||||
|
: roleNames.constBegin().key();
|
||||||
entry.item->setProperty("modelData", m_model->data(index, role));
|
entry.item->setProperty("modelData", m_model->data(index, role));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -852,24 +883,46 @@ void LazyListView::connectModel() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_modelConnections = {
|
m_modelConnections = {
|
||||||
connect(m_model, &QAbstractItemModel::rowsInserted, this, &LazyListView::onRowsInserted),
|
connect(m_model,
|
||||||
connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &LazyListView::onRowsAboutToBeRemoved),
|
&QAbstractItemModel::rowsInserted,
|
||||||
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LazyListView::onRowsRemoved),
|
this,
|
||||||
connect(m_model, &QAbstractItemModel::rowsMoved, this, &LazyListView::onRowsMoved),
|
&LazyListView::onRowsInserted),
|
||||||
connect(m_model, &QAbstractItemModel::dataChanged, this, &LazyListView::onDataChanged),
|
connect(m_model,
|
||||||
connect(m_model, &QAbstractItemModel::modelReset, this, &LazyListView::onModelReset),
|
&QAbstractItemModel::rowsAboutToBeRemoved,
|
||||||
connect(m_model, &QAbstractItemModel::layoutChanged, this,
|
this,
|
||||||
[this] {
|
&LazyListView::onRowsAboutToBeRemoved),
|
||||||
for (auto& entry : m_delegates)
|
connect(m_model,
|
||||||
updateDelegateData(entry);
|
&QAbstractItemModel::rowsRemoved,
|
||||||
polish();
|
this,
|
||||||
}),
|
&LazyListView::onRowsRemoved),
|
||||||
connect(m_model, &QObject::destroyed, this,
|
connect(m_model,
|
||||||
[this] {
|
&QAbstractItemModel::rowsMoved,
|
||||||
m_model = nullptr;
|
this,
|
||||||
resetContent();
|
&LazyListView::onRowsMoved),
|
||||||
emit modelChanged();
|
connect(m_model,
|
||||||
}),
|
&QAbstractItemModel::dataChanged,
|
||||||
|
this,
|
||||||
|
&LazyListView::onDataChanged),
|
||||||
|
connect(m_model,
|
||||||
|
&QAbstractItemModel::modelReset,
|
||||||
|
this,
|
||||||
|
&LazyListView::onModelReset),
|
||||||
|
connect(m_model,
|
||||||
|
&QAbstractItemModel::layoutChanged,
|
||||||
|
this,
|
||||||
|
[this] {
|
||||||
|
for (auto& entry : m_delegates)
|
||||||
|
updateDelegateData(entry);
|
||||||
|
polish();
|
||||||
|
}),
|
||||||
|
connect(m_model,
|
||||||
|
&QObject::destroyed,
|
||||||
|
this,
|
||||||
|
[this] {
|
||||||
|
m_model = nullptr;
|
||||||
|
resetContent();
|
||||||
|
emit modelChanged();
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -909,13 +962,15 @@ void LazyListView::resetContent() {
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsInserted(const QModelIndex& parent, int first, int last) {
|
void LazyListView::onRowsInserted(const QModelIndex& parent,
|
||||||
|
int first,
|
||||||
|
int last) {
|
||||||
if (parent.isValid())
|
if (parent.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const int insertCount = last - first + 1;
|
const int insertCount = last - first + 1;
|
||||||
// Insert new layout records
|
// Insert new layout records
|
||||||
m_layout.insert(first, insertCount, ItemRecord{ 0, 0, false, true });
|
m_layout.insert(first, insertCount, ItemRecord{0, 0, false, true});
|
||||||
|
|
||||||
// Shift existing delegate indices
|
// Shift existing delegate indices
|
||||||
QHash<int, DelegateEntry> shifted;
|
QHash<int, DelegateEntry> shifted;
|
||||||
@@ -935,7 +990,9 @@ void LazyListView::onRowsInserted(const QModelIndex& parent, int first, int last
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) {
|
void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent,
|
||||||
|
int first,
|
||||||
|
int last) {
|
||||||
if (parent.isValid())
|
if (parent.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -955,22 +1012,24 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_removeDuration > 0 && entry.item) {
|
if (m_removeDuration > 0 && entry.item) {
|
||||||
auto* attached =
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
qobject_cast<LazyListViewAttached*>(qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
||||||
if (attached)
|
if (attached)
|
||||||
attached->setRemoving(true);
|
attached->setRemoving(true);
|
||||||
|
|
||||||
// Schedule destruction after the remove animation duration
|
// Schedule destruction after the remove animation duration
|
||||||
auto* item = entry.item;
|
auto* item = entry.item;
|
||||||
QTimer::singleShot(m_removeDuration, this, [this, item] {
|
QTimer::singleShot(m_removeDuration, this, [this, item] {
|
||||||
for (auto it = m_dyingDelegates.begin(); it != m_dyingDelegates.end(); ++it) {
|
for (auto it = m_dyingDelegates.begin();
|
||||||
if (it->item == item) {
|
it != m_dyingDelegates.end();
|
||||||
destroyDelegate(*it);
|
++it) {
|
||||||
m_dyingDelegates.erase(it);
|
if (it->item == item) {
|
||||||
return;
|
destroyDelegate(*it);
|
||||||
}
|
m_dyingDelegates.erase(it);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
m_dyingDelegates.append(std::move(entry));
|
m_dyingDelegates.append(std::move(entry));
|
||||||
} else {
|
} else {
|
||||||
destroyDelegate(entry);
|
destroyDelegate(entry);
|
||||||
@@ -978,7 +1037,9 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsRemoved(const QModelIndex& parent, int first, int last) {
|
void LazyListView::onRowsRemoved(const QModelIndex& parent,
|
||||||
|
int first,
|
||||||
|
int last) {
|
||||||
if (parent.isValid())
|
if (parent.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -1011,7 +1072,11 @@ void LazyListView::onRowsRemoved(const QModelIndex& parent, int first, int last)
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row) {
|
void LazyListView::onRowsMoved(const QModelIndex& parent,
|
||||||
|
int start,
|
||||||
|
int end,
|
||||||
|
const QModelIndex& destination,
|
||||||
|
int row) {
|
||||||
if (parent.isValid() || destination.isValid())
|
if (parent.isValid() || destination.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -1055,7 +1120,9 @@ void LazyListView::onRowsMoved(const QModelIndex& parent, int start, int end, co
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles) {
|
void LazyListView::onDataChanged(const QModelIndex& topLeft,
|
||||||
|
const QModelIndex& bottomRight,
|
||||||
|
const QList<int>& roles) {
|
||||||
Q_UNUSED(roles)
|
Q_UNUSED(roles)
|
||||||
|
|
||||||
if (topLeft.parent().isValid())
|
if (topLeft.parent().isValid())
|
||||||
@@ -1079,15 +1146,18 @@ void LazyListView::onModelReset() {
|
|||||||
// Check if the model data actually changed
|
// Check if the model data actually changed
|
||||||
if (newRows == oldRows) {
|
if (newRows == oldRows) {
|
||||||
const auto roleNames = m_model->roleNames();
|
const auto roleNames = m_model->roleNames();
|
||||||
const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key();
|
const auto role = roleNames.isEmpty() ? Qt::DisplayRole
|
||||||
|
: roleNames.constBegin().key();
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) {
|
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd();
|
||||||
|
++it) {
|
||||||
if (!it->item || it.key() >= newRows) {
|
if (!it->item || it.key() >= newRows) {
|
||||||
changed = true;
|
changed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const auto newData = m_model->data(m_model->index(it.key(), 0), role);
|
const auto newData =
|
||||||
|
m_model->data(m_model->index(it.key(), 0), role);
|
||||||
const auto oldData = it->item->property("modelData");
|
const auto oldData = it->item->property("modelData");
|
||||||
if (newData != oldData) {
|
if (newData != oldData) {
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|||||||
@@ -6,55 +6,52 @@
|
|||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
StrokeCanvasItem::StrokeCanvasItem(QQuickItem *parent) : QCanvasPainterItem(parent) {
|
StrokeCanvasItem::StrokeCanvasItem(QQuickItem* parent)
|
||||||
|
: QCanvasPainterItem(parent) {
|
||||||
setFillColor(Qt::transparent);
|
setFillColor(Qt::transparent);
|
||||||
setAlphaBlending(true);
|
setAlphaBlending(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QCanvasPainterItemRenderer *StrokeCanvasItem::createItemRenderer() const {
|
QCanvasPainterItemRenderer* StrokeCanvasItem::createItemRenderer() const {
|
||||||
return new StrokeCanvasRenderer;
|
return new StrokeCanvasRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool shouldAddPoint(
|
static bool shouldAddPoint(
|
||||||
const QVector<QPointF> &points,
|
const QVector<QPointF>& points, const QPointF& p, qreal minDistance) {
|
||||||
const QPointF &p,
|
if (points.isEmpty()) return true;
|
||||||
qreal minDistance)
|
|
||||||
{
|
|
||||||
if (points.isEmpty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const QPointF delta = p - points.last();
|
const QPointF delta = p - points.last();
|
||||||
|
|
||||||
return QPointF::dotProduct(delta, delta)
|
return QPointF::dotProduct(delta, delta) >= minDistance * minDistance;
|
||||||
>= minDistance * minDistance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static QCanvasPath buildStrokePath(const QVector<QPointF> &points, float width) {
|
static QCanvasPath buildStrokePath(const QVector<QPointF>& points, qreal width) {
|
||||||
QCanvasPath path;
|
QCanvasPath path;
|
||||||
|
|
||||||
if (points.size() == 1) {
|
if (points.size() == 1) {
|
||||||
path.circle(points[0], width * 0.5f);
|
path.circle(points[0], width * 0.5);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto catmullToBezier = [](
|
auto catmullToBezier = [](const QPointF& p0,
|
||||||
const QPointF &p0, const QPointF &p1,
|
const QPointF& p1,
|
||||||
const QPointF &p2, const QPointF &p3,
|
const QPointF& p2,
|
||||||
float tension,
|
const QPointF& p3,
|
||||||
QPointF &cp1, QPointF &cp2)
|
qreal tension,
|
||||||
{
|
QPointF& cp1,
|
||||||
cp1 = p1 + (p2 - p0) * tension / 3.0f;
|
QPointF& cp2) {
|
||||||
cp2 = p2 - (p3 - p1) * tension / 3.0f;
|
cp1 = p1 + (p2 - p0) * tension / 3.0;
|
||||||
};
|
cp2 = p2 - (p3 - p1) * tension / 3.0;
|
||||||
|
};
|
||||||
|
|
||||||
const float tension = 0.5f;
|
const qreal tension = 0.5;
|
||||||
path.moveTo(points[0]);
|
path.moveTo(points[0]);
|
||||||
|
|
||||||
for (int i = 0; i < points.size() - 1; ++i) {
|
for (int i = 0; i < points.size() - 1; ++i) {
|
||||||
const QPointF &p0 = points[qMax(i - 1, 0)];
|
const QPointF& p0 = points[qMax(i - 1, 0)];
|
||||||
const QPointF &p1 = points[i];
|
const QPointF& p1 = points[i];
|
||||||
const QPointF &p2 = points[i + 1];
|
const QPointF& p2 = points[i + 1];
|
||||||
const QPointF &p3 = points[qMin(i + 2, points.size() - 1)];
|
const QPointF& p3 = points[qMin(i + 2, points.size() - 1)];
|
||||||
|
|
||||||
QPointF cp1, cp2;
|
QPointF cp1, cp2;
|
||||||
catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2);
|
catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2);
|
||||||
@@ -64,9 +61,8 @@ static QCanvasPath buildStrokePath(const QVector<QPointF> &points, float width)
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::setPenColor(const QColor &color) {
|
void StrokeCanvasItem::setPenColor(const QColor& color) {
|
||||||
if (m_penColor == color)
|
if (m_penColor == color) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_penColor = color;
|
m_penColor = color;
|
||||||
update();
|
update();
|
||||||
@@ -75,8 +71,7 @@ void StrokeCanvasItem::setPenColor(const QColor &color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::setHoverVisible(bool visible) {
|
void StrokeCanvasItem::setHoverVisible(bool visible) {
|
||||||
if (m_hoverVisible == visible)
|
if (m_hoverVisible == visible) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_hoverVisible = visible;
|
m_hoverVisible = visible;
|
||||||
update();
|
update();
|
||||||
@@ -84,9 +79,8 @@ void StrokeCanvasItem::setHoverVisible(bool visible) {
|
|||||||
emit hoverVisibleChanged();
|
emit hoverVisibleChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::setHoverPoint(const QPointF &point) {
|
void StrokeCanvasItem::setHoverPoint(const QPointF& point) {
|
||||||
if (m_hoverPoint == point)
|
if (m_hoverPoint == point) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_hoverPoint = point;
|
m_hoverPoint = point;
|
||||||
update();
|
update();
|
||||||
@@ -110,8 +104,7 @@ void StrokeCanvasItem::showHover(qreal x, qreal y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::hideHover() {
|
void StrokeCanvasItem::hideHover() {
|
||||||
if (!m_hoverVisible)
|
if (!m_hoverVisible) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_hoverVisible = false;
|
m_hoverVisible = false;
|
||||||
update();
|
update();
|
||||||
@@ -120,8 +113,7 @@ void StrokeCanvasItem::hideHover() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::setPenWidth(float width) {
|
void StrokeCanvasItem::setPenWidth(float width) {
|
||||||
if (qFuzzyCompare(m_penWidth, width))
|
if (qFuzzyCompare(m_penWidth, width)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_penWidth = width;
|
m_penWidth = width;
|
||||||
update();
|
update();
|
||||||
@@ -143,8 +135,7 @@ void StrokeCanvasItem::beginStroke(qreal x, qreal y) {
|
|||||||
void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
|
void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
|
||||||
const QPointF incoming{x, y};
|
const QPointF incoming{x, y};
|
||||||
|
|
||||||
if (!shouldAddPoint(m_currentStroke.points, incoming, 2.0))
|
if (!shouldAddPoint(m_currentStroke.points, incoming, 2.0)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
QPointF smoothed;
|
QPointF smoothed;
|
||||||
if (m_currentStroke.points.isEmpty()) {
|
if (m_currentStroke.points.isEmpty()) {
|
||||||
@@ -159,7 +150,8 @@ void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
|
|||||||
constexpr qreal minAlpha = 0.1;
|
constexpr qreal minAlpha = 0.1;
|
||||||
constexpr qreal maxAlpha = 0.3;
|
constexpr qreal maxAlpha = 0.3;
|
||||||
|
|
||||||
const qreal t = std::clamp((dist - minDist) / (maxDist - minDist), 0.0, 1.0);
|
const qreal t =
|
||||||
|
std::clamp((dist - minDist) / (maxDist - minDist), 0.0, 1.0);
|
||||||
const qreal alpha = minAlpha + t * (maxAlpha - minAlpha);
|
const qreal alpha = minAlpha + t * (maxAlpha - minAlpha);
|
||||||
|
|
||||||
smoothed = last * (1.0 - alpha) + incoming * alpha;
|
smoothed = last * (1.0 - alpha) + incoming * alpha;
|
||||||
@@ -171,11 +163,11 @@ void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
|
|||||||
|
|
||||||
void StrokeCanvasItem::endStroke() {
|
void StrokeCanvasItem::endStroke() {
|
||||||
m_isDrawing = false;
|
m_isDrawing = false;
|
||||||
if (m_currentStroke.points.isEmpty())
|
if (m_currentStroke.points.isEmpty()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_currentStroke.isSinglePoint = (m_currentStroke.points.size() == 1);
|
m_currentStroke.isSinglePoint = (m_currentStroke.points.size() == 1);
|
||||||
m_currentStroke.path = buildStrokePath(m_currentStroke.points, m_currentStroke.width);
|
m_currentStroke.path =
|
||||||
|
buildStrokePath(m_currentStroke.points, m_currentStroke.width);
|
||||||
m_currentStroke.groupId = m_nextGroupId++;
|
m_currentStroke.groupId = m_nextGroupId++;
|
||||||
m_currentStroke.points.clear();
|
m_currentStroke.points.clear();
|
||||||
m_strokes.append(m_currentStroke);
|
m_strokes.append(m_currentStroke);
|
||||||
@@ -190,4 +182,4 @@ void StrokeCanvasItem::clear() {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}; // namespace ZShell::internal
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
#include <QtConcurrent/qtconcurrentrun.h>
|
#include <QtConcurrent/qtconcurrentrun.h>
|
||||||
#include <QtQuick/qquickitemgrabresult.h>
|
#include <QtQuick/qquickitemgrabresult.h>
|
||||||
#include <QtQuick/qquickwindow.h>
|
#include <QtQuick/qquickwindow.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qfuturewatcher.h>
|
#include <qfuturewatcher.h>
|
||||||
|
#include <qjsprimitivevalue.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
|
|
||||||
@@ -142,4 +144,16 @@ qreal ZUtils::clamp(qreal value, qreal min, qreal max) {
|
|||||||
return qBound(min, value, max);
|
return qBound(min, value, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef ZSHELL_VERSION
|
||||||
|
#define ZSHELL_VERSION ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString ZUtils::version() const {
|
||||||
|
return QStringLiteral(ZSHELL_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ZUtils::qtVersion() const {
|
||||||
|
return QStringLiteral(QT_VERSION_STR);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QtQuick/qquickitem.h>
|
#include <QtQuick/qquickitem.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ Q_INVOKABLE static bool deleteFile(const QUrl& path);
|
|||||||
Q_INVOKABLE static QString toLocalFile(const QUrl& url);
|
Q_INVOKABLE static QString toLocalFile(const QUrl& url);
|
||||||
|
|
||||||
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
|
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
|
||||||
|
|
||||||
|
[[nodiscard]] QString version() const;
|
||||||
|
[[nodiscard]] QString qtVersion() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
FROM archlinux:latest
|
||||||
|
|
||||||
|
RUN pacman -Syu --noconfirm
|
||||||
|
|
||||||
|
# Build toolchain
|
||||||
|
RUN pacman -S --noconfirm \
|
||||||
|
base-devel \
|
||||||
|
cmake \
|
||||||
|
ninja \
|
||||||
|
clang \
|
||||||
|
lld \
|
||||||
|
nodejs \
|
||||||
|
git
|
||||||
|
|
||||||
|
# Qt6
|
||||||
|
RUN pacman -S --noconfirm \
|
||||||
|
qt6-base \
|
||||||
|
qt6-declarative \
|
||||||
|
qt6-shadertools \
|
||||||
|
qt6-5compat \
|
||||||
|
qt6-canvaspainter
|
||||||
|
|
||||||
|
# Project-specific dependencies
|
||||||
|
RUN pacman -S --noconfirm \
|
||||||
|
libqalculate \
|
||||||
|
pipewire \
|
||||||
|
aubio \
|
||||||
|
cava \
|
||||||
|
lm_sensors \
|
||||||
|
glib2
|
||||||
|
|
||||||
|
# AUR: libcava (provides development headers for cava)
|
||||||
|
RUN pacman -S --needed --noconfirm sudo && \
|
||||||
|
useradd -m builder && \
|
||||||
|
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \
|
||||||
|
su builder -c " \
|
||||||
|
git clone https://aur.archlinux.org/libcava.git /tmp/libcava && \
|
||||||
|
cd /tmp/libcava && \
|
||||||
|
makepkg -si --noconfirm \
|
||||||
|
" && \
|
||||||
|
rm -rf /tmp/libcava && \
|
||||||
|
userdel -r builder && \
|
||||||
|
sed -i '/^builder/d' /etc/sudoers
|
||||||
Reference in New Issue
Block a user