Initial commit
14 files changed +6706 −0
- + CMakeLists.txt
- + COPYING
- + COPYING.LESSER
- + LICENSE
- + README.md
- + cmake/Qml6StretchConfig.cmake.in
- + cmake/QtQmlInstallLayout.cmake
- + qml/StretchListView.qml
- + src/stretchflickable.cpp
- + src/stretchflickable_p.h
- + src/stretchflickable_p_p.h
- + src/stretchflickablebehavior_p.h
- + src/stretchtimeline.cpp
- + src/stretchtimeline_p_p.h
| 1 | commit e2b4771717dae3fe135a8191c76b5e4e7cbe29d6 |
| 2 | Author: Sander <sander@sanderf.nl> |
| 3 | Date: Fri Sep 4 16:44:51 2026 +0300 |
| 4 | |
| 5 | Initial commit |
| 6 | |
| 7 | diff --git a/CMakeLists.txt b/CMakeLists.txt |
| 8 | new file mode 100644 |
| 9 | index 0000000..d8d4421 |
| 10 | --- /dev/null |
| 11 | +++ b/CMakeLists.txt |
| 12 | @@ -0,0 +1,156 @@ |
| 13 | +cmake_minimum_required(VERSION 3.21) |
| 14 | + |
| 15 | +project(Qml6Stretch |
| 16 | + VERSION 1.0.0 |
| 17 | + DESCRIPTION "Drag-resistant Flickable and ListView-alike QML components for Qt6" |
| 18 | + LANGUAGES CXX |
| 19 | +) |
| 20 | + |
| 21 | +set(CMAKE_CXX_STANDARD 20) |
| 22 | +set(CMAKE_CXX_STANDARD_REQUIRED ON) |
| 23 | + |
| 24 | +find_program(CCACHE_PROGRAM ccache) |
| 25 | +if(CCACHE_PROGRAM) |
| 26 | + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") |
| 27 | + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") |
| 28 | +endif() |
| 29 | + |
| 30 | +set(CMAKE_AUTOMOC ON) |
| 31 | + |
| 32 | +include(CMakePackageConfigHelpers) |
| 33 | + |
| 34 | +find_package(Qt6 6.5 REQUIRED COMPONENTS |
| 35 | + Core |
| 36 | + Gui |
| 37 | + Qml |
| 38 | + Quick |
| 39 | + CorePrivate |
| 40 | + GuiPrivate |
| 41 | + QmlPrivate |
| 42 | + QuickPrivate |
| 43 | +) |
| 44 | + |
| 45 | +qt_standard_project_setup() |
| 46 | + |
| 47 | +qt_policy(SET QTP0001 NEW) |
| 48 | +qt_policy(SET QTP0004 NEW) |
| 49 | + |
| 50 | +# Derives the install prefix and QML module destination from the Qt being built |
| 51 | +# against, so the module lands in Qt's own import dir. Includes GNUInstallDirs. |
| 52 | +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") |
| 53 | +include(QtQmlInstallLayout) |
| 54 | + |
| 55 | +qt6_add_qml_module(qml6stretch |
| 56 | + URI Qml6Stretch |
| 57 | + VERSION 1.0 |
| 58 | + SHARED |
| 59 | + PLUGIN_TARGET qml6stretchplugin |
| 60 | + OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml/Qml6Stretch" |
| 61 | + SOURCES |
| 62 | + src/stretchflickable_p.h |
| 63 | + src/stretchflickable_p_p.h |
| 64 | + src/stretchflickable.cpp |
| 65 | + src/stretchflickablebehavior_p.h |
| 66 | + src/stretchtimeline_p_p.h |
| 67 | + src/stretchtimeline.cpp |
| 68 | + QML_FILES |
| 69 | + qml/StretchListView.qml |
| 70 | +) |
| 71 | + |
| 72 | +set_target_properties(qml6stretch PROPERTIES |
| 73 | + VERSION ${PROJECT_VERSION} |
| 74 | + SOVERSION ${PROJECT_VERSION_MAJOR} |
| 75 | +) |
| 76 | + |
| 77 | +# qt_standard_project_setup() assumes a one-level-deep layout ($ORIGIN/../lib); |
| 78 | +# the plugin lives deeper, under Qt's qml dir. |
| 79 | +qt_qml_plugin_rpath(_plugin_rpath Qml6Stretch) |
| 80 | +set_target_properties(qml6stretchplugin PROPERTIES INSTALL_RPATH "${_plugin_rpath}") |
| 81 | + |
| 82 | +target_include_directories(qml6stretch PUBLIC |
| 83 | + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> |
| 84 | + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/qml6stretch> |
| 85 | +) |
| 86 | + |
| 87 | +target_link_libraries(qml6stretch PUBLIC |
| 88 | + Qt6::Core |
| 89 | + Qt6::Gui |
| 90 | + Qt6::Qml |
| 91 | + Qt6::Quick |
| 92 | + Qt6::CorePrivate |
| 93 | + Qt6::GuiPrivate |
| 94 | + Qt6::QmlPrivate |
| 95 | + Qt6::QuickPrivate |
| 96 | +) |
| 97 | + |
| 98 | +# ---- Installation --------------------------------------------------------- |
| 99 | + |
| 100 | +install(FILES |
| 101 | + src/stretchflickable_p.h |
| 102 | + src/stretchflickable_p_p.h |
| 103 | + src/stretchflickablebehavior_p.h |
| 104 | + src/stretchtimeline_p_p.h |
| 105 | + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qml6stretch |
| 106 | +) |
| 107 | + |
| 108 | +install(TARGETS qml6stretch |
| 109 | + EXPORT Qml6StretchTargets |
| 110 | + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| 111 | + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| 112 | + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} |
| 113 | + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} |
| 114 | +) |
| 115 | + |
| 116 | +install(TARGETS qml6stretchplugin |
| 117 | + EXPORT Qml6StretchTargets |
| 118 | + LIBRARY DESTINATION ${QT_QML_MODULE_INSTALL_DIR}/Qml6Stretch |
| 119 | + RUNTIME DESTINATION ${QT_QML_MODULE_INSTALL_DIR}/Qml6Stretch |
| 120 | +) |
| 121 | + |
| 122 | +# qmldir records QML_FILES by their relative on-disk path (e.g. |
| 123 | +# "qml/StretchListView.qml"). They're also embedded as a resource inside |
| 124 | +# qml6stretchplugin, but Qt's static-plugin import scanner (qt6_import_qml_plugins, |
| 125 | +# used when consuming apps link Qt statically) still opens that path on disk |
| 126 | +# at configure time, so the loose file has to ship alongside qmldir too. |
| 127 | +install(FILES |
| 128 | + qml/StretchListView.qml |
| 129 | + DESTINATION ${QT_QML_MODULE_INSTALL_DIR}/Qml6Stretch/qml |
| 130 | +) |
| 131 | + |
| 132 | +install(FILES |
| 133 | + "${CMAKE_BINARY_DIR}/qml/Qml6Stretch/qmldir" |
| 134 | + DESTINATION ${QT_QML_MODULE_INSTALL_DIR}/Qml6Stretch |
| 135 | +) |
| 136 | + |
| 137 | +install(FILES |
| 138 | + "${CMAKE_BINARY_DIR}/qml/Qml6Stretch/qml6stretch.qmltypes" |
| 139 | + DESTINATION ${QT_QML_MODULE_INSTALL_DIR}/Qml6Stretch |
| 140 | + OPTIONAL |
| 141 | +) |
| 142 | + |
| 143 | +# ---- CMake package config for find_package() ------------------------------ |
| 144 | + |
| 145 | +install(EXPORT Qml6StretchTargets |
| 146 | + FILE Qml6StretchTargets.cmake |
| 147 | + NAMESPACE Qml6Stretch:: |
| 148 | + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qml6Stretch |
| 149 | +) |
| 150 | + |
| 151 | +configure_package_config_file( |
| 152 | + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Qml6StretchConfig.cmake.in" |
| 153 | + "${CMAKE_CURRENT_BINARY_DIR}/Qml6StretchConfig.cmake" |
| 154 | + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qml6Stretch |
| 155 | + PATH_VARS QT_QML_MODULE_INSTALL_FULL_DIR |
| 156 | +) |
| 157 | + |
| 158 | +write_basic_package_version_file( |
| 159 | + "${CMAKE_CURRENT_BINARY_DIR}/Qml6StretchConfigVersion.cmake" |
| 160 | + VERSION ${PROJECT_VERSION} |
| 161 | + COMPATIBILITY SameMajorVersion |
| 162 | +) |
| 163 | + |
| 164 | +install(FILES |
| 165 | + "${CMAKE_CURRENT_BINARY_DIR}/Qml6StretchConfig.cmake" |
| 166 | + "${CMAKE_CURRENT_BINARY_DIR}/Qml6StretchConfigVersion.cmake" |
| 167 | + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qml6Stretch |
| 168 | +) |
| 169 | diff --git a/COPYING b/COPYING |
| 170 | new file mode 100644 |
| 171 | index 0000000..f288702 |
| 172 | --- /dev/null |
| 173 | +++ b/COPYING |
| 174 | @@ -0,0 +1,674 @@ |
| 175 | + GNU GENERAL PUBLIC LICENSE |
| 176 | + Version 3, 29 June 2007 |
| 177 | + |
| 178 | + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |
| 179 | + Everyone is permitted to copy and distribute verbatim copies |
| 180 | + of this license document, but changing it is not allowed. |
| 181 | + |
| 182 | + Preamble |
| 183 | + |
| 184 | + The GNU General Public License is a free, copyleft license for |
| 185 | +software and other kinds of works. |
| 186 | + |
| 187 | + The licenses for most software and other practical works are designed |
| 188 | +to take away your freedom to share and change the works. By contrast, |
| 189 | +the GNU General Public License is intended to guarantee your freedom to |
| 190 | +share and change all versions of a program--to make sure it remains free |
| 191 | +software for all its users. We, the Free Software Foundation, use the |
| 192 | +GNU General Public License for most of our software; it applies also to |
| 193 | +any other work released this way by its authors. You can apply it to |
| 194 | +your programs, too. |
| 195 | + |
| 196 | + When we speak of free software, we are referring to freedom, not |
| 197 | +price. Our General Public Licenses are designed to make sure that you |
| 198 | +have the freedom to distribute copies of free software (and charge for |
| 199 | +them if you wish), that you receive source code or can get it if you |
| 200 | +want it, that you can change the software or use pieces of it in new |
| 201 | +free programs, and that you know you can do these things. |
| 202 | + |
| 203 | + To protect your rights, we need to prevent others from denying you |
| 204 | +these rights or asking you to surrender the rights. Therefore, you have |
| 205 | +certain responsibilities if you distribute copies of the software, or if |
| 206 | +you modify it: responsibilities to respect the freedom of others. |
| 207 | + |
| 208 | + For example, if you distribute copies of such a program, whether |
| 209 | +gratis or for a fee, you must pass on to the recipients the same |
| 210 | +freedoms that you received. You must make sure that they, too, receive |
| 211 | +or can get the source code. And you must show them these terms so they |
| 212 | +know their rights. |
| 213 | + |
| 214 | + Developers that use the GNU GPL protect your rights with two steps: |
| 215 | +(1) assert copyright on the software, and (2) offer you this License |
| 216 | +giving you legal permission to copy, distribute and/or modify it. |
| 217 | + |
| 218 | + For the developers' and authors' protection, the GPL clearly explains |
| 219 | +that there is no warranty for this free software. For both users' and |
| 220 | +authors' sake, the GPL requires that modified versions be marked as |
| 221 | +changed, so that their problems will not be attributed erroneously to |
| 222 | +authors of previous versions. |
| 223 | + |
| 224 | + Some devices are designed to deny users access to install or run |
| 225 | +modified versions of the software inside them, although the manufacturer |
| 226 | +can do so. This is fundamentally incompatible with the aim of |
| 227 | +protecting users' freedom to change the software. The systematic |
| 228 | +pattern of such abuse occurs in the area of products for individuals to |
| 229 | +use, which is precisely where it is most unacceptable. Therefore, we |
| 230 | +have designed this version of the GPL to prohibit the practice for those |
| 231 | +products. If such problems arise substantially in other domains, we |
| 232 | +stand ready to extend this provision to those domains in future versions |
| 233 | +of the GPL, as needed to protect the freedom of users. |
| 234 | + |
| 235 | + Finally, every program is threatened constantly by software patents. |
| 236 | +States should not allow patents to restrict development and use of |
| 237 | +software on general-purpose computers, but in those that do, we wish to |
| 238 | +avoid the special danger that patents applied to a free program could |
| 239 | +make it effectively proprietary. To prevent this, the GPL assures that |
| 240 | +patents cannot be used to render the program non-free. |
| 241 | + |
| 242 | + The precise terms and conditions for copying, distribution and |
| 243 | +modification follow. |
| 244 | + |
| 245 | + TERMS AND CONDITIONS |
| 246 | + |
| 247 | + 0. Definitions. |
| 248 | + |
| 249 | + "This License" refers to version 3 of the GNU General Public License. |
| 250 | + |
| 251 | + "Copyright" also means copyright-like laws that apply to other kinds of |
| 252 | +works, such as semiconductor masks. |
| 253 | + |
| 254 | + "The Program" refers to any copyrightable work licensed under this |
| 255 | +License. Each licensee is addressed as "you". "Licensees" and |
| 256 | +"recipients" may be individuals or organizations. |
| 257 | + |
| 258 | + To "modify" a work means to copy from or adapt all or part of the work |
| 259 | +in a fashion requiring copyright permission, other than the making of an |
| 260 | +exact copy. The resulting work is called a "modified version" of the |
| 261 | +earlier work or a work "based on" the earlier work. |
| 262 | + |
| 263 | + A "covered work" means either the unmodified Program or a work based |
| 264 | +on the Program. |
| 265 | + |
| 266 | + To "propagate" a work means to do anything with it that, without |
| 267 | +permission, would make you directly or secondarily liable for |
| 268 | +infringement under applicable copyright law, except executing it on a |
| 269 | +computer or modifying a private copy. Propagation includes copying, |
| 270 | +distribution (with or without modification), making available to the |
| 271 | +public, and in some countries other activities as well. |
| 272 | + |
| 273 | + To "convey" a work means any kind of propagation that enables other |
| 274 | +parties to make or receive copies. Mere interaction with a user through |
| 275 | +a computer network, with no transfer of a copy, is not conveying. |
| 276 | + |
| 277 | + An interactive user interface displays "Appropriate Legal Notices" |
| 278 | +to the extent that it includes a convenient and prominently visible |
| 279 | +feature that (1) displays an appropriate copyright notice, and (2) |
| 280 | +tells the user that there is no warranty for the work (except to the |
| 281 | +extent that warranties are provided), that licensees may convey the |
| 282 | +work under this License, and how to view a copy of this License. If |
| 283 | +the interface presents a list of user commands or options, such as a |
| 284 | +menu, a prominent item in the list meets this criterion. |
| 285 | + |
| 286 | + 1. Source Code. |
| 287 | + |
| 288 | + The "source code" for a work means the preferred form of the work |
| 289 | +for making modifications to it. "Object code" means any non-source |
| 290 | +form of a work. |
| 291 | + |
| 292 | + A "Standard Interface" means an interface that either is an official |
| 293 | +standard defined by a recognized standards body, or, in the case of |
| 294 | +interfaces specified for a particular programming language, one that |
| 295 | +is widely used among developers working in that language. |
| 296 | + |
| 297 | + The "System Libraries" of an executable work include anything, other |
| 298 | +than the work as a whole, that (a) is included in the normal form of |
| 299 | +packaging a Major Component, but which is not part of that Major |
| 300 | +Component, and (b) serves only to enable use of the work with that |
| 301 | +Major Component, or to implement a Standard Interface for which an |
| 302 | +implementation is available to the public in source code form. A |
| 303 | +"Major Component", in this context, means a major essential component |
| 304 | +(kernel, window system, and so on) of the specific operating system |
| 305 | +(if any) on which the executable work runs, or a compiler used to |
| 306 | +produce the work, or an object code interpreter used to run it. |
| 307 | + |
| 308 | + The "Corresponding Source" for a work in object code form means all |
| 309 | +the source code needed to generate, install, and (for an executable |
| 310 | +work) run the object code and to modify the work, including scripts to |
| 311 | +control those activities. However, it does not include the work's |
| 312 | +System Libraries, or general-purpose tools or generally available free |
| 313 | +programs which are used unmodified in performing those activities but |
| 314 | +which are not part of the work. For example, Corresponding Source |
| 315 | +includes interface definition files associated with source files for |
| 316 | +the work, and the source code for shared libraries and dynamically |
| 317 | +linked subprograms that the work is specifically designed to require, |
| 318 | +such as by intimate data communication or control flow between those |
| 319 | +subprograms and other parts of the work. |
| 320 | + |
| 321 | + The Corresponding Source need not include anything that users |
| 322 | +can regenerate automatically from other parts of the Corresponding |
| 323 | +Source. |
| 324 | + |
| 325 | + The Corresponding Source for a work in source code form is that |
| 326 | +same work. |
| 327 | + |
| 328 | + 2. Basic Permissions. |
| 329 | + |
| 330 | + All rights granted under this License are granted for the term of |
| 331 | +copyright on the Program, and are irrevocable provided the stated |
| 332 | +conditions are met. This License explicitly affirms your unlimited |
| 333 | +permission to run the unmodified Program. The output from running a |
| 334 | +covered work is covered by this License only if the output, given its |
| 335 | +content, constitutes a covered work. This License acknowledges your |
| 336 | +rights of fair use or other equivalent, as provided by copyright law. |
| 337 | + |
| 338 | + You may make, run and propagate covered works that you do not |
| 339 | +convey, without conditions so long as your license otherwise remains |
| 340 | +in force. You may convey covered works to others for the sole purpose |
| 341 | +of having them make modifications exclusively for you, or provide you |
| 342 | +with facilities for running those works, provided that you comply with |
| 343 | +the terms of this License in conveying all material for which you do |
| 344 | +not control copyright. Those thus making or running the covered works |
| 345 | +for you must do so exclusively on your behalf, under your direction |
| 346 | +and control, on terms that prohibit them from making any copies of |
| 347 | +your copyrighted material outside their relationship with you. |
| 348 | + |
| 349 | + Conveying under any other circumstances is permitted solely under |
| 350 | +the conditions stated below. Sublicensing is not allowed; section 10 |
| 351 | +makes it unnecessary. |
| 352 | + |
| 353 | + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
| 354 | + |
| 355 | + No covered work shall be deemed part of an effective technological |
| 356 | +measure under any applicable law fulfilling obligations under article |
| 357 | +11 of the WIPO copyright treaty adopted on 20 December 1996, or |
| 358 | +similar laws prohibiting or restricting circumvention of such |
| 359 | +measures. |
| 360 | + |
| 361 | + When you convey a covered work, you waive any legal power to forbid |
| 362 | +circumvention of technological measures to the extent such circumvention |
| 363 | +is effected by exercising rights under this License with respect to |
| 364 | +the covered work, and you disclaim any intention to limit operation or |
| 365 | +modification of the work as a means of enforcing, against the work's |
| 366 | +users, your or third parties' legal rights to forbid circumvention of |
| 367 | +technological measures. |
| 368 | + |
| 369 | + 4. Conveying Verbatim Copies. |
| 370 | + |
| 371 | + You may convey verbatim copies of the Program's source code as you |
| 372 | +receive it, in any medium, provided that you conspicuously and |
| 373 | +appropriately publish on each copy an appropriate copyright notice; |
| 374 | +keep intact all notices stating that this License and any |
| 375 | +non-permissive terms added in accord with section 7 apply to the code; |
| 376 | +keep intact all notices of the absence of any warranty; and give all |
| 377 | +recipients a copy of this License along with the Program. |
| 378 | + |
| 379 | + You may charge any price or no price for each copy that you convey, |
| 380 | +and you may offer support or warranty protection for a fee. |
| 381 | + |
| 382 | + 5. Conveying Modified Source Versions. |
| 383 | + |
| 384 | + You may convey a work based on the Program, or the modifications to |
| 385 | +produce it from the Program, in the form of source code under the |
| 386 | +terms of section 4, provided that you also meet all of these conditions: |
| 387 | + |
| 388 | + a) The work must carry prominent notices stating that you modified |
| 389 | + it, and giving a relevant date. |
| 390 | + |
| 391 | + b) The work must carry prominent notices stating that it is |
| 392 | + released under this License and any conditions added under section |
| 393 | + 7. This requirement modifies the requirement in section 4 to |
| 394 | + "keep intact all notices". |
| 395 | + |
| 396 | + c) You must license the entire work, as a whole, under this |
| 397 | + License to anyone who comes into possession of a copy. This |
| 398 | + License will therefore apply, along with any applicable section 7 |
| 399 | + additional terms, to the whole of the work, and all its parts, |
| 400 | + regardless of how they are packaged. This License gives no |
| 401 | + permission to license the work in any other way, but it does not |
| 402 | + invalidate such permission if you have separately received it. |
| 403 | + |
| 404 | + d) If the work has interactive user interfaces, each must display |
| 405 | + Appropriate Legal Notices; however, if the Program has interactive |
| 406 | + interfaces that do not display Appropriate Legal Notices, your |
| 407 | + work need not make them do so. |
| 408 | + |
| 409 | + A compilation of a covered work with other separate and independent |
| 410 | +works, which are not by their nature extensions of the covered work, |
| 411 | +and which are not combined with it such as to form a larger program, |
| 412 | +in or on a volume of a storage or distribution medium, is called an |
| 413 | +"aggregate" if the compilation and its resulting copyright are not |
| 414 | +used to limit the access or legal rights of the compilation's users |
| 415 | +beyond what the individual works permit. Inclusion of a covered work |
| 416 | +in an aggregate does not cause this License to apply to the other |
| 417 | +parts of the aggregate. |
| 418 | + |
| 419 | + 6. Conveying Non-Source Forms. |
| 420 | + |
| 421 | + You may convey a covered work in object code form under the terms |
| 422 | +of sections 4 and 5, provided that you also convey the |
| 423 | +machine-readable Corresponding Source under the terms of this License, |
| 424 | +in one of these ways: |
| 425 | + |
| 426 | + a) Convey the object code in, or embodied in, a physical product |
| 427 | + (including a physical distribution medium), accompanied by the |
| 428 | + Corresponding Source fixed on a durable physical medium |
| 429 | + customarily used for software interchange. |
| 430 | + |
| 431 | + b) Convey the object code in, or embodied in, a physical product |
| 432 | + (including a physical distribution medium), accompanied by a |
| 433 | + written offer, valid for at least three years and valid for as |
| 434 | + long as you offer spare parts or customer support for that product |
| 435 | + model, to give anyone who possesses the object code either (1) a |
| 436 | + copy of the Corresponding Source for all the software in the |
| 437 | + product that is covered by this License, on a durable physical |
| 438 | + medium customarily used for software interchange, for a price no |
| 439 | + more than your reasonable cost of physically performing this |
| 440 | + conveying of source, or (2) access to copy the |
| 441 | + Corresponding Source from a network server at no charge. |
| 442 | + |
| 443 | + c) Convey individual copies of the object code with a copy of the |
| 444 | + written offer to provide the Corresponding Source. This |
| 445 | + alternative is allowed only occasionally and noncommercially, and |
| 446 | + only if you received the object code with such an offer, in accord |
| 447 | + with subsection 6b. |
| 448 | + |
| 449 | + d) Convey the object code by offering access from a designated |
| 450 | + place (gratis or for a charge), and offer equivalent access to the |
| 451 | + Corresponding Source in the same way through the same place at no |
| 452 | + further charge. You need not require recipients to copy the |
| 453 | + Corresponding Source along with the object code. If the place to |
| 454 | + copy the object code is a network server, the Corresponding Source |
| 455 | + may be on a different server (operated by you or a third party) |
| 456 | + that supports equivalent copying facilities, provided you maintain |
| 457 | + clear directions next to the object code saying where to find the |
| 458 | + Corresponding Source. Regardless of what server hosts the |
| 459 | + Corresponding Source, you remain obligated to ensure that it is |
| 460 | + available for as long as needed to satisfy these requirements. |
| 461 | + |
| 462 | + e) Convey the object code using peer-to-peer transmission, provided |
| 463 | + you inform other peers where the object code and Corresponding |
| 464 | + Source of the work are being offered to the general public at no |
| 465 | + charge under subsection 6d. |
| 466 | + |
| 467 | + A separable portion of the object code, whose source code is excluded |
| 468 | +from the Corresponding Source as a System Library, need not be |
| 469 | +included in conveying the object code work. |
| 470 | + |
| 471 | + A "User Product" is either (1) a "consumer product", which means any |
| 472 | +tangible personal property which is normally used for personal, family, |
| 473 | +or household purposes, or (2) anything designed or sold for incorporation |
| 474 | +into a dwelling. In determining whether a product is a consumer product, |
| 475 | +doubtful cases shall be resolved in favor of coverage. For a particular |
| 476 | +product received by a particular user, "normally used" refers to a |
| 477 | +typical or common use of that class of product, regardless of the status |
| 478 | +of the particular user or of the way in which the particular user |
| 479 | +actually uses, or expects or is expected to use, the product. A product |
| 480 | +is a consumer product regardless of whether the product has substantial |
| 481 | +commercial, industrial or non-consumer uses, unless such uses represent |
| 482 | +the only significant mode of use of the product. |
| 483 | + |
| 484 | + "Installation Information" for a User Product means any methods, |
| 485 | +procedures, authorization keys, or other information required to install |
| 486 | +and execute modified versions of a covered work in that User Product from |
| 487 | +a modified version of its Corresponding Source. The information must |
| 488 | +suffice to ensure that the continued functioning of the modified object |
| 489 | +code is in no case prevented or interfered with solely because |
| 490 | +modification has been made. |
| 491 | + |
| 492 | + If you convey an object code work under this section in, or with, or |
| 493 | +specifically for use in, a User Product, and the conveying occurs as |
| 494 | +part of a transaction in which the right of possession and use of the |
| 495 | +User Product is transferred to the recipient in perpetuity or for a |
| 496 | +fixed term (regardless of how the transaction is characterized), the |
| 497 | +Corresponding Source conveyed under this section must be accompanied |
| 498 | +by the Installation Information. But this requirement does not apply |
| 499 | +if neither you nor any third party retains the ability to install |
| 500 | +modified object code on the User Product (for example, the work has |
| 501 | +been installed in ROM). |
| 502 | + |
| 503 | + The requirement to provide Installation Information does not include a |
| 504 | +requirement to continue to provide support service, warranty, or updates |
| 505 | +for a work that has been modified or installed by the recipient, or for |
| 506 | +the User Product in which it has been modified or installed. Access to a |
| 507 | +network may be denied when the modification itself materially and |
| 508 | +adversely affects the operation of the network or violates the rules and |
| 509 | +protocols for communication across the network. |
| 510 | + |
| 511 | + Corresponding Source conveyed, and Installation Information provided, |
| 512 | +in accord with this section must be in a format that is publicly |
| 513 | +documented (and with an implementation available to the public in |
| 514 | +source code form), and must require no special password or key for |
| 515 | +unpacking, reading or copying. |
| 516 | + |
| 517 | + 7. Additional Terms. |
| 518 | + |
| 519 | + "Additional permissions" are terms that supplement the terms of this |
| 520 | +License by making exceptions from one or more of its conditions. |
| 521 | +Additional permissions that are applicable to the entire Program shall |
| 522 | +be treated as though they were included in this License, to the extent |
| 523 | +that they are valid under applicable law. If additional permissions |
| 524 | +apply only to part of the Program, that part may be used separately |
| 525 | +under those permissions, but the entire Program remains governed by |
| 526 | +this License without regard to the additional permissions. |
| 527 | + |
| 528 | + When you convey a copy of a covered work, you may at your option |
| 529 | +remove any additional permissions from that copy, or from any part of |
| 530 | +it. (Additional permissions may be written to require their own |
| 531 | +removal in certain cases when you modify the work.) You may place |
| 532 | +additional permissions on material, added by you to a covered work, |
| 533 | +for which you have or can give appropriate copyright permission. |
| 534 | + |
| 535 | + Notwithstanding any other provision of this License, for material you |
| 536 | +add to a covered work, you may (if authorized by the copyright holders of |
| 537 | +that material) supplement the terms of this License with terms: |
| 538 | + |
| 539 | + a) Disclaiming warranty or limiting liability differently from the |
| 540 | + terms of sections 15 and 16 of this License; or |
| 541 | + |
| 542 | + b) Requiring preservation of specified reasonable legal notices or |
| 543 | + author attributions in that material or in the Appropriate Legal |
| 544 | + Notices displayed by works containing it; or |
| 545 | + |
| 546 | + c) Prohibiting misrepresentation of the origin of that material, or |
| 547 | + requiring that modified versions of such material be marked in |
| 548 | + reasonable ways as different from the original version; or |
| 549 | + |
| 550 | + d) Limiting the use for publicity purposes of names of licensors or |
| 551 | + authors of the material; or |
| 552 | + |
| 553 | + e) Declining to grant rights under trademark law for use of some |
| 554 | + trade names, trademarks, or service marks; or |
| 555 | + |
| 556 | + f) Requiring indemnification of licensors and authors of that |
| 557 | + material by anyone who conveys the material (or modified versions of |
| 558 | + it) with contractual assumptions of liability to the recipient, for |
| 559 | + any liability that these contractual assumptions directly impose on |
| 560 | + those licensors and authors. |
| 561 | + |
| 562 | + All other non-permissive additional terms are considered "further |
| 563 | +restrictions" within the meaning of section 10. If the Program as you |
| 564 | +received it, or any part of it, contains a notice stating that it is |
| 565 | +governed by this License along with a term that is a further |
| 566 | +restriction, you may remove that term. If a license document contains |
| 567 | +a further restriction but permits relicensing or conveying under this |
| 568 | +License, you may add to a covered work material governed by the terms |
| 569 | +of that license document, provided that the further restriction does |
| 570 | +not survive such relicensing or conveying. |
| 571 | + |
| 572 | + If you add terms to a covered work in accord with this section, you |
| 573 | +must place, in the relevant source files, a statement of the |
| 574 | +additional terms that apply to those files, or a notice indicating |
| 575 | +where to find the applicable terms. |
| 576 | + |
| 577 | + Additional terms, permissive or non-permissive, may be stated in the |
| 578 | +form of a separately written license, or stated as exceptions; |
| 579 | +the above requirements apply either way. |
| 580 | + |
| 581 | + 8. Termination. |
| 582 | + |
| 583 | + You may not propagate or modify a covered work except as expressly |
| 584 | +provided under this License. Any attempt otherwise to propagate or |
| 585 | +modify it is void, and will automatically terminate your rights under |
| 586 | +this License (including any patent licenses granted under the third |
| 587 | +paragraph of section 11). |
| 588 | + |
| 589 | + However, if you cease all violation of this License, then your |
| 590 | +license from a particular copyright holder is reinstated (a) |
| 591 | +provisionally, unless and until the copyright holder explicitly and |
| 592 | +finally terminates your license, and (b) permanently, if the copyright |
| 593 | +holder fails to notify you of the violation by some reasonable means |
| 594 | +prior to 60 days after the cessation. |
| 595 | + |
| 596 | + Moreover, your license from a particular copyright holder is |
| 597 | +reinstated permanently if the copyright holder notifies you of the |
| 598 | +violation by some reasonable means, this is the first time you have |
| 599 | +received notice of violation of this License (for any work) from that |
| 600 | +copyright holder, and you cure the violation prior to 30 days after |
| 601 | +your receipt of the notice. |
| 602 | + |
| 603 | + Termination of your rights under this section does not terminate the |
| 604 | +licenses of parties who have received copies or rights from you under |
| 605 | +this License. If your rights have been terminated and not permanently |
| 606 | +reinstated, you do not qualify to receive new licenses for the same |
| 607 | +material under section 10. |
| 608 | + |
| 609 | + 9. Acceptance Not Required for Having Copies. |
| 610 | + |
| 611 | + You are not required to accept this License in order to receive or |
| 612 | +run a copy of the Program. Ancillary propagation of a covered work |
| 613 | +occurring solely as a consequence of using peer-to-peer transmission |
| 614 | +to receive a copy likewise does not require acceptance. However, |
| 615 | +nothing other than this License grants you permission to propagate or |
| 616 | +modify any covered work. These actions infringe copyright if you do |
| 617 | +not accept this License. Therefore, by modifying or propagating a |
| 618 | +covered work, you indicate your acceptance of this License to do so. |
| 619 | + |
| 620 | + 10. Automatic Licensing of Downstream Recipients. |
| 621 | + |
| 622 | + Each time you convey a covered work, the recipient automatically |
| 623 | +receives a license from the original licensors, to run, modify and |
| 624 | +propagate that work, subject to this License. You are not responsible |
| 625 | +for enforcing compliance by third parties with this License. |
| 626 | + |
| 627 | + An "entity transaction" is a transaction transferring control of an |
| 628 | +organization, or substantially all assets of one, or subdividing an |
| 629 | +organization, or merging organizations. If propagation of a covered |
| 630 | +work results from an entity transaction, each party to that |
| 631 | +transaction who receives a copy of the work also receives whatever |
| 632 | +licenses to the work the party's predecessor in interest had or could |
| 633 | +give under the previous paragraph, plus a right to possession of the |
| 634 | +Corresponding Source of the work from the predecessor in interest, if |
| 635 | +the predecessor has it or can get it with reasonable efforts. |
| 636 | + |
| 637 | + You may not impose any further restrictions on the exercise of the |
| 638 | +rights granted or affirmed under this License. For example, you may |
| 639 | +not impose a license fee, royalty, or other charge for exercise of |
| 640 | +rights granted under this License, and you may not initiate litigation |
| 641 | +(including a cross-claim or counterclaim in a lawsuit) alleging that |
| 642 | +any patent claim is infringed by making, using, selling, offering for |
| 643 | +sale, or importing the Program or any portion of it. |
| 644 | + |
| 645 | + 11. Patents. |
| 646 | + |
| 647 | + A "contributor" is a copyright holder who authorizes use under this |
| 648 | +License of the Program or a work on which the Program is based. The |
| 649 | +work thus licensed is called the contributor's "contributor version". |
| 650 | + |
| 651 | + A contributor's "essential patent claims" are all patent claims |
| 652 | +owned or controlled by the contributor, whether already acquired or |
| 653 | +hereafter acquired, that would be infringed by some manner, permitted |
| 654 | +by this License, of making, using, or selling its contributor version, |
| 655 | +but do not include claims that would be infringed only as a |
| 656 | +consequence of further modification of the contributor version. For |
| 657 | +purposes of this definition, "control" includes the right to grant |
| 658 | +patent sublicenses in a manner consistent with the requirements of |
| 659 | +this License. |
| 660 | + |
| 661 | + Each contributor grants you a non-exclusive, worldwide, royalty-free |
| 662 | +patent license under the contributor's essential patent claims, to |
| 663 | +make, use, sell, offer for sale, import and otherwise run, modify and |
| 664 | +propagate the contents of its contributor version. |
| 665 | + |
| 666 | + In the following three paragraphs, a "patent license" is any express |
| 667 | +agreement or commitment, however denominated, not to enforce a patent |
| 668 | +(such as an express permission to practice a patent or covenant not to |
| 669 | +sue for patent infringement). To "grant" such a patent license to a |
| 670 | +party means to make such an agreement or commitment not to enforce a |
| 671 | +patent against the party. |
| 672 | + |
| 673 | + If you convey a covered work, knowingly relying on a patent license, |
| 674 | +and the Corresponding Source of the work is not available for anyone |
| 675 | +to copy, free of charge and under the terms of this License, through a |
| 676 | +publicly available network server or other readily accessible means, |
| 677 | +then you must either (1) cause the Corresponding Source to be so |
| 678 | +available, or (2) arrange to deprive yourself of the benefit of the |
| 679 | +patent license for this particular work, or (3) arrange, in a manner |
| 680 | +consistent with the requirements of this License, to extend the patent |
| 681 | +license to downstream recipients. "Knowingly relying" means you have |
| 682 | +actual knowledge that, but for the patent license, your conveying the |
| 683 | +covered work in a country, or your recipient's use of the covered work |
| 684 | +in a country, would infringe one or more identifiable patents in that |
| 685 | +country that you have reason to believe are valid. |
| 686 | + |
| 687 | + If, pursuant to or in connection with a single transaction or |
| 688 | +arrangement, you convey, or propagate by procuring conveyance of, a |
| 689 | +covered work, and grant a patent license to some of the parties |
| 690 | +receiving the covered work authorizing them to use, propagate, modify |
| 691 | +or convey a specific copy of the covered work, then the patent license |
| 692 | +you grant is automatically extended to all recipients of the covered |
| 693 | +work and works based on it. |
| 694 | + |
| 695 | + A patent license is "discriminatory" if it does not include within |
| 696 | +the scope of its coverage, prohibits the exercise of, or is |
| 697 | +conditioned on the non-exercise of one or more of the rights that are |
| 698 | +specifically granted under this License. You may not convey a covered |
| 699 | +work if you are a party to an arrangement with a third party that is |
| 700 | +in the business of distributing software, under which you make payment |
| 701 | +to the third party based on the extent of your activity of conveying |
| 702 | +the work, and under which the third party grants, to any of the |
| 703 | +parties who would receive the covered work from you, a discriminatory |
| 704 | +patent license (a) in connection with copies of the covered work |
| 705 | +conveyed by you (or copies made from those copies), or (b) primarily |
| 706 | +for and in connection with specific products or compilations that |
| 707 | +contain the covered work, unless you entered into that arrangement, |
| 708 | +or that patent license was granted, prior to 28 March 2007. |
| 709 | + |
| 710 | + Nothing in this License shall be construed as excluding or limiting |
| 711 | +any implied license or other defenses to infringement that may |
| 712 | +otherwise be available to you under applicable patent law. |
| 713 | + |
| 714 | + 12. No Surrender of Others' Freedom. |
| 715 | + |
| 716 | + If conditions are imposed on you (whether by court order, agreement or |
| 717 | +otherwise) that contradict the conditions of this License, they do not |
| 718 | +excuse you from the conditions of this License. If you cannot convey a |
| 719 | +covered work so as to satisfy simultaneously your obligations under this |
| 720 | +License and any other pertinent obligations, then as a consequence you may |
| 721 | +not convey it at all. For example, if you agree to terms that obligate you |
| 722 | +to collect a royalty for further conveying from those to whom you convey |
| 723 | +the Program, the only way you could satisfy both those terms and this |
| 724 | +License would be to refrain entirely from conveying the Program. |
| 725 | + |
| 726 | + 13. Use with the GNU Affero General Public License. |
| 727 | + |
| 728 | + Notwithstanding any other provision of this License, you have |
| 729 | +permission to link or combine any covered work with a work licensed |
| 730 | +under version 3 of the GNU Affero General Public License into a single |
| 731 | +combined work, and to convey the resulting work. The terms of this |
| 732 | +License will continue to apply to the part which is the covered work, |
| 733 | +but the special requirements of the GNU Affero General Public License, |
| 734 | +section 13, concerning interaction through a network will apply to the |
| 735 | +combination as such. |
| 736 | + |
| 737 | + 14. Revised Versions of this License. |
| 738 | + |
| 739 | + The Free Software Foundation may publish revised and/or new versions of |
| 740 | +the GNU General Public License from time to time. Such new versions will |
| 741 | +be similar in spirit to the present version, but may differ in detail to |
| 742 | +address new problems or concerns. |
| 743 | + |
| 744 | + Each version is given a distinguishing version number. If the |
| 745 | +Program specifies that a certain numbered version of the GNU General |
| 746 | +Public License "or any later version" applies to it, you have the |
| 747 | +option of following the terms and conditions either of that numbered |
| 748 | +version or of any later version published by the Free Software |
| 749 | +Foundation. If the Program does not specify a version number of the |
| 750 | +GNU General Public License, you may choose any version ever published |
| 751 | +by the Free Software Foundation. |
| 752 | + |
| 753 | + If the Program specifies that a proxy can decide which future |
| 754 | +versions of the GNU General Public License can be used, that proxy's |
| 755 | +public statement of acceptance of a version permanently authorizes you |
| 756 | +to choose that version for the Program. |
| 757 | + |
| 758 | + Later license versions may give you additional or different |
| 759 | +permissions. However, no additional obligations are imposed on any |
| 760 | +author or copyright holder as a result of your choosing to follow a |
| 761 | +later version. |
| 762 | + |
| 763 | + 15. Disclaimer of Warranty. |
| 764 | + |
| 765 | + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
| 766 | +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
| 767 | +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
| 768 | +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
| 769 | +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 770 | +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
| 771 | +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
| 772 | +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
| 773 | + |
| 774 | + 16. Limitation of Liability. |
| 775 | + |
| 776 | + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
| 777 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
| 778 | +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
| 779 | +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
| 780 | +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
| 781 | +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
| 782 | +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
| 783 | +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
| 784 | +SUCH DAMAGES. |
| 785 | + |
| 786 | + 17. Interpretation of Sections 15 and 16. |
| 787 | + |
| 788 | + If the disclaimer of warranty and limitation of liability provided |
| 789 | +above cannot be given local legal effect according to their terms, |
| 790 | +reviewing courts shall apply local law that most closely approximates |
| 791 | +an absolute waiver of all civil liability in connection with the |
| 792 | +Program, unless a warranty or assumption of liability accompanies a |
| 793 | +copy of the Program in return for a fee. |
| 794 | + |
| 795 | + END OF TERMS AND CONDITIONS |
| 796 | + |
| 797 | + How to Apply These Terms to Your New Programs |
| 798 | + |
| 799 | + If you develop a new program, and you want it to be of the greatest |
| 800 | +possible use to the public, the best way to achieve this is to make it |
| 801 | +free software which everyone can redistribute and change under these terms. |
| 802 | + |
| 803 | + To do so, attach the following notices to the program. It is safest |
| 804 | +to attach them to the start of each source file to most effectively |
| 805 | +state the exclusion of warranty; and each file should have at least |
| 806 | +the "copyright" line and a pointer to where the full notice is found. |
| 807 | + |
| 808 | + <one line to give the program's name and a brief idea of what it does.> |
| 809 | + Copyright (C) <year> <name of author> |
| 810 | + |
| 811 | + This program is free software: you can redistribute it and/or modify |
| 812 | + it under the terms of the GNU General Public License as published by |
| 813 | + the Free Software Foundation, either version 3 of the License, or |
| 814 | + (at your option) any later version. |
| 815 | + |
| 816 | + This program is distributed in the hope that it will be useful, |
| 817 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 818 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 819 | + GNU General Public License for more details. |
| 820 | + |
| 821 | + You should have received a copy of the GNU General Public License |
| 822 | + along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 823 | + |
| 824 | +Also add information on how to contact you by electronic and paper mail. |
| 825 | + |
| 826 | + If the program does terminal interaction, make it output a short |
| 827 | +notice like this when it starts in an interactive mode: |
| 828 | + |
| 829 | + <program> Copyright (C) <year> <name of author> |
| 830 | + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
| 831 | + This is free software, and you are welcome to redistribute it |
| 832 | + under certain conditions; type `show c' for details. |
| 833 | + |
| 834 | +The hypothetical commands `show w' and `show c' should show the appropriate |
| 835 | +parts of the General Public License. Of course, your program's commands |
| 836 | +might be different; for a GUI interface, you would use an "about box". |
| 837 | + |
| 838 | + You should also get your employer (if you work as a programmer) or school, |
| 839 | +if any, to sign a "copyright disclaimer" for the program, if necessary. |
| 840 | +For more information on this, and how to apply and follow the GNU GPL, see |
| 841 | +<https://www.gnu.org/licenses/>. |
| 842 | + |
| 843 | + The GNU General Public License does not permit incorporating your program |
| 844 | +into proprietary programs. If your program is a subroutine library, you |
| 845 | +may consider it more useful to permit linking proprietary applications with |
| 846 | +the library. If this is what you want to do, use the GNU Lesser General |
| 847 | +Public License instead of this License. But first, please read |
| 848 | +<https://www.gnu.org/licenses/why-not-lgpl.html>. |
| 849 | diff --git a/COPYING.LESSER b/COPYING.LESSER |
| 850 | new file mode 100644 |
| 851 | index 0000000..0a04128 |
| 852 | --- /dev/null |
| 853 | +++ b/COPYING.LESSER |
| 854 | @@ -0,0 +1,165 @@ |
| 855 | + GNU LESSER GENERAL PUBLIC LICENSE |
| 856 | + Version 3, 29 June 2007 |
| 857 | + |
| 858 | + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |
| 859 | + Everyone is permitted to copy and distribute verbatim copies |
| 860 | + of this license document, but changing it is not allowed. |
| 861 | + |
| 862 | + |
| 863 | + This version of the GNU Lesser General Public License incorporates |
| 864 | +the terms and conditions of version 3 of the GNU General Public |
| 865 | +License, supplemented by the additional permissions listed below. |
| 866 | + |
| 867 | + 0. Additional Definitions. |
| 868 | + |
| 869 | + As used herein, "this License" refers to version 3 of the GNU Lesser |
| 870 | +General Public License, and the "GNU GPL" refers to version 3 of the GNU |
| 871 | +General Public License. |
| 872 | + |
| 873 | + "The Library" refers to a covered work governed by this License, |
| 874 | +other than an Application or a Combined Work as defined below. |
| 875 | + |
| 876 | + An "Application" is any work that makes use of an interface provided |
| 877 | +by the Library, but which is not otherwise based on the Library. |
| 878 | +Defining a subclass of a class defined by the Library is deemed a mode |
| 879 | +of using an interface provided by the Library. |
| 880 | + |
| 881 | + A "Combined Work" is a work produced by combining or linking an |
| 882 | +Application with the Library. The particular version of the Library |
| 883 | +with which the Combined Work was made is also called the "Linked |
| 884 | +Version". |
| 885 | + |
| 886 | + The "Minimal Corresponding Source" for a Combined Work means the |
| 887 | +Corresponding Source for the Combined Work, excluding any source code |
| 888 | +for portions of the Combined Work that, considered in isolation, are |
| 889 | +based on the Application, and not on the Linked Version. |
| 890 | + |
| 891 | + The "Corresponding Application Code" for a Combined Work means the |
| 892 | +object code and/or source code for the Application, including any data |
| 893 | +and utility programs needed for reproducing the Combined Work from the |
| 894 | +Application, but excluding the System Libraries of the Combined Work. |
| 895 | + |
| 896 | + 1. Exception to Section 3 of the GNU GPL. |
| 897 | + |
| 898 | + You may convey a covered work under sections 3 and 4 of this License |
| 899 | +without being bound by section 3 of the GNU GPL. |
| 900 | + |
| 901 | + 2. Conveying Modified Versions. |
| 902 | + |
| 903 | + If you modify a copy of the Library, and, in your modifications, a |
| 904 | +facility refers to a function or data to be supplied by an Application |
| 905 | +that uses the facility (other than as an argument passed when the |
| 906 | +facility is invoked), then you may convey a copy of the modified |
| 907 | +version: |
| 908 | + |
| 909 | + a) under this License, provided that you make a good faith effort to |
| 910 | + ensure that, in the event an Application does not supply the |
| 911 | + function or data, the facility still operates, and performs |
| 912 | + whatever part of its purpose remains meaningful, or |
| 913 | + |
| 914 | + b) under the GNU GPL, with none of the additional permissions of |
| 915 | + this License applicable to that copy. |
| 916 | + |
| 917 | + 3. Object Code Incorporating Material from Library Header Files. |
| 918 | + |
| 919 | + The object code form of an Application may incorporate material from |
| 920 | +a header file that is part of the Library. You may convey such object |
| 921 | +code under terms of your choice, provided that, if the incorporated |
| 922 | +material is not limited to numerical parameters, data structure |
| 923 | +layouts and accessors, or small macros, inline functions and templates |
| 924 | +(ten or fewer lines in length), you do both of the following: |
| 925 | + |
| 926 | + a) Give prominent notice with each copy of the object code that the |
| 927 | + Library is used in it and that the Library and its use are |
| 928 | + covered by this License. |
| 929 | + |
| 930 | + b) Accompany the object code with a copy of the GNU GPL and this license |
| 931 | + document. |
| 932 | + |
| 933 | + 4. Combined Works. |
| 934 | + |
| 935 | + You may convey a Combined Work under terms of your choice that, |
| 936 | +taken together, effectively do not restrict modification of the |
| 937 | +portions of the Library contained in the Combined Work and reverse |
| 938 | +engineering for debugging such modifications, if you also do each of |
| 939 | +the following: |
| 940 | + |
| 941 | + a) Give prominent notice with each copy of the Combined Work that |
| 942 | + the Library is used in it and that the Library and its use are |
| 943 | + covered by this License. |
| 944 | + |
| 945 | + b) Accompany the Combined Work with a copy of the GNU GPL and this license |
| 946 | + document. |
| 947 | + |
| 948 | + c) For a Combined Work that displays copyright notices during |
| 949 | + execution, include the copyright notice for the Library among |
| 950 | + these notices, as well as a reference directing the user to the |
| 951 | + copies of the GNU GPL and this license document. |
| 952 | + |
| 953 | + d) Do one of the following: |
| 954 | + |
| 955 | + 0) Convey the Minimal Corresponding Source under the terms of this |
| 956 | + License, and the Corresponding Application Code in a form |
| 957 | + suitable for, and under terms that permit, the user to |
| 958 | + recombine or relink the Application with a modified version of |
| 959 | + the Linked Version to produce a modified Combined Work, in the |
| 960 | + manner specified by section 6 of the GNU GPL for conveying |
| 961 | + Corresponding Source. |
| 962 | + |
| 963 | + 1) Use a suitable shared library mechanism for linking with the |
| 964 | + Library. A suitable mechanism is one that (a) uses at run time |
| 965 | + a copy of the Library already present on the user's computer |
| 966 | + system, and (b) will operate properly with a modified version |
| 967 | + of the Library that is interface-compatible with the Linked |
| 968 | + Version. |
| 969 | + |
| 970 | + e) Provide Installation Information, but only if you would otherwise |
| 971 | + be required to provide such information under section 6 of the |
| 972 | + GNU GPL, and only to the extent that such information is |
| 973 | + necessary to install and execute a modified version of the |
| 974 | + Combined Work produced by recombining or relinking the |
| 975 | + Application with a modified version of the Linked Version. (If |
| 976 | + you use option 4d0, the Installation Information must accompany |
| 977 | + the Minimal Corresponding Source and Corresponding Application |
| 978 | + Code. If you use option 4d1, you must provide the Installation |
| 979 | + Information in the manner specified by section 6 of the GNU GPL |
| 980 | + for conveying Corresponding Source.) |
| 981 | + |
| 982 | + 5. Combined Libraries. |
| 983 | + |
| 984 | + You may place library facilities that are a work based on the |
| 985 | +Library side by side in a single library together with other library |
| 986 | +facilities that are not Applications and are not covered by this |
| 987 | +License, and convey such a combined library under terms of your |
| 988 | +choice, if you do both of the following: |
| 989 | + |
| 990 | + a) Accompany the combined library with a copy of the same work based |
| 991 | + on the Library, uncombined with any other library facilities, |
| 992 | + conveyed under the terms of this License. |
| 993 | + |
| 994 | + b) Give prominent notice with the combined library that part of it |
| 995 | + is a work based on the Library, and explaining where to find the |
| 996 | + accompanying uncombined form of the same work. |
| 997 | + |
| 998 | + 6. Revised Versions of the GNU Lesser General Public License. |
| 999 | + |
| 1000 | + The Free Software Foundation may publish revised and/or new versions |
| 1001 | +of the GNU Lesser General Public License from time to time. Such new |
| 1002 | +versions will be similar in spirit to the present version, but may |
| 1003 | +differ in detail to address new problems or concerns. |
| 1004 | + |
| 1005 | + Each version is given a distinguishing version number. If the |
| 1006 | +Library as you received it specifies that a certain numbered version |
| 1007 | +of the GNU Lesser General Public License "or any later version" |
| 1008 | +applies to it, you have the option of following the terms and |
| 1009 | +conditions either of that published version or of any later version |
| 1010 | +published by the Free Software Foundation. If the Library as you |
| 1011 | +received it does not specify a version number of the GNU Lesser |
| 1012 | +General Public License, you may choose any version of the GNU Lesser |
| 1013 | +General Public License ever published by the Free Software Foundation. |
| 1014 | + |
| 1015 | + If the Library as you received it specifies that a proxy can decide |
| 1016 | +whether future versions of the GNU Lesser General Public License shall |
| 1017 | +apply, that proxy's public statement of acceptance of any version is |
| 1018 | +permanent authorization for you to choose that version for the |
| 1019 | +Library. |
| 1020 | diff --git a/LICENSE b/LICENSE |
| 1021 | new file mode 100644 |
| 1022 | index 0000000..0a04128 |
| 1023 | --- /dev/null |
| 1024 | +++ b/LICENSE |
| 1025 | @@ -0,0 +1,165 @@ |
| 1026 | + GNU LESSER GENERAL PUBLIC LICENSE |
| 1027 | + Version 3, 29 June 2007 |
| 1028 | + |
| 1029 | + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |
| 1030 | + Everyone is permitted to copy and distribute verbatim copies |
| 1031 | + of this license document, but changing it is not allowed. |
| 1032 | + |
| 1033 | + |
| 1034 | + This version of the GNU Lesser General Public License incorporates |
| 1035 | +the terms and conditions of version 3 of the GNU General Public |
| 1036 | +License, supplemented by the additional permissions listed below. |
| 1037 | + |
| 1038 | + 0. Additional Definitions. |
| 1039 | + |
| 1040 | + As used herein, "this License" refers to version 3 of the GNU Lesser |
| 1041 | +General Public License, and the "GNU GPL" refers to version 3 of the GNU |
| 1042 | +General Public License. |
| 1043 | + |
| 1044 | + "The Library" refers to a covered work governed by this License, |
| 1045 | +other than an Application or a Combined Work as defined below. |
| 1046 | + |
| 1047 | + An "Application" is any work that makes use of an interface provided |
| 1048 | +by the Library, but which is not otherwise based on the Library. |
| 1049 | +Defining a subclass of a class defined by the Library is deemed a mode |
| 1050 | +of using an interface provided by the Library. |
| 1051 | + |
| 1052 | + A "Combined Work" is a work produced by combining or linking an |
| 1053 | +Application with the Library. The particular version of the Library |
| 1054 | +with which the Combined Work was made is also called the "Linked |
| 1055 | +Version". |
| 1056 | + |
| 1057 | + The "Minimal Corresponding Source" for a Combined Work means the |
| 1058 | +Corresponding Source for the Combined Work, excluding any source code |
| 1059 | +for portions of the Combined Work that, considered in isolation, are |
| 1060 | +based on the Application, and not on the Linked Version. |
| 1061 | + |
| 1062 | + The "Corresponding Application Code" for a Combined Work means the |
| 1063 | +object code and/or source code for the Application, including any data |
| 1064 | +and utility programs needed for reproducing the Combined Work from the |
| 1065 | +Application, but excluding the System Libraries of the Combined Work. |
| 1066 | + |
| 1067 | + 1. Exception to Section 3 of the GNU GPL. |
| 1068 | + |
| 1069 | + You may convey a covered work under sections 3 and 4 of this License |
| 1070 | +without being bound by section 3 of the GNU GPL. |
| 1071 | + |
| 1072 | + 2. Conveying Modified Versions. |
| 1073 | + |
| 1074 | + If you modify a copy of the Library, and, in your modifications, a |
| 1075 | +facility refers to a function or data to be supplied by an Application |
| 1076 | +that uses the facility (other than as an argument passed when the |
| 1077 | +facility is invoked), then you may convey a copy of the modified |
| 1078 | +version: |
| 1079 | + |
| 1080 | + a) under this License, provided that you make a good faith effort to |
| 1081 | + ensure that, in the event an Application does not supply the |
| 1082 | + function or data, the facility still operates, and performs |
| 1083 | + whatever part of its purpose remains meaningful, or |
| 1084 | + |
| 1085 | + b) under the GNU GPL, with none of the additional permissions of |
| 1086 | + this License applicable to that copy. |
| 1087 | + |
| 1088 | + 3. Object Code Incorporating Material from Library Header Files. |
| 1089 | + |
| 1090 | + The object code form of an Application may incorporate material from |
| 1091 | +a header file that is part of the Library. You may convey such object |
| 1092 | +code under terms of your choice, provided that, if the incorporated |
| 1093 | +material is not limited to numerical parameters, data structure |
| 1094 | +layouts and accessors, or small macros, inline functions and templates |
| 1095 | +(ten or fewer lines in length), you do both of the following: |
| 1096 | + |
| 1097 | + a) Give prominent notice with each copy of the object code that the |
| 1098 | + Library is used in it and that the Library and its use are |
| 1099 | + covered by this License. |
| 1100 | + |
| 1101 | + b) Accompany the object code with a copy of the GNU GPL and this license |
| 1102 | + document. |
| 1103 | + |
| 1104 | + 4. Combined Works. |
| 1105 | + |
| 1106 | + You may convey a Combined Work under terms of your choice that, |
| 1107 | +taken together, effectively do not restrict modification of the |
| 1108 | +portions of the Library contained in the Combined Work and reverse |
| 1109 | +engineering for debugging such modifications, if you also do each of |
| 1110 | +the following: |
| 1111 | + |
| 1112 | + a) Give prominent notice with each copy of the Combined Work that |
| 1113 | + the Library is used in it and that the Library and its use are |
| 1114 | + covered by this License. |
| 1115 | + |
| 1116 | + b) Accompany the Combined Work with a copy of the GNU GPL and this license |
| 1117 | + document. |
| 1118 | + |
| 1119 | + c) For a Combined Work that displays copyright notices during |
| 1120 | + execution, include the copyright notice for the Library among |
| 1121 | + these notices, as well as a reference directing the user to the |
| 1122 | + copies of the GNU GPL and this license document. |
| 1123 | + |
| 1124 | + d) Do one of the following: |
| 1125 | + |
| 1126 | + 0) Convey the Minimal Corresponding Source under the terms of this |
| 1127 | + License, and the Corresponding Application Code in a form |
| 1128 | + suitable for, and under terms that permit, the user to |
| 1129 | + recombine or relink the Application with a modified version of |
| 1130 | + the Linked Version to produce a modified Combined Work, in the |
| 1131 | + manner specified by section 6 of the GNU GPL for conveying |
| 1132 | + Corresponding Source. |
| 1133 | + |
| 1134 | + 1) Use a suitable shared library mechanism for linking with the |
| 1135 | + Library. A suitable mechanism is one that (a) uses at run time |
| 1136 | + a copy of the Library already present on the user's computer |
| 1137 | + system, and (b) will operate properly with a modified version |
| 1138 | + of the Library that is interface-compatible with the Linked |
| 1139 | + Version. |
| 1140 | + |
| 1141 | + e) Provide Installation Information, but only if you would otherwise |
| 1142 | + be required to provide such information under section 6 of the |
| 1143 | + GNU GPL, and only to the extent that such information is |
| 1144 | + necessary to install and execute a modified version of the |
| 1145 | + Combined Work produced by recombining or relinking the |
| 1146 | + Application with a modified version of the Linked Version. (If |
| 1147 | + you use option 4d0, the Installation Information must accompany |
| 1148 | + the Minimal Corresponding Source and Corresponding Application |
| 1149 | + Code. If you use option 4d1, you must provide the Installation |
| 1150 | + Information in the manner specified by section 6 of the GNU GPL |
| 1151 | + for conveying Corresponding Source.) |
| 1152 | + |
| 1153 | + 5. Combined Libraries. |
| 1154 | + |
| 1155 | + You may place library facilities that are a work based on the |
| 1156 | +Library side by side in a single library together with other library |
| 1157 | +facilities that are not Applications and are not covered by this |
| 1158 | +License, and convey such a combined library under terms of your |
| 1159 | +choice, if you do both of the following: |
| 1160 | + |
| 1161 | + a) Accompany the combined library with a copy of the same work based |
| 1162 | + on the Library, uncombined with any other library facilities, |
| 1163 | + conveyed under the terms of this License. |
| 1164 | + |
| 1165 | + b) Give prominent notice with the combined library that part of it |
| 1166 | + is a work based on the Library, and explaining where to find the |
| 1167 | + accompanying uncombined form of the same work. |
| 1168 | + |
| 1169 | + 6. Revised Versions of the GNU Lesser General Public License. |
| 1170 | + |
| 1171 | + The Free Software Foundation may publish revised and/or new versions |
| 1172 | +of the GNU Lesser General Public License from time to time. Such new |
| 1173 | +versions will be similar in spirit to the present version, but may |
| 1174 | +differ in detail to address new problems or concerns. |
| 1175 | + |
| 1176 | + Each version is given a distinguishing version number. If the |
| 1177 | +Library as you received it specifies that a certain numbered version |
| 1178 | +of the GNU Lesser General Public License "or any later version" |
| 1179 | +applies to it, you have the option of following the terms and |
| 1180 | +conditions either of that published version or of any later version |
| 1181 | +published by the Free Software Foundation. If the Library as you |
| 1182 | +received it does not specify a version number of the GNU Lesser |
| 1183 | +General Public License, you may choose any version of the GNU Lesser |
| 1184 | +General Public License ever published by the Free Software Foundation. |
| 1185 | + |
| 1186 | + If the Library as you received it specifies that a proxy can decide |
| 1187 | +whether future versions of the GNU Lesser General Public License shall |
| 1188 | +apply, that proxy's public statement of acceptance of any version is |
| 1189 | +permanent authorization for you to choose that version for the |
| 1190 | +Library. |
| 1191 | diff --git a/README.md b/README.md |
| 1192 | new file mode 100644 |
| 1193 | index 0000000..346a971 |
| 1194 | --- /dev/null |
| 1195 | +++ b/README.md |
| 1196 | @@ -0,0 +1,126 @@ |
| 1197 | +# Qml6Stretch |
| 1198 | + |
| 1199 | +loooong components |
| 1200 | + |
| 1201 | +Installs *into your Qt6 installation* (`QT_INSTALL_QML` and friends), so |
| 1202 | +consuming projects only need `find_package`: |
| 1203 | + |
| 1204 | +- `<qt>/qml/Qml6Stretch/` - the importable `Qml6Stretch` QML module |
| 1205 | + (`libqml6stretchplugin.so`, `qmldir`, `qml6stretch.qmltypes`) |
| 1206 | +- `<qt>/lib/libqml6stretch.so.<version>` |
| 1207 | +- `<qt>/include/qml6stretch/*.h` - `#include <qml6stretch/stretchflickable_p.h>` |
| 1208 | +- `<qt>/lib/cmake/Qml6Stretch/` - CMake package config for `find_package()` |
| 1209 | + |
| 1210 | +## Building |
| 1211 | + |
| 1212 | +```sh |
| 1213 | +cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.y/gcc_64 |
| 1214 | +cmake --build build -j |
| 1215 | +cmake --install build |
| 1216 | +``` |
| 1217 | + |
| 1218 | +The install prefix defaults to that Qt's prefix; the QML module destination |
| 1219 | +comes from `qmake -query QT_INSTALL_QML` on the qmake belonging to *that* Qt |
| 1220 | +(not whatever `qmake6` is first on `PATH`). Pass `-DCMAKE_INSTALL_PREFIX=...` |
| 1221 | +to install elsewhere, `-DQT_QML_INSTALL_INTO_QT=OFF` for a plain standalone |
| 1222 | +`<prefix>/qml` layout, or `-DQT_QMAKE_EXECUTABLE=...` to pin the qmake. |
| 1223 | + |
| 1224 | +## Consuming it from CMake |
| 1225 | + |
| 1226 | +```cmake |
| 1227 | +find_package(Qml6Stretch REQUIRED CONFIG) |
| 1228 | + |
| 1229 | +qt_add_qml_module(myapp |
| 1230 | + URI App |
| 1231 | + VERSION 1.0 |
| 1232 | + QML_FILES qml/Main.qml |
| 1233 | +) |
| 1234 | + |
| 1235 | +target_link_libraries(myapp PRIVATE Qml6Stretch::qml6stretch) |
| 1236 | +``` |
| 1237 | + |
| 1238 | +No `IMPORT_PATH`, `QML_IMPORT_PATH` or compile definitions needed: the module |
| 1239 | +lives in Qt's own import directory, so `import Qml6Stretch` resolves for |
| 1240 | +qmllint, qmlls and the QML engine at runtime. (`Qml6Stretch_QML_DIR` is still |
| 1241 | +set by the package config, for non-default install layouts.) |
| 1242 | + |
| 1243 | +## Usage from QML |
| 1244 | + |
| 1245 | +```qml |
| 1246 | +import QtQuick |
| 1247 | +import Qml6Stretch |
| 1248 | + |
| 1249 | +Rectangle { |
| 1250 | + anchors.fill: parent |
| 1251 | + color: "black" |
| 1252 | + |
| 1253 | + StretchListView { |
| 1254 | + id: list |
| 1255 | + anchors.fill: parent |
| 1256 | + model: rowModel |
| 1257 | + delegate: rowDelegate |
| 1258 | + spacing: 8 |
| 1259 | + |
| 1260 | + // optional: tune the rubber-band feel |
| 1261 | + dragResistanceRange: 600 |
| 1262 | + dragOvershootMax: 50 |
| 1263 | + } |
| 1264 | + |
| 1265 | + ListModel { |
| 1266 | + id: rowModel |
| 1267 | + ListElement { label: "Item 1"; bgColor: "#00B4D8" } |
| 1268 | + ListElement { label: "Item 2"; bgColor: "#07AFD5" } |
| 1269 | + // ... |
| 1270 | + } |
| 1271 | + |
| 1272 | + Component { |
| 1273 | + id: rowDelegate |
| 1274 | + Rectangle { |
| 1275 | + required property string label |
| 1276 | + required property color bgColor |
| 1277 | + |
| 1278 | + width: list.width |
| 1279 | + height: 128 |
| 1280 | + radius: 12 |
| 1281 | + color: bgColor |
| 1282 | + |
| 1283 | + Text { |
| 1284 | + anchors.centerIn: parent |
| 1285 | + text: label |
| 1286 | + color: "white" |
| 1287 | + } |
| 1288 | + } |
| 1289 | + } |
| 1290 | +} |
| 1291 | +``` |
| 1292 | + |
| 1293 | +Or use `StretchFlickable` directly, like `Flickable`: |
| 1294 | + |
| 1295 | +```qml |
| 1296 | +import QtQuick |
| 1297 | +import Qml6Stretch |
| 1298 | + |
| 1299 | +StretchFlickable { |
| 1300 | + anchors.fill: parent |
| 1301 | + contentWidth: width |
| 1302 | + contentHeight: column.height |
| 1303 | + clip: true |
| 1304 | + boundsBehavior: StretchFlickable.DragOverBounds |
| 1305 | + dragResistanceRange: 600 |
| 1306 | + dragOvershootMax: 50 |
| 1307 | + |
| 1308 | + Column { |
| 1309 | + id: column |
| 1310 | + width: parent.width |
| 1311 | + // ... your content ... |
| 1312 | + } |
| 1313 | +} |
| 1314 | +``` |
| 1315 | + |
| 1316 | +## License |
| 1317 | + |
| 1318 | +Qml6Stretch is licensed under the |
| 1319 | +[GNU Lesser General Public License v3.0](LICENSE) (LGPL-3.0). It is |
| 1320 | +derived from Qt's own `QQuickFlickable`/`QQuickTimeLine` sources |
| 1321 | +(`LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR |
| 1322 | +GPL-3.0-only`), and is distributed here under the LGPL-3.0 option. |
| 1323 | diff --git a/cmake/Qml6StretchConfig.cmake.in b/cmake/Qml6StretchConfig.cmake.in |
| 1324 | new file mode 100644 |
| 1325 | index 0000000..1633dde |
| 1326 | --- /dev/null |
| 1327 | +++ b/cmake/Qml6StretchConfig.cmake.in |
| 1328 | @@ -0,0 +1,20 @@ |
| 1329 | +@PACKAGE_INIT@ |
| 1330 | + |
| 1331 | +include(CMakeFindDependencyMacro) |
| 1332 | + |
| 1333 | +find_dependency(Qt6 @Qt6_VERSION@ COMPONENTS |
| 1334 | + Core |
| 1335 | + Gui |
| 1336 | + Qml |
| 1337 | + Quick |
| 1338 | + CorePrivate |
| 1339 | + GuiPrivate |
| 1340 | + QmlPrivate |
| 1341 | + QuickPrivate |
| 1342 | +) |
| 1343 | + |
| 1344 | +include("${CMAKE_CURRENT_LIST_DIR}/Qml6StretchTargets.cmake") |
| 1345 | + |
| 1346 | +set_and_check(Qml6Stretch_QML_DIR "@PACKAGE_QT_QML_MODULE_INSTALL_FULL_DIR@") |
| 1347 | + |
| 1348 | +check_required_components(Qml6Stretch) |
| 1349 | diff --git a/cmake/QtQmlInstallLayout.cmake b/cmake/QtQmlInstallLayout.cmake |
| 1350 | new file mode 100644 |
| 1351 | index 0000000..03a382a |
| 1352 | --- /dev/null |
| 1353 | +++ b/cmake/QtQmlInstallLayout.cmake |
| 1354 | @@ -0,0 +1,130 @@ |
| 1355 | +# Install layout for projects shipping a QML module. Include after find_package(Qt6). |
| 1356 | +# |
| 1357 | +# Targets Qt's own import dir (QT_INSTALL_QML) so consumers get `import <URI>` |
| 1358 | +# with no IMPORT_PATH / QML_IMPORT_PATH plumbing. Defines: |
| 1359 | +# |
| 1360 | +# QT_QML_INSTALL_INTO_QT option, ON |
| 1361 | +# QT_INSTALL_QML_DIR Qt's QML import dir, absolute |
| 1362 | +# QT_QML_MODULE_INSTALL_DIR cache var, install destination (prefix-relative |
| 1363 | +# where possible), "qml" when the option is OFF |
| 1364 | +# QT_QML_MODULE_INSTALL_FULL_DIR same, absolute |
| 1365 | +# QT_QMAKE_EXECUTABLE the qmake of the Qt being built against |
| 1366 | +# qt_qml_plugin_rpath(<out> <subdir>) RPATH for a plugin at <qml-dir>/<subdir> |
| 1367 | +# |
| 1368 | +# Also includes GNUInstallDirs, since it may first default CMAKE_INSTALL_PREFIX |
| 1369 | +# to Qt's prefix and GNUInstallDirs' libdir choice depends on that. |
| 1370 | + |
| 1371 | +if(NOT TARGET Qt6::Core) |
| 1372 | + message(FATAL_ERROR "QtQmlInstallLayout must be included after find_package(Qt6).") |
| 1373 | +endif() |
| 1374 | + |
| 1375 | +# Find the qmake of the Qt that find_package(Qt6) resolved - not whatever |
| 1376 | +# qmake6 happens to be first on PATH. |
| 1377 | + |
| 1378 | +# 1. Qt's own imported tool target, when available. |
| 1379 | +if(NOT QT_QMAKE_EXECUTABLE AND TARGET Qt6::qmake) |
| 1380 | + get_target_property(QT_QMAKE_EXECUTABLE Qt6::qmake IMPORTED_LOCATION) |
| 1381 | +endif() |
| 1382 | + |
| 1383 | +# 2. Next to the Qt6Config.cmake we loaded (Qt6_DIR is <prefix>/lib/cmake/Qt6). |
| 1384 | +if(NOT QT_QMAKE_EXECUTABLE) |
| 1385 | + set(_hints "") |
| 1386 | + if(QT6_INSTALL_PREFIX) |
| 1387 | + list(APPEND _hints "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}" "${QT6_INSTALL_PREFIX}/bin") |
| 1388 | + endif() |
| 1389 | + if(Qt6_DIR) |
| 1390 | + get_filename_component(_qtprefix "${Qt6_DIR}/../../.." ABSOLUTE) |
| 1391 | + list(APPEND _hints "${_qtprefix}/bin") |
| 1392 | + endif() |
| 1393 | + find_program(QT_QMAKE_EXECUTABLE NAMES qmake6 qmake HINTS ${_hints} NO_DEFAULT_PATH |
| 1394 | + DOC "qmake of the Qt6 being built against") |
| 1395 | +endif() |
| 1396 | + |
| 1397 | +# 3. Last resort: PATH. Still has to pass the prefix cross-check below. |
| 1398 | +if(NOT QT_QMAKE_EXECUTABLE) |
| 1399 | + find_program(QT_QMAKE_EXECUTABLE NAMES qmake6 qmake |
| 1400 | + DOC "qmake of the Qt6 being built against") |
| 1401 | +endif() |
| 1402 | + |
| 1403 | +set(QT_INSTALL_QML_DIR "") |
| 1404 | + |
| 1405 | +if(QT_QMAKE_EXECUTABLE) |
| 1406 | + execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_QML |
| 1407 | + OUTPUT_VARIABLE _qml_dir OUTPUT_STRIP_TRAILING_WHITESPACE |
| 1408 | + ERROR_QUIET RESULT_VARIABLE _qmake_result) |
| 1409 | + execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_PREFIX |
| 1410 | + OUTPUT_VARIABLE _qmake_prefix OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) |
| 1411 | + |
| 1412 | + # Reject a qmake from a different Qt (distro qmake6 on PATH vs. the Qt in |
| 1413 | + # CMAKE_PREFIX_PATH) - installing into its qml dir would be silently wrong. |
| 1414 | + if(_qmake_result EQUAL 0 AND _qml_dir AND QT6_INSTALL_PREFIX AND _qmake_prefix) |
| 1415 | + get_filename_component(_a "${QT6_INSTALL_PREFIX}" REALPATH) |
| 1416 | + get_filename_component(_b "${_qmake_prefix}" REALPATH) |
| 1417 | + if(NOT _a STREQUAL _b) |
| 1418 | + message(WARNING "Ignoring ${QT_QMAKE_EXECUTABLE}: reports prefix " |
| 1419 | + "'${_qmake_prefix}', but this build uses Qt at '${QT6_INSTALL_PREFIX}'.") |
| 1420 | + set(_qml_dir "") |
| 1421 | + endif() |
| 1422 | + endif() |
| 1423 | + if(_qmake_result EQUAL 0) |
| 1424 | + set(QT_INSTALL_QML_DIR "${_qml_dir}") |
| 1425 | + endif() |
| 1426 | +endif() |
| 1427 | + |
| 1428 | +# Fallback for a Qt without a usable qmake (QT6_INSTALL_QML is prefix-relative). |
| 1429 | +if(NOT QT_INSTALL_QML_DIR AND QT6_INSTALL_PREFIX AND QT6_INSTALL_QML) |
| 1430 | + set(QT_INSTALL_QML_DIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_QML}") |
| 1431 | +endif() |
| 1432 | + |
| 1433 | +option(QT_QML_INSTALL_INTO_QT |
| 1434 | + "Install into the Qt6 installation itself, so consumers need no import paths" ON) |
| 1435 | + |
| 1436 | +if(QT_QML_INSTALL_INTO_QT AND NOT QT_INSTALL_QML_DIR) |
| 1437 | + message(FATAL_ERROR "Could not determine QT_INSTALL_QML. Set QT_QMAKE_EXECUTABLE " |
| 1438 | + "to the right qmake6, or configure with -DQT_QML_INSTALL_INTO_QT=OFF.") |
| 1439 | +endif() |
| 1440 | + |
| 1441 | +# Default the prefix to Qt's, unless one was given on the command line. |
| 1442 | +if(QT_QML_INSTALL_INTO_QT AND CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) |
| 1443 | + if(QT6_INSTALL_PREFIX) |
| 1444 | + set(_prefix "${QT6_INSTALL_PREFIX}") |
| 1445 | + else() |
| 1446 | + get_filename_component(_prefix "${QT_INSTALL_QML_DIR}/.." ABSOLUTE) |
| 1447 | + endif() |
| 1448 | + set(CMAKE_INSTALL_PREFIX "${_prefix}" CACHE PATH |
| 1449 | + "Install path prefix (defaults to the Qt6 installation)" FORCE) |
| 1450 | +endif() |
| 1451 | + |
| 1452 | +include(GNUInstallDirs) |
| 1453 | + |
| 1454 | +# QML import resolution looks for <import-path>/<URI>/qmldir, so this is kept |
| 1455 | +# separate from CMAKE_INSTALL_LIBDIR. Prefer a prefix-relative path to keep the |
| 1456 | +# install tree relocatable and DESTDIR working. |
| 1457 | +if(QT_QML_INSTALL_INTO_QT) |
| 1458 | + file(RELATIVE_PATH _qml_rel "${CMAKE_INSTALL_PREFIX}" "${QT_INSTALL_QML_DIR}") |
| 1459 | + if(_qml_rel STREQUAL "" OR _qml_rel MATCHES "^\\.\\.") |
| 1460 | + set(_qml_rel "${QT_INSTALL_QML_DIR}") # prefix overridden away from Qt |
| 1461 | + endif() |
| 1462 | +else() |
| 1463 | + set(_qml_rel "qml") |
| 1464 | +endif() |
| 1465 | + |
| 1466 | +set(QT_QML_MODULE_INSTALL_DIR "${_qml_rel}" CACHE STRING |
| 1467 | + "Install location (relative to CMAKE_INSTALL_PREFIX) of this project's QML module") |
| 1468 | + |
| 1469 | +if(IS_ABSOLUTE "${QT_QML_MODULE_INSTALL_DIR}") |
| 1470 | + set(QT_QML_MODULE_INSTALL_FULL_DIR "${QT_QML_MODULE_INSTALL_DIR}") |
| 1471 | +else() |
| 1472 | + set(QT_QML_MODULE_INSTALL_FULL_DIR "${CMAKE_INSTALL_PREFIX}/${QT_QML_MODULE_INSTALL_DIR}") |
| 1473 | +endif() |
| 1474 | + |
| 1475 | +# A QML plugin sits at <qml-dir>/<subdir> and must find its backing library in |
| 1476 | +# <prefix>/<libdir>; the hop between them depends on where Qt's qml dir is. |
| 1477 | +function(qt_qml_plugin_rpath out_var subdir) |
| 1478 | + file(RELATIVE_PATH _rel "${QT_QML_MODULE_INSTALL_FULL_DIR}/${subdir}" |
| 1479 | + "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") |
| 1480 | + set(${out_var} "$ORIGIN/${_rel}" PARENT_SCOPE) |
| 1481 | +endfunction() |
| 1482 | + |
| 1483 | +message(STATUS "${PROJECT_NAME}: prefix = ${CMAKE_INSTALL_PREFIX}") |
| 1484 | +message(STATUS "${PROJECT_NAME}: QML module dir = ${QT_QML_MODULE_INSTALL_DIR}") |
| 1485 | diff --git a/qml/StretchListView.qml b/qml/StretchListView.qml |
| 1486 | new file mode 100644 |
| 1487 | index 0000000..aa8e84a |
| 1488 | --- /dev/null |
| 1489 | +++ b/qml/StretchListView.qml |
| 1490 | @@ -0,0 +1,83 @@ |
| 1491 | +import QtQuick |
| 1492 | +import Qml6Stretch |
| 1493 | + |
| 1494 | +// Minimal ListView-alike: vertical Column + Repeater riding inside a |
| 1495 | +// StretchFlickable, so scrolling gets the same drag-resistance/overshoot |
| 1496 | +// feel as CoolGrid without forking QQuickListView's C++ internals. |
| 1497 | +Item { |
| 1498 | + id: root |
| 1499 | + |
| 1500 | + property alias model: repeater.model |
| 1501 | + property alias delegate: repeater.delegate |
| 1502 | + property real spacing: 0 |
| 1503 | + property int currentIndex: -1 |
| 1504 | + readonly property int count: repeater.count |
| 1505 | + readonly property Item currentItem: currentIndex >= 0 ? repeater.itemAt(currentIndex) : null |
| 1506 | + |
| 1507 | + property alias interactive: flick.interactive |
| 1508 | + property alias contentY: flick.contentY |
| 1509 | + readonly property real contentHeight: column.height |
| 1510 | + property alias dragResistanceRange: flick.dragResistanceRange |
| 1511 | + property alias dragOvershootMax: flick.dragOvershootMax |
| 1512 | + |
| 1513 | + function positionViewAtBeginning() { |
| 1514 | + flick.contentY = 0 |
| 1515 | + } |
| 1516 | + |
| 1517 | + function positionViewAtEnd() { |
| 1518 | + flick.contentY = Math.max(0, column.height - flick.height) |
| 1519 | + } |
| 1520 | + |
| 1521 | + function positionViewAtIndex(index) { |
| 1522 | + const item = repeater.itemAt(index) |
| 1523 | + if (!item) |
| 1524 | + return |
| 1525 | + flick.contentY = Math.max(0, Math.min(item.y, Math.max(0, column.height - flick.height))) |
| 1526 | + } |
| 1527 | + |
| 1528 | + // how far contentY sits past [0, maxContentY] - negative at the top, |
| 1529 | + // positive at the bottom; turned into a stretch below instead of a slide |
| 1530 | + readonly property real overscrollStretch: { |
| 1531 | + const t = Math.min(1, Math.abs(flick.overshootY) / 250) |
| 1532 | + const eased = 1 - Math.pow(1 - t, 3) |
| 1533 | + return eased * 0.15 |
| 1534 | + } |
| 1535 | + |
| 1536 | + StretchFlickable { |
| 1537 | + id: flick |
| 1538 | + anchors.fill: parent |
| 1539 | + contentWidth: width |
| 1540 | + contentHeight: column.height + 32 |
| 1541 | + clip: true |
| 1542 | + // rubber-band only while actively dragging past the bounds - a hard |
| 1543 | + // flick should decelerate to a stop at the edge, not bounce once |
| 1544 | + // released (no OvershootBounds) |
| 1545 | + boundsBehavior: StretchFlickable.DragOverBounds |
| 1546 | + |
| 1547 | + readonly property real maxContentY: Math.max(0, contentHeight - height) |
| 1548 | + readonly property real overshootY: contentY - Math.max(0, Math.min(contentY, maxContentY)) |
| 1549 | + |
| 1550 | + Column { |
| 1551 | + id: column |
| 1552 | + width: flick.width |
| 1553 | + spacing: root.spacing |
| 1554 | + |
| 1555 | + // cancels Flickable's -contentY translation for just the overshoot |
| 1556 | + // portion, pinning the column at its top/bottom-most position |
| 1557 | + // instead of sliding past it |
| 1558 | + y: flick.overshootY |
| 1559 | + |
| 1560 | + // ...replaces the cancelled slide with a stretch from the pulled |
| 1561 | + // edge, so that edge stays put while the column elongates |
| 1562 | + transform: Scale { |
| 1563 | + origin.x: 0 |
| 1564 | + origin.y: flick.overshootY < 0 ? 0 : column.height |
| 1565 | + yScale: 1 + root.overscrollStretch |
| 1566 | + } |
| 1567 | + |
| 1568 | + Repeater { |
| 1569 | + id: repeater |
| 1570 | + } |
| 1571 | + } |
| 1572 | + } |
| 1573 | +} |
| 1574 | diff --git a/src/stretchflickable.cpp b/src/stretchflickable.cpp |
| 1575 | new file mode 100644 |
| 1576 | index 0000000..08ed2cd |
| 1577 | --- /dev/null |
| 1578 | +++ b/src/stretchflickable.cpp |
| 1579 | @@ -0,0 +1,3417 @@ |
| 1580 | +// Copyright (C) 2020 The Qt Company Ltd. |
| 1581 | +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 1582 | + |
| 1583 | +#include "stretchflickable_p.h" |
| 1584 | +#include "stretchflickable_p_p.h" |
| 1585 | +#include "stretchflickablebehavior_p.h" |
| 1586 | +#include <QtQuick/qquickwindow.h> |
| 1587 | +#include <QtQuick/private/qquickwindow_p.h> |
| 1588 | +#include <QtQuick/private/qquickmousearea_p.h> |
| 1589 | +#if QT_CONFIG(quick_draganddrop) |
| 1590 | +#include <QtQuick/private/qquickdrag_p.h> |
| 1591 | +#endif |
| 1592 | + |
| 1593 | +#include <QtQuick/private/qquickpointerhandler_p.h> |
| 1594 | +#include <QtQuick/private/qquicktransition_p.h> |
| 1595 | +#include <private/qqmlglobal_p.h> |
| 1596 | + |
| 1597 | +#include <QtQml/qqmlinfo.h> |
| 1598 | +#include <QtGui/qevent.h> |
| 1599 | +#include <QtGui/qguiapplication.h> |
| 1600 | +#include <QtGui/private/qguiapplication_p.h> |
| 1601 | +#include <QtGui/private/qeventpoint_p.h> |
| 1602 | +#include <QtGui/qstylehints.h> |
| 1603 | +#include <QtGui/qaccessible.h> |
| 1604 | +#include <QtCore/qmath.h> |
| 1605 | +#include <qpa/qplatformtheme.h> |
| 1606 | + |
| 1607 | +#include <math.h> |
| 1608 | +#include <cmath> |
| 1609 | + |
| 1610 | +QT_BEGIN_NAMESPACE |
| 1611 | + |
| 1612 | +Q_STATIC_LOGGING_CATEGORY(lcFlickable, "qt.quick.flickable") |
| 1613 | +Q_STATIC_LOGGING_CATEGORY(lcFilter, "qt.quick.flickable.filter") |
| 1614 | +Q_STATIC_LOGGING_CATEGORY(lcReplay, "qt.quick.flickable.replay") |
| 1615 | +Q_STATIC_LOGGING_CATEGORY(lcWheel, "qt.quick.flickable.wheel") |
| 1616 | +Q_STATIC_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity") |
| 1617 | +// QtQuick's own lcHandlerParent (qquickitem_p.h) has no exported definition on |
| 1618 | +// this build (it's only declared, not Q_QUICK_EXPORTed) - use our own instead, |
| 1619 | +// under a different name so it doesn't clash with that (still visible) declaration. |
| 1620 | +Q_STATIC_LOGGING_CATEGORY(lcStretchHandlerParent, "qt.quick.handler.parent") |
| 1621 | + |
| 1622 | +// RetainGrabVelocity is the maxmimum instantaneous velocity that |
| 1623 | +// will ensure the Flickable retains the grab on consecutive flicks. |
| 1624 | +static const int RetainGrabVelocity = 100; |
| 1625 | + |
| 1626 | +static qreal EaseOvershoot(qreal t) { |
| 1627 | + return qAtan(t); |
| 1628 | +} |
| 1629 | + |
| 1630 | +// Drag-over-bounds resistance curve: rawOvershoot is how far past the bound |
| 1631 | +// the finger would be with zero resistance; maps it into [0, maxOvershoot) |
| 1632 | +// using the same atan shape as EaseOvershoot above, but normalized by pi/2 so |
| 1633 | +// it asymptotes at exactly maxOvershoot instead of maxOvershoot * pi/2. |
| 1634 | +// resistanceDistance is the raw-drag distance needed to approach that cap - |
| 1635 | +// larger means more resistance (you have to drag much further for the same |
| 1636 | +// visual stretch). |
| 1637 | +static qreal dragResistanceCurve(qreal rawOvershoot, qreal resistanceDistance, qreal maxOvershoot) { |
| 1638 | + return maxOvershoot * (2.0 / M_PI) * qAtan(rawOvershoot / resistanceDistance); |
| 1639 | +} |
| 1640 | + |
| 1641 | +StretchFlickableVisibleArea::StretchFlickableVisibleArea(StretchFlickable *parent) |
| 1642 | + : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.) |
| 1643 | + , m_yPosition(0.), m_heightRatio(0.) |
| 1644 | +{ |
| 1645 | +} |
| 1646 | + |
| 1647 | +qreal StretchFlickableVisibleArea::widthRatio() const |
| 1648 | +{ |
| 1649 | + return m_widthRatio; |
| 1650 | +} |
| 1651 | + |
| 1652 | +qreal StretchFlickableVisibleArea::xPosition() const |
| 1653 | +{ |
| 1654 | + return m_xPosition; |
| 1655 | +} |
| 1656 | + |
| 1657 | +qreal StretchFlickableVisibleArea::heightRatio() const |
| 1658 | +{ |
| 1659 | + return m_heightRatio; |
| 1660 | +} |
| 1661 | + |
| 1662 | +qreal StretchFlickableVisibleArea::yPosition() const |
| 1663 | +{ |
| 1664 | + return m_yPosition; |
| 1665 | +} |
| 1666 | + |
| 1667 | +void StretchFlickableVisibleArea::updateVisible() |
| 1668 | +{ |
| 1669 | + StretchFlickablePrivate *p = StretchFlickablePrivate::get(flickable); |
| 1670 | + |
| 1671 | + bool changeX = false; |
| 1672 | + bool changeY = false; |
| 1673 | + bool changeWidth = false; |
| 1674 | + bool changeHeight = false; |
| 1675 | + |
| 1676 | + // Vertical |
| 1677 | + const qreal viewheight = flickable->height(); |
| 1678 | + const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent(); |
| 1679 | + const qreal maxYBounds = maxyextent + viewheight; |
| 1680 | + qreal pagePos = 0; |
| 1681 | + qreal pageSize = 0; |
| 1682 | + if (!qFuzzyIsNull(maxYBounds)) { |
| 1683 | + qreal y = p->pixelAligned ? std::round(p->vData.move.value()) : p->vData.move.value(); |
| 1684 | + pagePos = (-y + flickable->minYExtent()) / maxYBounds; |
| 1685 | + pageSize = viewheight / maxYBounds; |
| 1686 | + } |
| 1687 | + |
| 1688 | + if (pageSize != m_heightRatio) { |
| 1689 | + m_heightRatio = pageSize; |
| 1690 | + changeHeight = true; |
| 1691 | + } |
| 1692 | + if (pagePos != m_yPosition) { |
| 1693 | + m_yPosition = pagePos; |
| 1694 | + changeY = true; |
| 1695 | + } |
| 1696 | + |
| 1697 | + // Horizontal |
| 1698 | + const qreal viewwidth = flickable->width(); |
| 1699 | + const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent(); |
| 1700 | + const qreal maxXBounds = maxxextent + viewwidth; |
| 1701 | + if (!qFuzzyIsNull(maxXBounds)) { |
| 1702 | + qreal x = p->pixelAligned ? std::round(p->hData.move.value()) : p->hData.move.value(); |
| 1703 | + pagePos = (-x + flickable->minXExtent()) / maxXBounds; |
| 1704 | + pageSize = viewwidth / maxXBounds; |
| 1705 | + } else { |
| 1706 | + pagePos = 0; |
| 1707 | + pageSize = 0; |
| 1708 | + } |
| 1709 | + |
| 1710 | + if (pageSize != m_widthRatio) { |
| 1711 | + m_widthRatio = pageSize; |
| 1712 | + changeWidth = true; |
| 1713 | + } |
| 1714 | + if (pagePos != m_xPosition) { |
| 1715 | + m_xPosition = pagePos; |
| 1716 | + changeX = true; |
| 1717 | + } |
| 1718 | + |
| 1719 | + if (changeX) |
| 1720 | + emit xPositionChanged(m_xPosition); |
| 1721 | + if (changeY) |
| 1722 | + emit yPositionChanged(m_yPosition); |
| 1723 | + if (changeWidth) |
| 1724 | + emit widthRatioChanged(m_widthRatio); |
| 1725 | + if (changeHeight) |
| 1726 | + emit heightRatioChanged(m_heightRatio); |
| 1727 | +} |
| 1728 | + |
| 1729 | + |
| 1730 | +class StretchFlickableReboundTransition : public QQuickTransitionManager |
| 1731 | +{ |
| 1732 | +public: |
| 1733 | + StretchFlickableReboundTransition(StretchFlickable *f, const QString &name) |
| 1734 | + : flickable(f), axisData(nullptr), propName(name), active(false) |
| 1735 | + { |
| 1736 | + } |
| 1737 | + |
| 1738 | + ~StretchFlickableReboundTransition() |
| 1739 | + { |
| 1740 | + flickable = nullptr; |
| 1741 | + } |
| 1742 | + |
| 1743 | + bool startTransition(StretchFlickablePrivate::AxisData *data, qreal toPos) { |
| 1744 | + StretchFlickablePrivate *fp = StretchFlickablePrivate::get(flickable); |
| 1745 | + if (!fp->rebound || !fp->rebound->enabled()) |
| 1746 | + return false; |
| 1747 | + active = true; |
| 1748 | + axisData = data; |
| 1749 | + axisData->transitionTo = toPos; |
| 1750 | + axisData->transitionToSet = true; |
| 1751 | + |
| 1752 | + actions.clear(); |
| 1753 | + actions << QQuickStateAction(fp->contentItem, propName, toPos); |
| 1754 | + QQuickTransitionManager::transition(actions, fp->rebound, fp->contentItem); |
| 1755 | + return true; |
| 1756 | + } |
| 1757 | + |
| 1758 | + bool isActive() const { |
| 1759 | + return active; |
| 1760 | + } |
| 1761 | + |
| 1762 | + void stopTransition() { |
| 1763 | + if (!flickable || !isRunning()) |
| 1764 | + return; |
| 1765 | + StretchFlickablePrivate *fp = StretchFlickablePrivate::get(flickable); |
| 1766 | + if (axisData == &fp->hData) |
| 1767 | + axisData->move.setValue(-flickable->contentX()); |
| 1768 | + else |
| 1769 | + axisData->move.setValue(-flickable->contentY()); |
| 1770 | + active = false; |
| 1771 | + cancel(); |
| 1772 | + } |
| 1773 | + |
| 1774 | +protected: |
| 1775 | + void finished() override { |
| 1776 | + if (!flickable) |
| 1777 | + return; |
| 1778 | + axisData->move.setValue(axisData->transitionTo); |
| 1779 | + StretchFlickablePrivate *fp = StretchFlickablePrivate::get(flickable); |
| 1780 | + active = false; |
| 1781 | + |
| 1782 | + if (!fp->hData.transitionToBounds->isActive() |
| 1783 | + && !fp->vData.transitionToBounds->isActive()) { |
| 1784 | + flickable->movementEnding(); |
| 1785 | + } |
| 1786 | + } |
| 1787 | + |
| 1788 | +private: |
| 1789 | + QQuickStateOperation::ActionList actions; |
| 1790 | + StretchFlickable *flickable; |
| 1791 | + StretchFlickablePrivate::AxisData *axisData; |
| 1792 | + QString propName; |
| 1793 | + bool active; |
| 1794 | +}; |
| 1795 | + |
| 1796 | +StretchFlickablePrivate::AxisData::~AxisData() |
| 1797 | +{ |
| 1798 | + delete transitionToBounds; |
| 1799 | +} |
| 1800 | + |
| 1801 | +class StretchFlickableContentItem : public QQuickItem |
| 1802 | +{ |
| 1803 | + /*! |
| 1804 | + \internal |
| 1805 | + The flickable area inside the viewport can be bigger than the bounds of the |
| 1806 | + content item itself, if the flickable is using non-zero extents (as returned |
| 1807 | + by e.g minXExtent()). Since the default implementation in QQuickItem::contains() |
| 1808 | + only checks if the point is inside the bounds of the item, we need to override it |
| 1809 | + to check the extents as well. The easist way to do this is to simply check if the |
| 1810 | + point is inside the bounds of the flickable rather than the content item. |
| 1811 | + */ |
| 1812 | + bool contains(const QPointF &point) const override |
| 1813 | + { |
| 1814 | + const QQuickItem *flickable = parentItem(); |
| 1815 | + const QPointF posInFlickable = flickable->mapFromItem(this, point); |
| 1816 | + return flickable->contains(posInFlickable); |
| 1817 | + } |
| 1818 | +}; |
| 1819 | + |
| 1820 | +StretchFlickablePrivate::StretchFlickablePrivate() |
| 1821 | + : contentItem(new StretchFlickableContentItem) |
| 1822 | + , hData(this, &StretchFlickablePrivate::setViewportX) |
| 1823 | + , vData(this, &StretchFlickablePrivate::setViewportY) |
| 1824 | + , hMoved(false), vMoved(false) |
| 1825 | + , stealMouse(false), pressed(false) |
| 1826 | + , scrollingPhase(false), interactive(true), calcVelocity(false) |
| 1827 | + , pixelAligned(false) |
| 1828 | + , syncDrag(false) |
| 1829 | + , acceptedButtons(Qt::LeftButton) |
| 1830 | + , lastPosTime(-1) |
| 1831 | + , lastPressTime(0) |
| 1832 | + , deceleration(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickDeceleration).toReal()) |
| 1833 | + , wheelDeceleration(15000) |
| 1834 | + , maxVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal()) |
| 1835 | + , delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400) |
| 1836 | + , flickBoost(1.0), initialWheelFlickDistance(qApp->styleHints()->wheelScrollLines() * 24) |
| 1837 | + , fixupMode(Normal), vTime(0), visibleArea(nullptr) |
| 1838 | + , flickableDirection(StretchFlickable::AutoFlickDirection) |
| 1839 | + , boundsBehavior(StretchFlickable::DragAndOvershootBounds) |
| 1840 | + , boundsMovement(StretchFlickable::FollowBoundsBehavior) |
| 1841 | + , rebound(nullptr) |
| 1842 | +{ |
| 1843 | + const int wheelDecelerationEnv = qEnvironmentVariableIntValue("QT_QUICK_FLICKABLE_WHEEL_DECELERATION"); |
| 1844 | + if (wheelDecelerationEnv > 0) |
| 1845 | + wheelDeceleration = wheelDecelerationEnv; |
| 1846 | +} |
| 1847 | + |
| 1848 | +void StretchFlickablePrivate::init() |
| 1849 | +{ |
| 1850 | + Q_Q(StretchFlickable); |
| 1851 | + QQml_setParent_noEvent(contentItem, q); |
| 1852 | + contentItem->setParentItem(q); |
| 1853 | + qmlobject_connect(&timeline, StretchTimeLine, SIGNAL(completed()), |
| 1854 | + q, StretchFlickable, SLOT(timelineCompleted())); |
| 1855 | + qmlobject_connect(&velocityTimeline, StretchTimeLine, SIGNAL(completed()), |
| 1856 | + q, StretchFlickable, SLOT(velocityTimelineCompleted())); |
| 1857 | + q->setAcceptedMouseButtons(acceptedButtons); |
| 1858 | + q->setAcceptTouchEvents(true); |
| 1859 | + q->setFiltersChildMouseEvents(true); |
| 1860 | + q->setFlag(QQuickItem::ItemIsViewport); |
| 1861 | + QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem); |
| 1862 | + viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry); |
| 1863 | + setSizePolicy(QLayoutPolicy::Expanding, QLayoutPolicy::Expanding); |
| 1864 | +} |
| 1865 | + |
| 1866 | +/*! |
| 1867 | + \internal |
| 1868 | + Returns the distance to overshoot, given \a velocity. |
| 1869 | + Will be in range 0 - velocity / 3, but limited to a max of QML_FLICK_OVERSHOOT |
| 1870 | +*/ |
| 1871 | +qreal StretchFlickablePrivate::overShootDistance(qreal velocity) const |
| 1872 | +{ |
| 1873 | + if (maxVelocity <= 0) |
| 1874 | + return 0; |
| 1875 | + |
| 1876 | + return qMin(qreal(QML_FLICK_OVERSHOOT), velocity / 3); |
| 1877 | +} |
| 1878 | + |
| 1879 | +qreal StretchFlickablePrivate::dragOvershootRange() const |
| 1880 | +{ |
| 1881 | + return dragResistanceRange * devicePixelRatio(); |
| 1882 | +} |
| 1883 | + |
| 1884 | +qreal StretchFlickablePrivate::dragOvershootMaxPixels() const |
| 1885 | +{ |
| 1886 | + return dragOvershootMax * devicePixelRatio(); |
| 1887 | +} |
| 1888 | + |
| 1889 | +void StretchFlickablePrivate::AxisData::addVelocitySample(qreal v, qreal maxVelocity) |
| 1890 | +{ |
| 1891 | + if (v > maxVelocity) |
| 1892 | + v = maxVelocity; |
| 1893 | + else if (v < -maxVelocity) |
| 1894 | + v = -maxVelocity; |
| 1895 | + velocityBuffer.append(v); |
| 1896 | + if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER) |
| 1897 | + velocityBuffer.remove(0); |
| 1898 | +} |
| 1899 | + |
| 1900 | +void StretchFlickablePrivate::AxisData::updateVelocity() |
| 1901 | +{ |
| 1902 | + velocity = 0; |
| 1903 | + if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) { |
| 1904 | + int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES; |
| 1905 | + for (int i = 0; i < count; ++i) { |
| 1906 | + qreal v = velocityBuffer.at(i); |
| 1907 | + velocity += v; |
| 1908 | + } |
| 1909 | + velocity /= count; |
| 1910 | + } |
| 1911 | +} |
| 1912 | + |
| 1913 | +void StretchFlickablePrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &oldGeom) |
| 1914 | +{ |
| 1915 | + Q_Q(StretchFlickable); |
| 1916 | + if (item == contentItem) { |
| 1917 | + Qt::Orientations orient; |
| 1918 | + if (change.xChange()) |
| 1919 | + orient |= Qt::Horizontal; |
| 1920 | + if (change.yChange()) |
| 1921 | + orient |= Qt::Vertical; |
| 1922 | + if (orient) { |
| 1923 | + q->viewportMoved(orient); |
| 1924 | + const QPointF deltaMoved = item->position() - oldGeom.topLeft(); |
| 1925 | + if (hData.contentPositionChangedExternallyDuringDrag) |
| 1926 | + hData.pressPos += deltaMoved.x(); |
| 1927 | + if (vData.contentPositionChangedExternallyDuringDrag) |
| 1928 | + vData.pressPos += deltaMoved.y(); |
| 1929 | + } |
| 1930 | + if (orient & Qt::Horizontal) |
| 1931 | + emit q->contentXChanged(); |
| 1932 | + if (orient & Qt::Vertical) |
| 1933 | + emit q->contentYChanged(); |
| 1934 | + } |
| 1935 | +} |
| 1936 | + |
| 1937 | +bool StretchFlickablePrivate::flickX(QEvent::Type eventType, qreal velocity) |
| 1938 | +{ |
| 1939 | + Q_Q(StretchFlickable); |
| 1940 | + return flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, eventType, velocity); |
| 1941 | +} |
| 1942 | + |
| 1943 | +bool StretchFlickablePrivate::flickY(QEvent::Type eventType, qreal velocity) |
| 1944 | +{ |
| 1945 | + Q_Q(StretchFlickable); |
| 1946 | + return flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, eventType, velocity); |
| 1947 | +} |
| 1948 | + |
| 1949 | +bool StretchFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal, |
| 1950 | + StretchTimeLineCallback::Callback fixupCallback, |
| 1951 | + QEvent::Type eventType, qreal velocity) |
| 1952 | +{ |
| 1953 | + Q_Q(StretchFlickable); |
| 1954 | + |
| 1955 | + // Releasing while already dragged past the bounds: don't let residual |
| 1956 | + // velocity launch it further into overshoot - snap straight back to the |
| 1957 | + // bound immediately, no matter how hard the flick was. (This only covers |
| 1958 | + // the drag-overshoot case; inOvershoot, checked below, covers the |
| 1959 | + // separate case of momentum carrying it past the bounds mid-flick.) |
| 1960 | + const bool draggedPastBounds = (data.move.value() > minExtent) |
| 1961 | + || (data.move.value() < maxExtent && maxExtent - minExtent <= 0); |
| 1962 | + if (draggedPastBounds) { |
| 1963 | + resetTimeline(data); |
| 1964 | + fixup(data, minExtent, maxExtent); |
| 1965 | + return false; |
| 1966 | + } |
| 1967 | + |
| 1968 | + qreal maxDistance = -1; |
| 1969 | + data.fixingUp = false; |
| 1970 | + // -ve velocity means list is moving up |
| 1971 | + if (velocity > 0) { |
| 1972 | + maxDistance = qAbs(minExtent - data.move.value()); |
| 1973 | + data.flickTarget = minExtent; |
| 1974 | + } else { |
| 1975 | + maxDistance = qAbs(maxExtent - data.move.value()); |
| 1976 | + data.flickTarget = maxExtent; |
| 1977 | + } |
| 1978 | + if (maxDistance > 0 || boundsBehavior & StretchFlickable::OvershootBounds) { |
| 1979 | + qreal v = velocity; |
| 1980 | + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { |
| 1981 | + if (v < 0) |
| 1982 | + v = -maxVelocity; |
| 1983 | + else |
| 1984 | + v = maxVelocity; |
| 1985 | + } |
| 1986 | + |
| 1987 | + qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration; |
| 1988 | + qCDebug(lcFlickable) << "choosing deceleration" << accel << "for" << eventType; |
| 1989 | + // adjust accel so that we hit a full pixel |
| 1990 | + qreal v2 = v * v; |
| 1991 | + qreal dist = v2 / (accel * 2.0); |
| 1992 | + if (v > 0) |
| 1993 | + dist = -dist; |
| 1994 | + qreal target = std::round(data.move.value() - dist); |
| 1995 | + dist = -target + data.move.value(); |
| 1996 | + accel = v2 / (2.0f * qAbs(dist)); |
| 1997 | + |
| 1998 | + resetTimeline(data); |
| 1999 | + if (!data.inOvershoot) { |
| 2000 | + if (boundsBehavior & StretchFlickable::OvershootBounds) |
| 2001 | + timeline.accel(data.move, v, accel); |
| 2002 | + else |
| 2003 | + timeline.accel(data.move, v, accel, maxDistance); |
| 2004 | + } |
| 2005 | + timeline.callback(StretchTimeLineCallback(&data.move, fixupCallback, this)); |
| 2006 | + |
| 2007 | + if (&data == &hData) |
| 2008 | + return !hData.flicking && q->xflick(); |
| 2009 | + else if (&data == &vData) |
| 2010 | + return !vData.flicking && q->yflick(); |
| 2011 | + return false; |
| 2012 | + } else { |
| 2013 | + resetTimeline(data); |
| 2014 | + fixup(data, minExtent, maxExtent); |
| 2015 | + return false; |
| 2016 | + } |
| 2017 | +} |
| 2018 | + |
| 2019 | +void StretchFlickablePrivate::fixupY_callback(void *data) |
| 2020 | +{ |
| 2021 | + ((StretchFlickablePrivate *)data)->fixupY(); |
| 2022 | +} |
| 2023 | + |
| 2024 | +void StretchFlickablePrivate::fixupX_callback(void *data) |
| 2025 | +{ |
| 2026 | + ((StretchFlickablePrivate *)data)->fixupX(); |
| 2027 | +} |
| 2028 | + |
| 2029 | +void StretchFlickablePrivate::fixupX() |
| 2030 | +{ |
| 2031 | + Q_Q(StretchFlickable); |
| 2032 | + if (!q->isComponentComplete()) |
| 2033 | + return; //Do not fixup from initialization values |
| 2034 | + fixup(hData, q->minXExtent(), q->maxXExtent()); |
| 2035 | +} |
| 2036 | + |
| 2037 | +void StretchFlickablePrivate::fixupY() |
| 2038 | +{ |
| 2039 | + Q_Q(StretchFlickable); |
| 2040 | + if (!q->isComponentComplete()) |
| 2041 | + return; //Do not fixup from initialization values |
| 2042 | + fixup(vData, q->minYExtent(), q->maxYExtent()); |
| 2043 | +} |
| 2044 | + |
| 2045 | +/*! |
| 2046 | + \internal |
| 2047 | + |
| 2048 | + Adjusts the contentItem's position via the timeline. |
| 2049 | + This function is used by StretchFlickablePrivate::fixup in order to |
| 2050 | + position the contentItem back into the viewport, in case flicking, |
| 2051 | + dragging or geometry adjustments moved it outside of bounds. |
| 2052 | +*/ |
| 2053 | +void StretchFlickablePrivate::adjustContentPos(AxisData &data, qreal toPos) |
| 2054 | +{ |
| 2055 | + Q_Q(StretchFlickable); |
| 2056 | + switch (fixupMode) { |
| 2057 | + case Immediate: |
| 2058 | + timeline.set(data.move, toPos); |
| 2059 | + break; |
| 2060 | + case ExtentChanged: |
| 2061 | + // The target has changed. Don't start from the beginning; just complete the |
| 2062 | + // second half of the animation using the new extent. |
| 2063 | + timeline.move(data.move, toPos, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); |
| 2064 | + data.fixingUp = true; |
| 2065 | + break; |
| 2066 | + default: { |
| 2067 | + if (data.transitionToBounds && data.transitionToBounds->startTransition(&data, toPos)) { |
| 2068 | + q->movementStarting(); |
| 2069 | + data.fixingUp = true; |
| 2070 | + } else { |
| 2071 | + qreal dist = toPos - data.move; |
| 2072 | + timeline.move(data.move, toPos - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4); |
| 2073 | + timeline.move(data.move, toPos, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); |
| 2074 | + data.fixingUp = true; |
| 2075 | + } |
| 2076 | + } |
| 2077 | + } |
| 2078 | +} |
| 2079 | + |
| 2080 | +void StretchFlickablePrivate::resetTimeline(AxisData &data) |
| 2081 | +{ |
| 2082 | + timeline.reset(data.move); |
| 2083 | + if (data.transitionToBounds) |
| 2084 | + data.transitionToBounds->stopTransition(); |
| 2085 | +} |
| 2086 | + |
| 2087 | +void StretchFlickablePrivate::clearTimeline() |
| 2088 | +{ |
| 2089 | + timeline.clear(); |
| 2090 | + if (hData.transitionToBounds) |
| 2091 | + hData.transitionToBounds->stopTransition(); |
| 2092 | + if (vData.transitionToBounds) |
| 2093 | + vData.transitionToBounds->stopTransition(); |
| 2094 | +} |
| 2095 | + |
| 2096 | +/*! |
| 2097 | + \internal |
| 2098 | + |
| 2099 | + This function should be called after the contentItem has been moved, either programmatically, |
| 2100 | + or by the timeline (as a result of a flick). |
| 2101 | + It ensures that the contentItem will be moved back into bounds, |
| 2102 | + in case it was flicked outside of the visible area. |
| 2103 | + |
| 2104 | + The positional adjustment will usually be animated by the timeline, unless the fixupMode is set to Immediate. |
| 2105 | +*/ |
| 2106 | +void StretchFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) |
| 2107 | +{ |
| 2108 | + if (data.move.value() >= minExtent || maxExtent > minExtent) { |
| 2109 | + resetTimeline(data); |
| 2110 | + if (data.move.value() != minExtent) { |
| 2111 | + adjustContentPos(data, minExtent); |
| 2112 | + } |
| 2113 | + } else if (data.move.value() <= maxExtent) { |
| 2114 | + resetTimeline(data); |
| 2115 | + adjustContentPos(data, maxExtent); |
| 2116 | + } else if (-std::round(-data.move.value()) != data.move.value()) { |
| 2117 | + // We could animate, but since it is less than 0.5 pixel it's probably not worthwhile. |
| 2118 | + resetTimeline(data); |
| 2119 | + qreal val = data.move.value(); |
| 2120 | + if (std::abs(std::round(val) - val) < 0.25) // round small differences |
| 2121 | + val = std::round(val); |
| 2122 | + else if (data.smoothVelocity.value() > 0) // continue direction of motion for larger |
| 2123 | + val = std::ceil(val); |
| 2124 | + else if (data.smoothVelocity.value() < 0) |
| 2125 | + val = std::floor(val); |
| 2126 | + else // otherwise round |
| 2127 | + val = std::round(val); |
| 2128 | + timeline.set(data.move, val); |
| 2129 | + } |
| 2130 | + data.inOvershoot = false; |
| 2131 | + fixupMode = Normal; |
| 2132 | + data.vTime = timeline.time(); |
| 2133 | +} |
| 2134 | + |
| 2135 | +static bool fuzzyLessThanOrEqualTo(qreal a, qreal b) |
| 2136 | +{ |
| 2137 | + if (a == 0.0 || b == 0.0) { |
| 2138 | + // qFuzzyCompare is broken |
| 2139 | + a += 1.0; |
| 2140 | + b += 1.0; |
| 2141 | + } |
| 2142 | + return a <= b || qFuzzyCompare(a, b); |
| 2143 | +} |
| 2144 | + |
| 2145 | +/*! |
| 2146 | + \internal |
| 2147 | + |
| 2148 | + This function's main purpose is to update the atBeginning and atEnd flags |
| 2149 | + in hData and vData. It should be called when the contentItem has moved, |
| 2150 | + to ensure that hData and vData are up to date. |
| 2151 | + |
| 2152 | + The origin will also be updated, if AxisData::markExtentsDirty has been called |
| 2153 | +*/ |
| 2154 | +void StretchFlickablePrivate::updateBeginningEnd() |
| 2155 | +{ |
| 2156 | + Q_Q(StretchFlickable); |
| 2157 | + bool atXBeginningChange = false, atXEndChange = false; |
| 2158 | + bool atYBeginningChange = false, atYEndChange = false; |
| 2159 | + |
| 2160 | + // Vertical |
| 2161 | + const qreal maxyextent = -q->maxYExtent(); |
| 2162 | + const qreal minyextent = -q->minYExtent(); |
| 2163 | + const qreal ypos = pixelAligned ? -std::round(vData.move.value()) : -vData.move.value(); |
| 2164 | + bool atBeginning = fuzzyLessThanOrEqualTo(ypos, std::ceil(minyextent)); |
| 2165 | + bool atEnd = fuzzyLessThanOrEqualTo(std::floor(maxyextent), ypos); |
| 2166 | + |
| 2167 | + if (atBeginning != vData.atBeginning) { |
| 2168 | + vData.atBeginning = atBeginning; |
| 2169 | + atYBeginningChange = true; |
| 2170 | + if (!vData.moving && atBeginning) |
| 2171 | + vData.smoothVelocity.setValue(0); |
| 2172 | + } |
| 2173 | + if (atEnd != vData.atEnd) { |
| 2174 | + vData.atEnd = atEnd; |
| 2175 | + atYEndChange = true; |
| 2176 | + if (!vData.moving && atEnd) |
| 2177 | + vData.smoothVelocity.setValue(0); |
| 2178 | + } |
| 2179 | + |
| 2180 | + // Horizontal |
| 2181 | + const qreal maxxextent = -q->maxXExtent(); |
| 2182 | + const qreal minxextent = -q->minXExtent(); |
| 2183 | + const qreal xpos = pixelAligned ? -std::round(hData.move.value()) : -hData.move.value(); |
| 2184 | + atBeginning = fuzzyLessThanOrEqualTo(xpos, std::ceil(minxextent)); |
| 2185 | + atEnd = fuzzyLessThanOrEqualTo(std::floor(maxxextent), xpos); |
| 2186 | + |
| 2187 | + if (atBeginning != hData.atBeginning) { |
| 2188 | + hData.atBeginning = atBeginning; |
| 2189 | + atXBeginningChange = true; |
| 2190 | + if (!hData.moving && atBeginning) |
| 2191 | + hData.smoothVelocity.setValue(0); |
| 2192 | + } |
| 2193 | + if (atEnd != hData.atEnd) { |
| 2194 | + hData.atEnd = atEnd; |
| 2195 | + atXEndChange = true; |
| 2196 | + if (!hData.moving && atEnd) |
| 2197 | + hData.smoothVelocity.setValue(0); |
| 2198 | + } |
| 2199 | + |
| 2200 | + if (vData.extentsChanged) { |
| 2201 | + vData.extentsChanged = false; |
| 2202 | + qreal originY = q->originY(); |
| 2203 | + if (vData.origin != originY) { |
| 2204 | + vData.origin = originY; |
| 2205 | + emit q->originYChanged(); |
| 2206 | + } |
| 2207 | + } |
| 2208 | + |
| 2209 | + if (hData.extentsChanged) { |
| 2210 | + hData.extentsChanged = false; |
| 2211 | + qreal originX = q->originX(); |
| 2212 | + if (hData.origin != originX) { |
| 2213 | + hData.origin = originX; |
| 2214 | + emit q->originXChanged(); |
| 2215 | + } |
| 2216 | + } |
| 2217 | + |
| 2218 | + if (atXEndChange || atYEndChange || atXBeginningChange || atYBeginningChange) |
| 2219 | + emit q->isAtBoundaryChanged(); |
| 2220 | + if (atXEndChange) |
| 2221 | + emit q->atXEndChanged(); |
| 2222 | + if (atXBeginningChange) |
| 2223 | + emit q->atXBeginningChanged(); |
| 2224 | + if (atYEndChange) |
| 2225 | + emit q->atYEndChanged(); |
| 2226 | + if (atYBeginningChange) |
| 2227 | + emit q->atYBeginningChanged(); |
| 2228 | + |
| 2229 | + if (visibleArea) |
| 2230 | + visibleArea->updateVisible(); |
| 2231 | +} |
| 2232 | + |
| 2233 | +/*! |
| 2234 | + \qmlsignal QtQuick::Flickable::dragStarted() |
| 2235 | + |
| 2236 | + This signal is emitted when the view starts to be dragged due to user |
| 2237 | + interaction. |
| 2238 | +*/ |
| 2239 | + |
| 2240 | +/*! |
| 2241 | + \qmlsignal QtQuick::Flickable::dragEnded() |
| 2242 | + |
| 2243 | + This signal is emitted when the user stops dragging the view. |
| 2244 | + |
| 2245 | + If the velocity of the drag is sufficient at the time the |
| 2246 | + touch/mouse button is released then a flick will start. |
| 2247 | +*/ |
| 2248 | + |
| 2249 | +/*! |
| 2250 | + \qmltype Flickable |
| 2251 | + \nativetype StretchFlickable |
| 2252 | + \inqmlmodule QtQuick |
| 2253 | + \ingroup qtquick-input |
| 2254 | + \ingroup qtquick-containers |
| 2255 | + |
| 2256 | + \brief Provides a surface that can be "flicked". |
| 2257 | + \inherits Item |
| 2258 | + |
| 2259 | + The Flickable item places its children on a surface that can be dragged |
| 2260 | + and flicked, causing the view onto the child items to scroll. This |
| 2261 | + behavior forms the basis of Items that are designed to show large numbers |
| 2262 | + of child items, such as \l ListView and \l GridView. |
| 2263 | + |
| 2264 | + In traditional user interfaces, views can be scrolled using standard |
| 2265 | + controls, such as scroll bars and arrow buttons. In some situations, it |
| 2266 | + is also possible to drag the view directly by pressing and holding a |
| 2267 | + mouse button while moving the cursor. In touch-based user interfaces, |
| 2268 | + this dragging action is often complemented with a flicking action, where |
| 2269 | + scrolling continues after the user has stopped touching the view. |
| 2270 | + |
| 2271 | + Flickable does not automatically clip its contents. If it is not used as |
| 2272 | + a full-screen item, you should consider setting the \l{Item::}{clip} property |
| 2273 | + to true. |
| 2274 | + |
| 2275 | + \section1 Example Usage |
| 2276 | + |
| 2277 | + \div {class="float-right"} |
| 2278 | + \inlineimage flickable.gif |
| 2279 | + \enddiv |
| 2280 | + |
| 2281 | + The following example shows a small view onto a large image in which the |
| 2282 | + user can drag or flick the image in order to view different parts of it. |
| 2283 | + |
| 2284 | + \snippet qml/flickable.qml document |
| 2285 | + |
| 2286 | + \clearfloat |
| 2287 | + |
| 2288 | + Items declared as children of a Flickable are automatically parented to the |
| 2289 | + Flickable's \l contentItem. This should be taken into account when |
| 2290 | + operating on the children of the Flickable; it is usually the children of |
| 2291 | + \c contentItem that are relevant. For example, the bound of Items added |
| 2292 | + to the Flickable will be available by \c contentItem.childrenRect |
| 2293 | + |
| 2294 | + \section1 Examples of contentX and contentY |
| 2295 | + |
| 2296 | + The following images demonstrate a flickable being flicked in various |
| 2297 | + directions and the resulting \l contentX and \l contentY values. |
| 2298 | + The blue square represents the flickable's content, and the black |
| 2299 | + border represents the bounds of the flickable. |
| 2300 | + |
| 2301 | + \table |
| 2302 | + \row |
| 2303 | + \li \image flickable-contentXY-resting.png |
| 2304 | + \li The \c contentX and \c contentY are both \c 0. |
| 2305 | + \row |
| 2306 | + \li \image flickable-contentXY-top-left.png |
| 2307 | + \li The \c contentX and the \c contentY are both \c 50. |
| 2308 | + \row |
| 2309 | + \li \image flickable-contentXY-top-right.png |
| 2310 | + \li The \c contentX is \c -50 and the \c contentY is \c 50. |
| 2311 | + \row |
| 2312 | + \li \image flickable-contentXY-bottom-right.png |
| 2313 | + \li The \c contentX and the \c contentY are both \c -50. |
| 2314 | + \row |
| 2315 | + \li \image flickable-contentXY-bottom-left.png |
| 2316 | + \li The \c contentX is \c 50 and the \c contentY is \c -50. |
| 2317 | + \endtable |
| 2318 | + |
| 2319 | + \section1 Limitations |
| 2320 | + |
| 2321 | + \note Due to an implementation detail, items placed inside a Flickable |
| 2322 | + cannot anchor to the Flickable. Instead, use \l {Item::}{parent}, which |
| 2323 | + refers to the Flickable's \l contentItem. The size of the content item is |
| 2324 | + determined by \l contentWidth and \l contentHeight. |
| 2325 | +*/ |
| 2326 | + |
| 2327 | +/*! |
| 2328 | + \qmlsignal QtQuick::Flickable::movementStarted() |
| 2329 | + |
| 2330 | + This signal is emitted when the view begins moving due to user |
| 2331 | + interaction or a generated flick(). |
| 2332 | +*/ |
| 2333 | + |
| 2334 | +/*! |
| 2335 | + \qmlsignal QtQuick::Flickable::movementEnded() |
| 2336 | + |
| 2337 | + This signal is emitted when the view stops moving due to user |
| 2338 | + interaction or a generated flick(). If a flick was active, this signal will |
| 2339 | + be emitted once the flick stops. If a flick was not |
| 2340 | + active, this signal will be emitted when the |
| 2341 | + user stops dragging - i.e. a mouse or touch release. |
| 2342 | +*/ |
| 2343 | + |
| 2344 | +/*! |
| 2345 | + \qmlsignal QtQuick::Flickable::flickStarted() |
| 2346 | + |
| 2347 | + This signal is emitted when the view is flicked. A flick |
| 2348 | + starts from the point that the mouse or touch is released, |
| 2349 | + while still in motion. |
| 2350 | +*/ |
| 2351 | + |
| 2352 | +/*! |
| 2353 | + \qmlsignal QtQuick::Flickable::flickEnded() |
| 2354 | + |
| 2355 | + This signal is emitted when the view stops moving after a flick |
| 2356 | + or a series of flicks. |
| 2357 | +*/ |
| 2358 | + |
| 2359 | +/*! |
| 2360 | + \qmlpropertygroup QtQuick::Flickable::visibleArea |
| 2361 | + \qmlproperty real QtQuick::Flickable::visibleArea.xPosition |
| 2362 | + \qmlproperty real QtQuick::Flickable::visibleArea.widthRatio |
| 2363 | + \qmlproperty real QtQuick::Flickable::visibleArea.yPosition |
| 2364 | + \qmlproperty real QtQuick::Flickable::visibleArea.heightRatio |
| 2365 | + |
| 2366 | + These properties describe the position and size of the currently viewed area. |
| 2367 | + The size is defined as the percentage of the full view currently visible, |
| 2368 | + scaled to 0.0 - 1.0. The page position is usually in the range 0.0 (beginning) to |
| 2369 | + 1.0 minus size ratio (end), i.e. \c yPosition is in the range 0.0 to 1.0-\c heightRatio. |
| 2370 | + However, it is possible for the contents to be dragged outside of the normal |
| 2371 | + range, resulting in the page positions also being outside the normal range. |
| 2372 | + |
| 2373 | + These properties are typically used to draw a scrollbar. For example: |
| 2374 | + |
| 2375 | + \snippet qml/flickableScrollbar.qml 0 |
| 2376 | + \dots 8 |
| 2377 | + \snippet qml/flickableScrollbar.qml 1 |
| 2378 | +*/ |
| 2379 | +StretchFlickable::StretchFlickable(QQuickItem *parent) |
| 2380 | + : QQuickItem(*(new StretchFlickablePrivate), parent) |
| 2381 | +{ |
| 2382 | + Q_D(StretchFlickable); |
| 2383 | + d->init(); |
| 2384 | +} |
| 2385 | + |
| 2386 | +StretchFlickable::StretchFlickable(StretchFlickablePrivate &dd, QQuickItem *parent) |
| 2387 | + : QQuickItem(dd, parent) |
| 2388 | +{ |
| 2389 | + Q_D(StretchFlickable); |
| 2390 | + d->init(); |
| 2391 | +} |
| 2392 | + |
| 2393 | +StretchFlickable::~StretchFlickable() |
| 2394 | +{ |
| 2395 | +} |
| 2396 | + |
| 2397 | +/*! |
| 2398 | + \qmlproperty real QtQuick::Flickable::contentX |
| 2399 | + \qmlproperty real QtQuick::Flickable::contentY |
| 2400 | + |
| 2401 | + These properties hold the surface coordinate currently at the top-left |
| 2402 | + corner of the Flickable. For example, if you flick an image up 100 pixels, |
| 2403 | + \c contentY will increase by 100. |
| 2404 | + |
| 2405 | + \note If you flick back to the origin (the top-left corner), after the |
| 2406 | + rebound animation, \c contentX will settle to the same value as \c originX, |
| 2407 | + and \c contentY to \c originY. These are usually (0,0), however ListView |
| 2408 | + and GridView may have an arbitrary origin due to delegate size variation, |
| 2409 | + or item insertion/removal outside the visible region. So if you want to |
| 2410 | + implement something like a vertical scrollbar, one way is to use |
| 2411 | + \c {y: (contentY - originY) * (height / contentHeight)} |
| 2412 | + for the position; another way is to use the normalized values in |
| 2413 | + \l {QtQuick::Flickable::visibleArea}{visibleArea}. |
| 2414 | + |
| 2415 | + \sa {Examples of contentX and contentY}, originX, originY |
| 2416 | +*/ |
| 2417 | +qreal StretchFlickable::contentX() const |
| 2418 | +{ |
| 2419 | + Q_D(const StretchFlickable); |
| 2420 | + return -d->contentItem->x(); |
| 2421 | +} |
| 2422 | + |
| 2423 | +void StretchFlickable::setContentX(qreal pos) |
| 2424 | +{ |
| 2425 | + Q_D(StretchFlickable); |
| 2426 | + d->hData.explicitValue = true; |
| 2427 | + d->resetTimeline(d->hData); |
| 2428 | + d->hData.vTime = d->timeline.time(); |
| 2429 | + if (isMoving() || isFlicking()) |
| 2430 | + movementEnding(true, false); |
| 2431 | + if (!qFuzzyCompare(-pos, d->hData.move.value())) { |
| 2432 | + d->hData.contentPositionChangedExternallyDuringDrag = d->hData.dragging; |
| 2433 | + d->hData.move.setValue(-pos); |
| 2434 | + d->hData.contentPositionChangedExternallyDuringDrag = false; |
| 2435 | + } |
| 2436 | +} |
| 2437 | + |
| 2438 | +qreal StretchFlickable::contentY() const |
| 2439 | +{ |
| 2440 | + Q_D(const StretchFlickable); |
| 2441 | + return -d->contentItem->y(); |
| 2442 | +} |
| 2443 | + |
| 2444 | +void StretchFlickable::setContentY(qreal pos) |
| 2445 | +{ |
| 2446 | + Q_D(StretchFlickable); |
| 2447 | + d->vData.explicitValue = true; |
| 2448 | + d->resetTimeline(d->vData); |
| 2449 | + d->vData.vTime = d->timeline.time(); |
| 2450 | + if (isMoving() || isFlicking()) |
| 2451 | + movementEnding(false, true); |
| 2452 | + if (!qFuzzyCompare(-pos, d->vData.move.value())) { |
| 2453 | + d->vData.contentPositionChangedExternallyDuringDrag = d->vData.dragging; |
| 2454 | + d->vData.move.setValue(-pos); |
| 2455 | + d->vData.contentPositionChangedExternallyDuringDrag = false; |
| 2456 | + } |
| 2457 | +} |
| 2458 | + |
| 2459 | +/*! |
| 2460 | + \qmlproperty bool QtQuick::Flickable::interactive |
| 2461 | + |
| 2462 | + This property describes whether the user can interact with the Flickable. |
| 2463 | + A user cannot drag or flick a Flickable that is not interactive. |
| 2464 | + |
| 2465 | + By default, this property is true. |
| 2466 | + |
| 2467 | + This property is useful for temporarily disabling flicking. This allows |
| 2468 | + special interaction with Flickable's children; for example, you might want |
| 2469 | + to freeze a flickable map while scrolling through a pop-up dialog that |
| 2470 | + is a child of the Flickable. |
| 2471 | +*/ |
| 2472 | +bool StretchFlickable::isInteractive() const |
| 2473 | +{ |
| 2474 | + Q_D(const StretchFlickable); |
| 2475 | + return d->interactive; |
| 2476 | +} |
| 2477 | + |
| 2478 | +void StretchFlickable::setInteractive(bool interactive) |
| 2479 | +{ |
| 2480 | + Q_D(StretchFlickable); |
| 2481 | + if (interactive != d->interactive) { |
| 2482 | + d->interactive = interactive; |
| 2483 | + if (!interactive) { |
| 2484 | + d->cancelInteraction(); |
| 2485 | + } |
| 2486 | + emit interactiveChanged(); |
| 2487 | + } |
| 2488 | +} |
| 2489 | + |
| 2490 | +/*! |
| 2491 | + \qmlproperty real QtQuick::Flickable::horizontalVelocity |
| 2492 | + \qmlproperty real QtQuick::Flickable::verticalVelocity |
| 2493 | + |
| 2494 | + The instantaneous velocity of movement along the x and y axes, in pixels/sec. |
| 2495 | + |
| 2496 | + The reported velocity is smoothed to avoid erratic output. |
| 2497 | + |
| 2498 | + Note that for views with a large content size (more than 10 times the view size), |
| 2499 | + the velocity of the flick may exceed the velocity of the touch in the case |
| 2500 | + of multiple quick consecutive flicks. This allows the user to flick faster |
| 2501 | + through large content. |
| 2502 | +*/ |
| 2503 | +qreal StretchFlickable::horizontalVelocity() const |
| 2504 | +{ |
| 2505 | + Q_D(const StretchFlickable); |
| 2506 | + return d->hData.smoothVelocity.value(); |
| 2507 | +} |
| 2508 | + |
| 2509 | +qreal StretchFlickable::verticalVelocity() const |
| 2510 | +{ |
| 2511 | + Q_D(const StretchFlickable); |
| 2512 | + return d->vData.smoothVelocity.value(); |
| 2513 | +} |
| 2514 | + |
| 2515 | +/*! |
| 2516 | + \qmlproperty bool QtQuick::Flickable::atXBeginning |
| 2517 | + \qmlproperty bool QtQuick::Flickable::atXEnd |
| 2518 | + \qmlproperty bool QtQuick::Flickable::atYBeginning |
| 2519 | + \qmlproperty bool QtQuick::Flickable::atYEnd |
| 2520 | + |
| 2521 | + These properties are true if the flickable view is positioned at the beginning, |
| 2522 | + or end respectively. |
| 2523 | +*/ |
| 2524 | +bool StretchFlickable::isAtXEnd() const |
| 2525 | +{ |
| 2526 | + Q_D(const StretchFlickable); |
| 2527 | + return d->hData.atEnd; |
| 2528 | +} |
| 2529 | + |
| 2530 | +bool StretchFlickable::isAtXBeginning() const |
| 2531 | +{ |
| 2532 | + Q_D(const StretchFlickable); |
| 2533 | + return d->hData.atBeginning; |
| 2534 | +} |
| 2535 | + |
| 2536 | +bool StretchFlickable::isAtYEnd() const |
| 2537 | +{ |
| 2538 | + Q_D(const StretchFlickable); |
| 2539 | + return d->vData.atEnd; |
| 2540 | +} |
| 2541 | + |
| 2542 | +bool StretchFlickable::isAtYBeginning() const |
| 2543 | +{ |
| 2544 | + Q_D(const StretchFlickable); |
| 2545 | + return d->vData.atBeginning; |
| 2546 | +} |
| 2547 | + |
| 2548 | +/*! |
| 2549 | + \qmlproperty Item QtQuick::Flickable::contentItem |
| 2550 | + |
| 2551 | + The internal item that contains the Items to be moved in the Flickable. |
| 2552 | + |
| 2553 | + Items declared as children of a Flickable are automatically parented to the Flickable's contentItem. |
| 2554 | + |
| 2555 | + Items created dynamically need to be explicitly parented to the \e contentItem: |
| 2556 | + \code |
| 2557 | + Flickable { |
| 2558 | + id: myFlickable |
| 2559 | + function addItem(file) { |
| 2560 | + var component = Qt.createComponent(file) |
| 2561 | + component.createObject(myFlickable.contentItem); |
| 2562 | + } |
| 2563 | + } |
| 2564 | + \endcode |
| 2565 | +*/ |
| 2566 | +QQuickItem *StretchFlickable::contentItem() const |
| 2567 | +{ |
| 2568 | + Q_D(const StretchFlickable); |
| 2569 | + return d->contentItem; |
| 2570 | +} |
| 2571 | + |
| 2572 | +StretchFlickableVisibleArea *StretchFlickable::visibleArea() |
| 2573 | +{ |
| 2574 | + Q_D(StretchFlickable); |
| 2575 | + if (!d->visibleArea) { |
| 2576 | + d->visibleArea = new StretchFlickableVisibleArea(this); |
| 2577 | + d->visibleArea->updateVisible(); // calculate initial ratios |
| 2578 | + } |
| 2579 | + return d->visibleArea; |
| 2580 | +} |
| 2581 | + |
| 2582 | +/*! |
| 2583 | + \qmlproperty enumeration QtQuick::Flickable::flickableDirection |
| 2584 | + |
| 2585 | + This property determines which directions the view can be flicked. |
| 2586 | + |
| 2587 | + \list |
| 2588 | + \li Flickable.AutoFlickDirection (default) - allows flicking vertically if the |
| 2589 | + \e contentHeight is not equal to the \e height of the Flickable. |
| 2590 | + Allows flicking horizontally if the \e contentWidth is not equal |
| 2591 | + to the \e width of the Flickable. |
| 2592 | + \li Flickable.AutoFlickIfNeeded - allows flicking vertically if the |
| 2593 | + \e contentHeight is greater than the \e height of the Flickable. |
| 2594 | + Allows flicking horizontally if the \e contentWidth is greater than |
| 2595 | + to the \e width of the Flickable. (since \c{QtQuick 2.7}) |
| 2596 | + \li Flickable.HorizontalFlick - allows flicking horizontally. |
| 2597 | + \li Flickable.VerticalFlick - allows flicking vertically. |
| 2598 | + \li Flickable.HorizontalAndVerticalFlick - allows flicking in both directions. |
| 2599 | + \endlist |
| 2600 | +*/ |
| 2601 | +StretchFlickable::FlickableDirection StretchFlickable::flickableDirection() const |
| 2602 | +{ |
| 2603 | + Q_D(const StretchFlickable); |
| 2604 | + return d->flickableDirection; |
| 2605 | +} |
| 2606 | + |
| 2607 | +void StretchFlickable::setFlickableDirection(FlickableDirection direction) |
| 2608 | +{ |
| 2609 | + Q_D(StretchFlickable); |
| 2610 | + if (direction != d->flickableDirection) { |
| 2611 | + d->flickableDirection = direction; |
| 2612 | + emit flickableDirectionChanged(); |
| 2613 | + } |
| 2614 | +} |
| 2615 | + |
| 2616 | +/*! |
| 2617 | + \qmlproperty bool QtQuick::Flickable::pixelAligned |
| 2618 | + |
| 2619 | + This property sets the alignment of \l contentX and \l contentY to |
| 2620 | + pixels (\c true) or subpixels (\c false). |
| 2621 | + |
| 2622 | + Enable pixelAligned to optimize for still content or moving content with |
| 2623 | + high constrast edges, such as one-pixel-wide lines, text or vector graphics. |
| 2624 | + Disable pixelAligned when optimizing for animation quality. |
| 2625 | + |
| 2626 | + The default is \c false. |
| 2627 | +*/ |
| 2628 | +bool StretchFlickable::pixelAligned() const |
| 2629 | +{ |
| 2630 | + Q_D(const StretchFlickable); |
| 2631 | + return d->pixelAligned; |
| 2632 | +} |
| 2633 | + |
| 2634 | +void StretchFlickable::setPixelAligned(bool align) |
| 2635 | +{ |
| 2636 | + Q_D(StretchFlickable); |
| 2637 | + if (align != d->pixelAligned) { |
| 2638 | + d->pixelAligned = align; |
| 2639 | + emit pixelAlignedChanged(); |
| 2640 | + } |
| 2641 | +} |
| 2642 | + |
| 2643 | +/*! |
| 2644 | + \qmlproperty bool QtQuick::Flickable::synchronousDrag |
| 2645 | + \since 5.12 |
| 2646 | + |
| 2647 | + If this property is set to true, then when the mouse or touchpoint moves |
| 2648 | + far enough to begin dragging the content, the content will jump, such that |
| 2649 | + the content pixel which was under the cursor or touchpoint when pressed |
| 2650 | + remains under that point. |
| 2651 | + |
| 2652 | + The default is \c false, which provides a smoother experience (no jump) |
| 2653 | + at the cost that some of the drag distance is "lost" at the beginning. |
| 2654 | +*/ |
| 2655 | +bool StretchFlickable::synchronousDrag() const |
| 2656 | +{ |
| 2657 | + Q_D(const StretchFlickable); |
| 2658 | + return d->syncDrag; |
| 2659 | +} |
| 2660 | + |
| 2661 | +void StretchFlickable::setSynchronousDrag(bool v) |
| 2662 | +{ |
| 2663 | + Q_D(StretchFlickable); |
| 2664 | + if (v != d->syncDrag) { |
| 2665 | + d->syncDrag = v; |
| 2666 | + emit synchronousDragChanged(); |
| 2667 | + } |
| 2668 | +} |
| 2669 | + |
| 2670 | +/*! |
| 2671 | + \qmlproperty flags QtQuick::Flickable::acceptedButtons |
| 2672 | + \since 6.9 |
| 2673 | + |
| 2674 | + The mouse buttons that can be used to scroll this Flickable by dragging. |
| 2675 | + |
| 2676 | + By default, this property is set to \l {QtQuick::MouseEvent::button} {Qt.LeftButton}, |
| 2677 | + which provides the same behavior as in previous Qt versions; but in most |
| 2678 | + user interfaces, this behavior is unexpected. Users expect to flick only on |
| 2679 | + a touchscreen, and to use the mouse wheel, touchpad gestures or a scroll |
| 2680 | + bar with mouse or touchpad. Set it to \c Qt.NoButton to disable dragging. |
| 2681 | + |
| 2682 | + It can be set to an OR combination of mouse buttons, and will ignore events |
| 2683 | + from other buttons. |
| 2684 | +*/ |
| 2685 | +Qt::MouseButtons StretchFlickable::acceptedButtons() const |
| 2686 | +{ |
| 2687 | + Q_D(const StretchFlickable); |
| 2688 | + return d->acceptedButtons; |
| 2689 | +} |
| 2690 | + |
| 2691 | +void StretchFlickable::setAcceptedButtons(Qt::MouseButtons buttons) |
| 2692 | +{ |
| 2693 | + Q_D(StretchFlickable); |
| 2694 | + if (d->acceptedButtons == buttons) |
| 2695 | + return; |
| 2696 | + |
| 2697 | + d->acceptedButtons = buttons; |
| 2698 | + setAcceptedMouseButtons(buttons); |
| 2699 | + emit acceptedButtonsChanged(); |
| 2700 | +} |
| 2701 | + |
| 2702 | +/*! \internal |
| 2703 | + Take the velocity of the first point from the given \a event and transform |
| 2704 | + it to the local coordinate system (taking scale and rotation into account). |
| 2705 | +*/ |
| 2706 | +QVector2D StretchFlickablePrivate::firstPointLocalVelocity(QPointerEvent *event) |
| 2707 | +{ |
| 2708 | + QTransform transform = windowToItemTransform(); |
| 2709 | + // rotate and scale the velocity vector from scene to local |
| 2710 | + return QVector2D(transform.map(event->point(0).velocity().toPointF()) - transform.map(QPointF())); |
| 2711 | +} |
| 2712 | + |
| 2713 | +qint64 StretchFlickablePrivate::computeCurrentTime(QInputEvent *event) const |
| 2714 | +{ |
| 2715 | + if (0 != event->timestamp()) |
| 2716 | + return event->timestamp(); |
| 2717 | + if (!timer.isValid()) |
| 2718 | + return 0LL; |
| 2719 | + return timer.elapsed(); |
| 2720 | +} |
| 2721 | + |
| 2722 | +qreal StretchFlickablePrivate::devicePixelRatio() const |
| 2723 | +{ |
| 2724 | + return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio()); |
| 2725 | +} |
| 2726 | + |
| 2727 | +void StretchFlickablePrivate::handlePressEvent(QPointerEvent *event) |
| 2728 | +{ |
| 2729 | + Q_Q(StretchFlickable); |
| 2730 | + timer.start(); |
| 2731 | + if (interactive && timeline.isActive() |
| 2732 | + && ((qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity && !hData.fixingUp && !hData.inOvershoot) |
| 2733 | + || (qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity && !vData.fixingUp && !vData.inOvershoot))) { |
| 2734 | + stealMouse = true; // If we've been flicked then steal the click. |
| 2735 | + int flickTime = timeline.time(); |
| 2736 | + if (flickTime > 600) { |
| 2737 | + // too long between flicks - cancel boost |
| 2738 | + hData.continuousFlickVelocity = 0; |
| 2739 | + vData.continuousFlickVelocity = 0; |
| 2740 | + flickBoost = 1.0; |
| 2741 | + } else { |
| 2742 | + hData.continuousFlickVelocity = -hData.smoothVelocity.value(); |
| 2743 | + vData.continuousFlickVelocity = -vData.smoothVelocity.value(); |
| 2744 | + if (flickTime > 300) // slower flicking - reduce boost |
| 2745 | + flickBoost = qMax(1.0, flickBoost - 0.5); |
| 2746 | + } |
| 2747 | + } else { |
| 2748 | + stealMouse = false; |
| 2749 | + hData.continuousFlickVelocity = 0; |
| 2750 | + vData.continuousFlickVelocity = 0; |
| 2751 | + flickBoost = 1.0; |
| 2752 | + } |
| 2753 | + q->setKeepMouseGrab(stealMouse); |
| 2754 | + |
| 2755 | + maybeBeginDrag(computeCurrentTime(event), event->points().first().position(), |
| 2756 | + event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->buttons() |
| 2757 | + : Qt::NoButton); |
| 2758 | +} |
| 2759 | + |
| 2760 | +void StretchFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn, Qt::MouseButtons buttons) |
| 2761 | +{ |
| 2762 | + Q_Q(StretchFlickable); |
| 2763 | + clearDelayedPress(); |
| 2764 | + // consider dragging only when buttons intersect acceptedButtons, or it's a touch event which has no button |
| 2765 | + pressed = (buttons == Qt::NoButton) || (acceptedButtons != Qt::NoButton && (buttons & acceptedButtons) != 0); |
| 2766 | + |
| 2767 | + if (hData.transitionToBounds) |
| 2768 | + hData.transitionToBounds->stopTransition(); |
| 2769 | + if (vData.transitionToBounds) |
| 2770 | + vData.transitionToBounds->stopTransition(); |
| 2771 | + if (!hData.fixingUp) |
| 2772 | + resetTimeline(hData); |
| 2773 | + if (!vData.fixingUp) |
| 2774 | + resetTimeline(vData); |
| 2775 | + |
| 2776 | + hData.reset(); |
| 2777 | + vData.reset(); |
| 2778 | + hData.dragMinBound = q->minXExtent() - hData.startMargin; |
| 2779 | + vData.dragMinBound = q->minYExtent() - vData.startMargin; |
| 2780 | + hData.dragMaxBound = q->maxXExtent() + hData.endMargin; |
| 2781 | + vData.dragMaxBound = q->maxYExtent() + vData.endMargin; |
| 2782 | + fixupMode = Normal; |
| 2783 | + lastPos = QPointF(); |
| 2784 | + pressPos = pressPosn; |
| 2785 | + hData.pressPos = hData.move.value(); |
| 2786 | + vData.pressPos = vData.move.value(); |
| 2787 | + const bool wasFlicking = hData.flicking || vData.flicking; |
| 2788 | + hData.flickingWhenDragBegan = hData.flicking; |
| 2789 | + vData.flickingWhenDragBegan = vData.flicking; |
| 2790 | + if (hData.flicking) { |
| 2791 | + hData.flicking = false; |
| 2792 | + emit q->flickingHorizontallyChanged(); |
| 2793 | + } |
| 2794 | + if (vData.flicking) { |
| 2795 | + vData.flicking = false; |
| 2796 | + emit q->flickingVerticallyChanged(); |
| 2797 | + } |
| 2798 | + if (wasFlicking) |
| 2799 | + emit q->flickingChanged(); |
| 2800 | + lastPosTime = lastPressTime = currentTimestamp; |
| 2801 | + vData.velocityTime.start(); |
| 2802 | + hData.velocityTime.start(); |
| 2803 | +} |
| 2804 | + |
| 2805 | +void StretchFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, |
| 2806 | + const QVector2D &deltas, bool overThreshold, bool momentum, |
| 2807 | + bool velocitySensitiveOverBounds, const QVector2D &velocity) |
| 2808 | +{ |
| 2809 | + Q_Q(StretchFlickable); |
| 2810 | + bool rejectY = false; |
| 2811 | + bool rejectX = false; |
| 2812 | + |
| 2813 | + bool keepY = q->yflick(); |
| 2814 | + bool keepX = q->xflick(); |
| 2815 | + |
| 2816 | + bool stealY = false; |
| 2817 | + bool stealX = false; |
| 2818 | + if (eventType == QEvent::MouseMove) { |
| 2819 | + stealX = stealY = stealMouse; |
| 2820 | + } else if (eventType == QEvent::Wheel) { |
| 2821 | + stealX = stealY = scrollingPhase; |
| 2822 | + } |
| 2823 | + |
| 2824 | + bool prevHMoved = hMoved; |
| 2825 | + bool prevVMoved = vMoved; |
| 2826 | + |
| 2827 | + qint64 elapsedSincePress = currentTimestamp - lastPressTime; |
| 2828 | + qCDebug(lcFlickable).nospace() << currentTimestamp << ' ' << eventType << " drag @ " << localPos.x() << ',' << localPos.y() |
| 2829 | + << " \u0394 " << deltas.x() << ',' << deltas.y() << " vel " << velocity.x() << ',' << velocity.y() |
| 2830 | + << " thrsld? " << overThreshold << " momentum? " << momentum << " velSens? " << velocitySensitiveOverBounds |
| 2831 | + << " sincePress " << elapsedSincePress; |
| 2832 | + |
| 2833 | + if (q->yflick()) { |
| 2834 | + qreal dy = deltas.y(); |
| 2835 | + if (overThreshold || elapsedSincePress > 200) { |
| 2836 | + if (!vMoved) |
| 2837 | + vData.dragStartOffset = dy; |
| 2838 | + qreal newY = dy + vData.pressPos - (syncDrag ? 0 : vData.dragStartOffset); |
| 2839 | + // Recalculate bounds in case margins have changed, but use the content |
| 2840 | + // size estimate taken at the start of the drag in case the drag causes |
| 2841 | + // the estimate to be altered |
| 2842 | + const qreal minY = vData.dragMinBound + vData.startMargin; |
| 2843 | + const qreal maxY = vData.dragMaxBound - vData.endMargin; |
| 2844 | + if (!(boundsBehavior & StretchFlickable::DragOverBounds)) { |
| 2845 | + if (fuzzyLessThanOrEqualTo(newY, maxY)) { |
| 2846 | + newY = maxY; |
| 2847 | + rejectY = vData.pressPos == maxY && vData.move.value() == maxY && dy < 0; |
| 2848 | + } |
| 2849 | + if (fuzzyLessThanOrEqualTo(minY, newY)) { |
| 2850 | + newY = minY; |
| 2851 | + rejectY |= vData.pressPos == minY && vData.move.value() == minY && dy > 0; |
| 2852 | + } |
| 2853 | + } else { |
| 2854 | + qreal vel = velocity.y() / QML_FLICK_OVERSHOOTFRICTION; |
| 2855 | + if (vel > 0. && vel > vData.velocity) |
| 2856 | + vData.velocity = qMin(velocity.y() / QML_FLICK_OVERSHOOTFRICTION, maxVelocity); |
| 2857 | + else if (vel < 0. && vel < vData.velocity) |
| 2858 | + vData.velocity = qMax(velocity.y() / QML_FLICK_OVERSHOOTFRICTION, -maxVelocity); |
| 2859 | + if (newY > minY) { |
| 2860 | + // Overshoot beyond the top. But don't wait for momentum phase to end before returning to bounds. |
| 2861 | + if (momentum && vData.atBeginning) { |
| 2862 | + if (!vData.inRebound) { |
| 2863 | + vData.inRebound = true; |
| 2864 | + q->returnToBounds(); |
| 2865 | + } |
| 2866 | + return; |
| 2867 | + } |
| 2868 | + if (velocitySensitiveOverBounds) { |
| 2869 | + qreal overshoot = (newY - minY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 2870 | + overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| 2871 | + newY = minY + overshoot; |
| 2872 | + } else { |
| 2873 | + // Progressive drag-over-bounds resistance: small |
| 2874 | + // overshoots track the finger closely, larger ones |
| 2875 | + // increasingly resist, asymptoting at dragOvershootMax |
| 2876 | + // (never sliding past it) - see dragResistanceCurve(). |
| 2877 | + qreal overshoot = newY - minY; |
| 2878 | + overshoot = dragResistanceCurve(overshoot, dragOvershootRange(), dragOvershootMaxPixels()); |
| 2879 | + newY = minY + overshoot; |
| 2880 | + } |
| 2881 | + } else if (newY < maxY && maxY - minY <= 0) { |
| 2882 | + // Overshoot beyond the bottom. But don't wait for momentum phase to end before returning to bounds. |
| 2883 | + if (momentum && vData.atEnd) { |
| 2884 | + if (!vData.inRebound) { |
| 2885 | + vData.inRebound = true; |
| 2886 | + q->returnToBounds(); |
| 2887 | + } |
| 2888 | + return; |
| 2889 | + } |
| 2890 | + if (velocitySensitiveOverBounds) { |
| 2891 | + qreal overshoot = (newY - maxY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 2892 | + overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| 2893 | + newY = maxY - overshoot; |
| 2894 | + } else { |
| 2895 | + qreal overshoot = newY - maxY; |
| 2896 | + overshoot = dragResistanceCurve(overshoot, dragOvershootRange(), dragOvershootMaxPixels()); |
| 2897 | + newY = maxY + overshoot; |
| 2898 | + } |
| 2899 | + } |
| 2900 | + } |
| 2901 | + if (!rejectY && stealMouse && dy != vData.previousDragDelta) { |
| 2902 | + clearTimeline(); |
| 2903 | + vData.move.setValue(newY); |
| 2904 | + vMoved = true; |
| 2905 | + } |
| 2906 | + if (!rejectY && overThreshold) |
| 2907 | + stealY = true; |
| 2908 | + |
| 2909 | + if ((newY >= minY && vData.pressPos == minY && vData.move.value() == minY && dy > 0) |
| 2910 | + || (newY <= maxY && vData.pressPos == maxY && vData.move.value() == maxY && dy < 0)) { |
| 2911 | + keepY = false; |
| 2912 | + } |
| 2913 | + } |
| 2914 | + vData.previousDragDelta = dy; |
| 2915 | + } |
| 2916 | + |
| 2917 | + if (q->xflick()) { |
| 2918 | + qreal dx = deltas.x(); |
| 2919 | + if (overThreshold || elapsedSincePress > 200) { |
| 2920 | + if (!hMoved) |
| 2921 | + hData.dragStartOffset = dx; |
| 2922 | + qreal newX = dx + hData.pressPos - (syncDrag ? 0 : hData.dragStartOffset); |
| 2923 | + const qreal minX = hData.dragMinBound + hData.startMargin; |
| 2924 | + const qreal maxX = hData.dragMaxBound - hData.endMargin; |
| 2925 | + if (!(boundsBehavior & StretchFlickable::DragOverBounds)) { |
| 2926 | + if (fuzzyLessThanOrEqualTo(newX, maxX)) { |
| 2927 | + newX = maxX; |
| 2928 | + rejectX = hData.pressPos == maxX && hData.move.value() == maxX && dx < 0; |
| 2929 | + } |
| 2930 | + if (fuzzyLessThanOrEqualTo(minX, newX)) { |
| 2931 | + newX = minX; |
| 2932 | + rejectX |= hData.pressPos == minX && hData.move.value() == minX && dx > 0; |
| 2933 | + } |
| 2934 | + } else { |
| 2935 | + qreal vel = velocity.x() / QML_FLICK_OVERSHOOTFRICTION; |
| 2936 | + if (vel > 0. && vel > hData.velocity) |
| 2937 | + hData.velocity = qMin(velocity.x() / QML_FLICK_OVERSHOOTFRICTION, maxVelocity); |
| 2938 | + else if (vel < 0. && vel < hData.velocity) |
| 2939 | + hData.velocity = qMax(velocity.x() / QML_FLICK_OVERSHOOTFRICTION, -maxVelocity); |
| 2940 | + if (newX > minX) { |
| 2941 | + // Overshoot beyond the left. But don't wait for momentum phase to end before returning to bounds. |
| 2942 | + if (momentum && hData.atBeginning) { |
| 2943 | + if (!hData.inRebound) { |
| 2944 | + hData.inRebound = true; |
| 2945 | + q->returnToBounds(); |
| 2946 | + } |
| 2947 | + return; |
| 2948 | + } |
| 2949 | + if (velocitySensitiveOverBounds) { |
| 2950 | + qreal overshoot = (newX - minX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 2951 | + overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| 2952 | + newX = minX + overshoot; |
| 2953 | + } else { |
| 2954 | + qreal overshoot = newX - minX; |
| 2955 | + overshoot = dragResistanceCurve(overshoot, dragOvershootRange(), dragOvershootMaxPixels()); |
| 2956 | + newX = minX + overshoot; |
| 2957 | + } |
| 2958 | + } else if (newX < maxX && maxX - minX <= 0) { |
| 2959 | + // Overshoot beyond the right. But don't wait for momentum phase to end before returning to bounds. |
| 2960 | + if (momentum && hData.atEnd) { |
| 2961 | + if (!hData.inRebound) { |
| 2962 | + hData.inRebound = true; |
| 2963 | + q->returnToBounds(); |
| 2964 | + } |
| 2965 | + return; |
| 2966 | + } |
| 2967 | + if (velocitySensitiveOverBounds) { |
| 2968 | + qreal overshoot = (newX - maxX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION; |
| 2969 | + overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| 2970 | + newX = maxX - overshoot; |
| 2971 | + } else { |
| 2972 | + qreal overshoot = newX - maxX; |
| 2973 | + overshoot = dragResistanceCurve(overshoot, dragOvershootRange(), dragOvershootMaxPixels()); |
| 2974 | + newX = maxX + overshoot; |
| 2975 | + } |
| 2976 | + } |
| 2977 | + } |
| 2978 | + if (!rejectX && stealMouse && dx != hData.previousDragDelta) { |
| 2979 | + clearTimeline(); |
| 2980 | + hData.move.setValue(newX); |
| 2981 | + hMoved = true; |
| 2982 | + } |
| 2983 | + |
| 2984 | + if (!rejectX && overThreshold) |
| 2985 | + stealX = true; |
| 2986 | + |
| 2987 | + if ((newX >= minX && vData.pressPos == minX && vData.move.value() == minX && dx > 0) |
| 2988 | + || (newX <= maxX && vData.pressPos == maxX && vData.move.value() == maxX && dx < 0)) { |
| 2989 | + keepX = false; |
| 2990 | + } |
| 2991 | + } |
| 2992 | + hData.previousDragDelta = dx; |
| 2993 | + } |
| 2994 | + |
| 2995 | + stealMouse = stealX || stealY; |
| 2996 | + if (stealMouse) { |
| 2997 | + if ((stealX && keepX) || (stealY && keepY)) |
| 2998 | + q->setKeepMouseGrab(true); |
| 2999 | + clearDelayedPress(); |
| 3000 | + } |
| 3001 | + |
| 3002 | + if (rejectY) { |
| 3003 | + vData.velocityBuffer.clear(); |
| 3004 | + vData.velocity = 0; |
| 3005 | + } |
| 3006 | + if (rejectX) { |
| 3007 | + hData.velocityBuffer.clear(); |
| 3008 | + hData.velocity = 0; |
| 3009 | + } |
| 3010 | + |
| 3011 | + if (momentum && !hData.flicking && !vData.flicking) |
| 3012 | + flickingStarted(hData.velocity != 0, vData.velocity != 0); |
| 3013 | + draggingStarting(); |
| 3014 | + |
| 3015 | + if ((hMoved && !prevHMoved) || (vMoved && !prevVMoved)) |
| 3016 | + q->movementStarting(); |
| 3017 | + |
| 3018 | + lastPosTime = currentTimestamp; |
| 3019 | + if (q->yflick() && !rejectY) |
| 3020 | + vData.addVelocitySample(velocity.y(), maxVelocity); |
| 3021 | + if (q->xflick() && !rejectX) |
| 3022 | + hData.addVelocitySample(velocity.x(), maxVelocity); |
| 3023 | + lastPos = localPos; |
| 3024 | +} |
| 3025 | + |
| 3026 | +void StretchFlickablePrivate::handleMoveEvent(QPointerEvent *event) |
| 3027 | +{ |
| 3028 | + Q_Q(StretchFlickable); |
| 3029 | + if (!interactive || lastPosTime == -1 || |
| 3030 | + (event->isSinglePointEvent() && !buttonsAccepted(static_cast<QSinglePointEvent *>(event)))) |
| 3031 | + return; |
| 3032 | + |
| 3033 | + qint64 currentTimestamp = computeCurrentTime(event); |
| 3034 | + const auto &firstPoint = event->points().first(); |
| 3035 | + const auto &pos = firstPoint.position(); |
| 3036 | + const QVector2D deltas = QVector2D(pos - q->mapFromGlobal(firstPoint.globalPressPosition())); |
| 3037 | + const QVector2D velocity = firstPointLocalVelocity(event); |
| 3038 | + bool overThreshold = false; |
| 3039 | + |
| 3040 | + if (q->isMoving()) { |
| 3041 | + /* |
| 3042 | + Only the first drag should be used to determine if the Flickable should start moving, |
| 3043 | + to the exclusion of some inner Control (such as Slider) or a child Flickable. |
| 3044 | + If the user releases the mouse or finger and drags again, this Flickable is the only |
| 3045 | + sensible recipient as long as it's still moving. |
| 3046 | + We also only care about the drag threshold for the first drag. If it's already moving, |
| 3047 | + every subsequent move event (however small) should move the content item immediately. |
| 3048 | + */ |
| 3049 | + overThreshold = true; |
| 3050 | + } else if (event->pointCount() == 1) { |
| 3051 | + if (q->yflick()) |
| 3052 | + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); |
| 3053 | + if (q->xflick()) |
| 3054 | + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); |
| 3055 | + } else { |
| 3056 | + qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event; |
| 3057 | + } |
| 3058 | + |
| 3059 | + drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity); |
| 3060 | +} |
| 3061 | + |
| 3062 | +void StretchFlickablePrivate::handleReleaseEvent(QPointerEvent *event) |
| 3063 | +{ |
| 3064 | + Q_Q(StretchFlickable); |
| 3065 | + stealMouse = false; |
| 3066 | + q->setKeepMouseGrab(false); |
| 3067 | + pressed = false; |
| 3068 | + |
| 3069 | + // if we drag then pause before release we should not cause a flick. |
| 3070 | + qint64 elapsed = computeCurrentTime(event) - lastPosTime; |
| 3071 | + |
| 3072 | + vData.updateVelocity(); |
| 3073 | + hData.updateVelocity(); |
| 3074 | + |
| 3075 | + draggingEnding(); |
| 3076 | + |
| 3077 | + if (lastPosTime == -1) |
| 3078 | + return; |
| 3079 | + |
| 3080 | + hData.vTime = vData.vTime = timeline.time(); |
| 3081 | + |
| 3082 | + bool canBoost = false; |
| 3083 | + const auto pos = event->points().first().position(); |
| 3084 | + const auto pressPos = q->mapFromGlobal(event->points().first().globalPressPosition()); |
| 3085 | + const QVector2D eventVelocity = firstPointLocalVelocity(event); |
| 3086 | + qCDebug(lcVel) << event->deviceType() << event->type() << "velocity" << event->points().first().velocity() << "transformed to local" << eventVelocity; |
| 3087 | + |
| 3088 | + qreal vVelocity = 0; |
| 3089 | + if (elapsed < 100 && vData.velocity != 0.) { |
| 3090 | + vVelocity = (event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity) |
| 3091 | + ? eventVelocity.y() : vData.velocity); |
| 3092 | + } |
| 3093 | + if ((vData.atBeginning && vVelocity > 0.) || (vData.atEnd && vVelocity < 0.)) { |
| 3094 | + vVelocity /= 2; |
| 3095 | + } else if (vData.continuousFlickVelocity != 0.0 |
| 3096 | + && vData.viewSize/q->height() > QML_FLICK_MULTIFLICK_RATIO |
| 3097 | + && ((vVelocity > 0) == (vData.continuousFlickVelocity > 0)) |
| 3098 | + && qAbs(vVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) { |
| 3099 | + // accelerate flick for large view flicked quickly |
| 3100 | + canBoost = true; |
| 3101 | + } |
| 3102 | + |
| 3103 | + qreal hVelocity = 0; |
| 3104 | + if (elapsed < 100 && hData.velocity != 0.) { |
| 3105 | + hVelocity = (event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity) |
| 3106 | + ? eventVelocity.x() : hData.velocity); |
| 3107 | + } |
| 3108 | + if ((hData.atBeginning && hVelocity > 0.) || (hData.atEnd && hVelocity < 0.)) { |
| 3109 | + hVelocity /= 2; |
| 3110 | + } else if (hData.continuousFlickVelocity != 0.0 |
| 3111 | + && hData.viewSize/q->width() > QML_FLICK_MULTIFLICK_RATIO |
| 3112 | + && ((hVelocity > 0) == (hData.continuousFlickVelocity > 0)) |
| 3113 | + && qAbs(hVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) { |
| 3114 | + // accelerate flick for large view flicked quickly |
| 3115 | + canBoost = true; |
| 3116 | + } |
| 3117 | + |
| 3118 | + flickBoost = canBoost ? qBound(1.0, flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0; |
| 3119 | + const int flickThreshold = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickStartDistance).toInt(); |
| 3120 | + |
| 3121 | + bool anyPointGrabbed = event->points().constEnd() != |
| 3122 | + std::find_if(event->points().constBegin(),event->points().constEnd(), |
| 3123 | + [q, event](const QEventPoint &point) { return event->exclusiveGrabber(point) == q; }); |
| 3124 | + |
| 3125 | + bool flickedVertically = false; |
| 3126 | + vVelocity *= flickBoost; |
| 3127 | + const bool isVerticalFlickAllowed = anyPointGrabbed && |
| 3128 | + q->yflick() && qAbs(vVelocity) > _q_MinimumFlickVelocity && |
| 3129 | + qAbs(pos.y() - pressPos.y()) > flickThreshold; |
| 3130 | + if (isVerticalFlickAllowed) { |
| 3131 | + velocityTimeline.reset(vData.smoothVelocity); |
| 3132 | + vData.smoothVelocity.setValue(-vVelocity); |
| 3133 | + flickedVertically = flickY(event->type(), vVelocity); |
| 3134 | + } |
| 3135 | + |
| 3136 | + bool flickedHorizontally = false; |
| 3137 | + hVelocity *= flickBoost; |
| 3138 | + const bool isHorizontalFlickAllowed = anyPointGrabbed && |
| 3139 | + q->xflick() && qAbs(hVelocity) > _q_MinimumFlickVelocity && |
| 3140 | + qAbs(pos.x() - pressPos.x()) > flickThreshold; |
| 3141 | + if (isHorizontalFlickAllowed) { |
| 3142 | + velocityTimeline.reset(hData.smoothVelocity); |
| 3143 | + hData.smoothVelocity.setValue(-hVelocity); |
| 3144 | + flickedHorizontally = flickX(event->type(), hVelocity); |
| 3145 | + } |
| 3146 | + |
| 3147 | + if (!isVerticalFlickAllowed) |
| 3148 | + fixupY(); |
| 3149 | + |
| 3150 | + if (!isHorizontalFlickAllowed) |
| 3151 | + fixupX(); |
| 3152 | + |
| 3153 | + flickingStarted(flickedHorizontally, flickedVertically); |
| 3154 | + if (!isViewMoving()) { |
| 3155 | + q->movementEnding(); |
| 3156 | + } else { |
| 3157 | + if (flickedVertically) |
| 3158 | + vMoved = true; |
| 3159 | + if (flickedHorizontally) |
| 3160 | + hMoved = true; |
| 3161 | + q->movementStarting(); |
| 3162 | + } |
| 3163 | +} |
| 3164 | + |
| 3165 | +bool StretchFlickablePrivate::buttonsAccepted(const QSinglePointEvent *event) |
| 3166 | +{ |
| 3167 | + return !((event->button() & acceptedButtons) == 0 && (event->buttons() & acceptedButtons) == 0); |
| 3168 | +} |
| 3169 | + |
| 3170 | +void StretchFlickable::mousePressEvent(QMouseEvent *event) |
| 3171 | +{ |
| 3172 | + Q_D(StretchFlickable); |
| 3173 | + if (d->interactive && !d->replayingPressEvent && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) { |
| 3174 | + if (!d->pressed) |
| 3175 | + d->handlePressEvent(event); |
| 3176 | + event->accept(); |
| 3177 | + } else { |
| 3178 | + QQuickItem::mousePressEvent(event); |
| 3179 | + } |
| 3180 | +} |
| 3181 | + |
| 3182 | +void StretchFlickable::mouseMoveEvent(QMouseEvent *event) |
| 3183 | +{ |
| 3184 | + Q_D(StretchFlickable); |
| 3185 | + if (d->interactive && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) { |
| 3186 | + d->handleMoveEvent(event); |
| 3187 | + event->accept(); |
| 3188 | + } else { |
| 3189 | + QQuickItem::mouseMoveEvent(event); |
| 3190 | + } |
| 3191 | +} |
| 3192 | + |
| 3193 | +void StretchFlickable::mouseReleaseEvent(QMouseEvent *event) |
| 3194 | +{ |
| 3195 | + Q_D(StretchFlickable); |
| 3196 | + if (d->interactive && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) { |
| 3197 | + if (d->delayedPressEvent) { |
| 3198 | + d->replayDelayedPress(); |
| 3199 | + |
| 3200 | + auto &firstPoint = event->point(0); |
| 3201 | + if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) { |
| 3202 | + // Since we sent the delayed press to the window, we need to resend the release to the window too. |
| 3203 | + // We're not copying or detaching, so restore the original event position afterwards. |
| 3204 | + const auto oldPosition = firstPoint.position(); |
| 3205 | + QMutableEventPoint::setPosition(firstPoint, event->scenePosition()); |
| 3206 | + QCoreApplication::sendEvent(window(), event); |
| 3207 | + QMutableEventPoint::setPosition(firstPoint, oldPosition); |
| 3208 | + } |
| 3209 | + |
| 3210 | + // And the event has been consumed |
| 3211 | + d->stealMouse = false; |
| 3212 | + d->pressed = false; |
| 3213 | + return; |
| 3214 | + } |
| 3215 | + |
| 3216 | + d->handleReleaseEvent(event); |
| 3217 | + event->accept(); |
| 3218 | + } else { |
| 3219 | + QQuickItem::mouseReleaseEvent(event); |
| 3220 | + } |
| 3221 | +} |
| 3222 | + |
| 3223 | +void StretchFlickable::touchEvent(QTouchEvent *event) |
| 3224 | +{ |
| 3225 | + Q_D(StretchFlickable); |
| 3226 | + |
| 3227 | + if (event->type() == QEvent::TouchCancel) { |
| 3228 | + if (d->interactive && d->wantsPointerEvent(event)) |
| 3229 | + d->cancelInteraction(); |
| 3230 | + else |
| 3231 | + QQuickItem::touchEvent(event); |
| 3232 | + return; |
| 3233 | + } |
| 3234 | + |
| 3235 | + bool unhandled = false; |
| 3236 | + const auto &firstPoint = event->points().first(); |
| 3237 | + switch (firstPoint.state()) { |
| 3238 | + case QEventPoint::State::Pressed: |
| 3239 | + if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) { |
| 3240 | + if (!d->pressed) |
| 3241 | + d->handlePressEvent(event); |
| 3242 | + event->accept(); |
| 3243 | + } else { |
| 3244 | + unhandled = true; |
| 3245 | + } |
| 3246 | + break; |
| 3247 | + case QEventPoint::State::Updated: |
| 3248 | + if (d->interactive && d->wantsPointerEvent(event)) { |
| 3249 | + d->handleMoveEvent(event); |
| 3250 | + event->accept(); |
| 3251 | + } else { |
| 3252 | + unhandled = true; |
| 3253 | + } |
| 3254 | + break; |
| 3255 | + case QEventPoint::State::Released: |
| 3256 | + if (d->interactive && d->wantsPointerEvent(event)) { |
| 3257 | + if (d->delayedPressEvent) { |
| 3258 | + d->replayDelayedPress(); |
| 3259 | + |
| 3260 | + const auto &firstPoint = event->point(0); |
| 3261 | + if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) { |
| 3262 | + // Since we sent the delayed press to the window, we need to resend the release to the window too. |
| 3263 | + QScopedPointer<QPointerEvent> localizedEvent( |
| 3264 | + QQuickDeliveryAgentPrivate::clonePointerEvent(event, firstPoint.scenePosition())); |
| 3265 | + QCoreApplication::sendEvent(window(), localizedEvent.data()); |
| 3266 | + } |
| 3267 | + |
| 3268 | + // And the event has been consumed |
| 3269 | + d->stealMouse = false; |
| 3270 | + d->pressed = false; |
| 3271 | + return; |
| 3272 | + } |
| 3273 | + |
| 3274 | + d->handleReleaseEvent(event); |
| 3275 | + event->accept(); |
| 3276 | + } else { |
| 3277 | + unhandled = true; |
| 3278 | + } |
| 3279 | + break; |
| 3280 | + case QEventPoint::State::Stationary: |
| 3281 | + case QEventPoint::State::Unknown: |
| 3282 | + break; |
| 3283 | + } |
| 3284 | + if (unhandled) |
| 3285 | + QQuickItem::touchEvent(event); |
| 3286 | +} |
| 3287 | + |
| 3288 | +#if QT_CONFIG(wheelevent) |
| 3289 | +void StretchFlickable::wheelEvent(QWheelEvent *event) |
| 3290 | +{ |
| 3291 | + Q_D(StretchFlickable); |
| 3292 | + if (!d->interactive || !d->wantsPointerEvent(event)) { |
| 3293 | + QQuickItem::wheelEvent(event); |
| 3294 | + return; |
| 3295 | + } |
| 3296 | + qCDebug(lcWheel) << event->device() << event << event->source(); |
| 3297 | + event->setAccepted(false); |
| 3298 | + qint64 currentTimestamp = d->computeCurrentTime(event); |
| 3299 | + switch (event->phase()) { |
| 3300 | + case Qt::ScrollBegin: |
| 3301 | + d->scrollingPhase = true; |
| 3302 | + d->accumulatedWheelPixelDelta = QVector2D(); |
| 3303 | + d->vData.velocity = 0; |
| 3304 | + d->hData.velocity = 0; |
| 3305 | + d->timer.start(); |
| 3306 | + d->maybeBeginDrag(currentTimestamp, event->position()); |
| 3307 | + d->lastPosTime = -1; |
| 3308 | + break; |
| 3309 | + case Qt::NoScrollPhase: // default phase with an ordinary wheel mouse |
| 3310 | + case Qt::ScrollUpdate: |
| 3311 | + if (d->scrollingPhase) |
| 3312 | + d->pressed = true; |
| 3313 | + break; |
| 3314 | + case Qt::ScrollMomentum: |
| 3315 | + d->pressed = false; |
| 3316 | + d->scrollingPhase = false; |
| 3317 | + d->draggingEnding(); |
| 3318 | + if (isMoving()) |
| 3319 | + event->accept(); |
| 3320 | + d->lastPosTime = -1; |
| 3321 | + break; |
| 3322 | + case Qt::ScrollEnd: |
| 3323 | + d->pressed = false; |
| 3324 | + d->scrollingPhase = false; |
| 3325 | + d->draggingEnding(); |
| 3326 | + returnToBounds(); |
| 3327 | + d->lastPosTime = -1; |
| 3328 | + d->stealMouse = false; |
| 3329 | + if (!d->velocityTimeline.isActive() && !d->timeline.isActive()) |
| 3330 | + movementEnding(true, true); |
| 3331 | + return; |
| 3332 | + } |
| 3333 | + |
| 3334 | + qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / qreal(1000); |
| 3335 | + if (elapsed <= 0) { |
| 3336 | + d->lastPosTime = currentTimestamp; |
| 3337 | + qCDebug(lcWheel) << "insufficient elapsed time: can't calculate velocity" << elapsed; |
| 3338 | + return; |
| 3339 | + } |
| 3340 | + |
| 3341 | + if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull() || event->phase() == Qt::NoScrollPhase) { |
| 3342 | + // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta |
| 3343 | + int xDelta = event->angleDelta().x(); |
| 3344 | + int yDelta = event->angleDelta().y(); |
| 3345 | + |
| 3346 | + if (d->wheelDeceleration > _q_MaximumWheelDeceleration) { |
| 3347 | + const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24; |
| 3348 | + // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel |
| 3349 | + // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines() |
| 3350 | + if (yflick() && yDelta != 0) { |
| 3351 | + d->moveReason = StretchFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. |
| 3352 | + d->vMoved = true; |
| 3353 | + qreal scrollPixel = (-yDelta / 120.0 * wheelScroll); |
| 3354 | + bool acceptEvent = true; // Set to false if event should propagate to parent |
| 3355 | + if (scrollPixel > 0) { // Forward direction (away from user) |
| 3356 | + if (d->vData.move.value() >= minYExtent()) { |
| 3357 | + d->vMoved = false; |
| 3358 | + acceptEvent = false; |
| 3359 | + } |
| 3360 | + } else { // Backward direction (towards user) |
| 3361 | + if (d->vData.move.value() <= maxYExtent()) { |
| 3362 | + d->vMoved = false; |
| 3363 | + acceptEvent = false; |
| 3364 | + } |
| 3365 | + } |
| 3366 | + if (d->vMoved) { |
| 3367 | + if (d->boundsBehavior == StretchFlickable::StopAtBounds) { |
| 3368 | + const qreal estContentPos = scrollPixel + d->vData.move.value(); |
| 3369 | + if (scrollPixel > 0) { // Forward direction (away from user) |
| 3370 | + if (estContentPos > minYExtent()) { |
| 3371 | + scrollPixel = minYExtent() - d->vData.move.value(); |
| 3372 | + acceptEvent = false; |
| 3373 | + } |
| 3374 | + } else { // Backward direction (towards user) |
| 3375 | + if (estContentPos < maxYExtent()) { |
| 3376 | + scrollPixel = maxYExtent() - d->vData.move.value(); |
| 3377 | + acceptEvent = false; |
| 3378 | + } |
| 3379 | + } |
| 3380 | + } |
| 3381 | + d->resetTimeline(d->vData); |
| 3382 | + movementStarting(); |
| 3383 | + d->timeline.moveBy(d->vData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4); |
| 3384 | + d->vData.fixingUp = true; |
| 3385 | + d->timeline.callback(StretchTimeLineCallback(&d->vData.move, StretchFlickablePrivate::fixupY_callback, d)); |
| 3386 | + } |
| 3387 | + if (acceptEvent) |
| 3388 | + event->accept(); |
| 3389 | + } |
| 3390 | + if (xflick() && xDelta != 0) { |
| 3391 | + d->moveReason = StretchFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. |
| 3392 | + d->hMoved = true; |
| 3393 | + qreal scrollPixel = (-xDelta / 120.0 * wheelScroll); |
| 3394 | + bool acceptEvent = true; // Set to false if event should propagate to parent |
| 3395 | + if (scrollPixel > 0) { // Forward direction (away from user) |
| 3396 | + if (d->hData.move.value() >= minXExtent()) { |
| 3397 | + d->hMoved = false; |
| 3398 | + acceptEvent = false; |
| 3399 | + } |
| 3400 | + } else { // Backward direction (towards user) |
| 3401 | + if (d->hData.move.value() <= maxXExtent()) { |
| 3402 | + d->hMoved = false; |
| 3403 | + acceptEvent = false; |
| 3404 | + } |
| 3405 | + } |
| 3406 | + if (d->hMoved) { |
| 3407 | + if (d->boundsBehavior == StretchFlickable::StopAtBounds) { |
| 3408 | + const qreal estContentPos = scrollPixel + d->hData.move.value(); |
| 3409 | + if (scrollPixel > 0) { // Forward direction (away from user) |
| 3410 | + if (estContentPos > minXExtent()) { |
| 3411 | + scrollPixel = minXExtent() - d->hData.move.value(); |
| 3412 | + acceptEvent = false; |
| 3413 | + } |
| 3414 | + } else { // Backward direction (towards user) |
| 3415 | + if (estContentPos < maxXExtent()) { |
| 3416 | + scrollPixel = maxXExtent() - d->hData.move.value(); |
| 3417 | + acceptEvent = false; |
| 3418 | + } |
| 3419 | + } |
| 3420 | + } |
| 3421 | + d->resetTimeline(d->hData); |
| 3422 | + movementStarting(); |
| 3423 | + d->timeline.moveBy(d->hData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4); |
| 3424 | + d->hData.fixingUp = true; |
| 3425 | + d->timeline.callback(StretchTimeLineCallback(&d->hData.move, StretchFlickablePrivate::fixupX_callback, d)); |
| 3426 | + } |
| 3427 | + if (acceptEvent) |
| 3428 | + event->accept(); |
| 3429 | + } |
| 3430 | + } else { |
| 3431 | + // wheelDeceleration is set to some reasonable value: the user or the platform wants to have |
| 3432 | + // the classic Qt Quick mouse wheel acceleration behavior. |
| 3433 | + // For a single "clicky" wheel event (angleDelta +/- 120), |
| 3434 | + // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines(). |
| 3435 | + // The decel algo from there is |
| 3436 | + // qreal dist = v2 / (accel * 2.0); |
| 3437 | + // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2) |
| 3438 | + // now solve for dt: |
| 3439 | + // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance) |
| 3440 | + if (!isMoving()) |
| 3441 | + elapsed = 120 / qSqrt(d->wheelDeceleration * 2 * d->initialWheelFlickDistance); |
| 3442 | + if (yflick() && yDelta != 0) { |
| 3443 | + qreal instVelocity = yDelta / elapsed; |
| 3444 | + // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction |
| 3445 | + if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0)) |
| 3446 | + d->vData.velocityBuffer.clear(); |
| 3447 | + d->vData.addVelocitySample(instVelocity, d->maxVelocity); |
| 3448 | + d->vData.updateVelocity(); |
| 3449 | + if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) { |
| 3450 | + const bool newFlick = d->flickY(event->type(), d->vData.velocity); |
| 3451 | + if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) { |
| 3452 | + d->flickingStarted(false, true); |
| 3453 | + d->vMoved = true; |
| 3454 | + movementStarting(); |
| 3455 | + } |
| 3456 | + event->accept(); |
| 3457 | + } |
| 3458 | + } |
| 3459 | + if (xflick() && xDelta != 0) { |
| 3460 | + qreal instVelocity = xDelta / elapsed; |
| 3461 | + // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction |
| 3462 | + if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0)) |
| 3463 | + d->hData.velocityBuffer.clear(); |
| 3464 | + d->hData.addVelocitySample(instVelocity, d->maxVelocity); |
| 3465 | + d->hData.updateVelocity(); |
| 3466 | + if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) { |
| 3467 | + const bool newFlick = d->flickX(event->type(), d->hData.velocity); |
| 3468 | + if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) { |
| 3469 | + d->flickingStarted(true, false); |
| 3470 | + d->hMoved = true; |
| 3471 | + movementStarting(); |
| 3472 | + } |
| 3473 | + event->accept(); |
| 3474 | + } |
| 3475 | + } |
| 3476 | + } |
| 3477 | + } else { |
| 3478 | + // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually |
| 3479 | + int xDelta = event->pixelDelta().x(); |
| 3480 | + int yDelta = event->pixelDelta().y(); |
| 3481 | + |
| 3482 | + QVector2D velocity(xDelta / elapsed, yDelta / elapsed); |
| 3483 | + d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta()); |
| 3484 | + // Try to drag if 1) we already are dragging or flicking, or |
| 3485 | + // 2) the flickable is free to flick both directions, or |
| 3486 | + // 3) the movement so far has been mostly horizontal AND it's free to flick horizontally, or |
| 3487 | + // 4) the movement so far has been mostly vertical AND it's free to flick vertically. |
| 3488 | + // Otherwise, wait until the next event. Wheel events with pixel deltas tend to come frequently. |
| 3489 | + if (isMoving() || isFlicking() || (yflick() && xflick()) |
| 3490 | + || (xflick() && qAbs(d->accumulatedWheelPixelDelta.x()) > qAbs(d->accumulatedWheelPixelDelta.y() * 2)) |
| 3491 | + || (yflick() && qAbs(d->accumulatedWheelPixelDelta.y()) > qAbs(d->accumulatedWheelPixelDelta.x() * 2))) { |
| 3492 | + d->drag(currentTimestamp, event->type(), event->position(), d->accumulatedWheelPixelDelta, |
| 3493 | + true, !d->scrollingPhase, true, velocity); |
| 3494 | + d->updateBeginningEnd(); |
| 3495 | + if ((xflick() && !isAtXBeginning() && !isAtXEnd()) || (yflick() && !isAtYBeginning() && !isAtYEnd())) |
| 3496 | + event->accept(); |
| 3497 | + } else { |
| 3498 | + qCDebug(lcWheel) << "not dragging: accumulated deltas" << d->accumulatedWheelPixelDelta << |
| 3499 | + "moving?" << isMoving() << "can flick horizontally?" << xflick() << "vertically?" << yflick(); |
| 3500 | + } |
| 3501 | + } |
| 3502 | + d->lastPosTime = currentTimestamp; |
| 3503 | + |
| 3504 | + if (!event->isAccepted()) |
| 3505 | + QQuickItem::wheelEvent(event); |
| 3506 | +} |
| 3507 | +#endif |
| 3508 | + |
| 3509 | +bool StretchFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const |
| 3510 | +{ |
| 3511 | + Q_Q(const StretchFlickable); |
| 3512 | + QQuickItem *item = i; |
| 3513 | + while (item) { |
| 3514 | + StretchFlickable *flick = qobject_cast<StretchFlickable*>(item); |
| 3515 | + if (flick && flick->pressDelay() > 0 && flick->isInteractive()) { |
| 3516 | + // Found the innermost flickable with press delay - is it me? |
| 3517 | + return (flick == q); |
| 3518 | + } |
| 3519 | + item = item->parentItem(); |
| 3520 | + } |
| 3521 | + return false; |
| 3522 | +} |
| 3523 | + |
| 3524 | +void StretchFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent *event) |
| 3525 | +{ |
| 3526 | + Q_Q(StretchFlickable); |
| 3527 | + if (!q->window() || pressDelay <= 0) |
| 3528 | + return; |
| 3529 | + |
| 3530 | + // Only the innermost flickable should handle the delayed press; this allows |
| 3531 | + // flickables up the parent chain to all see the events in their filter functions |
| 3532 | + if (!isInnermostPressDelay(item)) |
| 3533 | + return; |
| 3534 | + |
| 3535 | + delayedPressEvent = QQuickDeliveryAgentPrivate::clonePointerEvent(event); |
| 3536 | + delayedPressEvent->setAccepted(false); |
| 3537 | + delayedPressTimer.start(pressDelay, q); |
| 3538 | + qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent; |
| 3539 | +} |
| 3540 | + |
| 3541 | +void StretchFlickablePrivate::clearDelayedPress() |
| 3542 | +{ |
| 3543 | + if (delayedPressEvent) { |
| 3544 | + delayedPressTimer.stop(); |
| 3545 | + qCDebug(lcReplay) << "clear delayed press" << delayedPressEvent; |
| 3546 | + delete delayedPressEvent; |
| 3547 | + delayedPressEvent = nullptr; |
| 3548 | + } |
| 3549 | +} |
| 3550 | + |
| 3551 | +void StretchFlickablePrivate::replayDelayedPress() |
| 3552 | +{ |
| 3553 | + Q_Q(StretchFlickable); |
| 3554 | + if (delayedPressEvent) { |
| 3555 | + // Losing the grab will clear the delayed press event; take control of it here |
| 3556 | + QScopedPointer<QPointerEvent> event(delayedPressEvent); |
| 3557 | + delayedPressEvent = nullptr; |
| 3558 | + delayedPressTimer.stop(); |
| 3559 | + |
| 3560 | + // If we have the grab, release before delivering the event |
| 3561 | + if (QQuickWindow *window = q->window()) { |
| 3562 | + auto da = deliveryAgentPrivate(); |
| 3563 | + da->allowChildEventFiltering = false; // don't allow re-filtering during replay |
| 3564 | + replayingPressEvent = true; |
| 3565 | + auto &firstPoint = event->point(0); |
| 3566 | + // At first glance, it's weird for delayedPressEvent to already have a grabber; |
| 3567 | + // but on press, filterMouseEvent() took the exclusive grab, and that's stored |
| 3568 | + // in the device-specific EventPointData instance in QPointingDevicePrivate::activePoints, |
| 3569 | + // not in the event itself. If this Flickable is still the grabber of that point on that device, |
| 3570 | + // that's the reason; but now it doesn't need that grab anymore. |
| 3571 | + if (event->exclusiveGrabber(firstPoint) == q) |
| 3572 | + event->setExclusiveGrabber(firstPoint, nullptr); |
| 3573 | + |
| 3574 | + qCDebug(lcReplay) << "replaying" << event.data(); |
| 3575 | + // Put scenePosition into position, for the sake of QQuickWindowPrivate::translateTouchEvent() |
| 3576 | + // TODO remove this if we remove QQuickWindowPrivate::translateTouchEvent() |
| 3577 | + QMutableEventPoint::setPosition(firstPoint, firstPoint.scenePosition()); |
| 3578 | + // Send it through like a fresh press event, and let QQuickWindow |
| 3579 | + // (more specifically, QQuickWindowPrivate::deliverPressOrReleaseEvent) |
| 3580 | + // find the item or handler that should receive it, as usual. |
| 3581 | + QCoreApplication::sendEvent(window, event.data()); |
| 3582 | + qCDebug(lcReplay) << "replay done"; |
| 3583 | + |
| 3584 | + // We're done with replay, go back to normal delivery behavior |
| 3585 | + replayingPressEvent = false; |
| 3586 | + da->allowChildEventFiltering = true; |
| 3587 | + } |
| 3588 | + } |
| 3589 | +} |
| 3590 | + |
| 3591 | +//XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned. |
| 3592 | + |
| 3593 | +/*! |
| 3594 | + \internal |
| 3595 | + |
| 3596 | + This function is called from the timeline, |
| 3597 | + when advancement in the timeline is modifying the hData.move value. |
| 3598 | + The \a x argument is the newly updated value in hData.move. |
| 3599 | + The purpose of the function is to update the x position of the contentItem. |
| 3600 | +*/ |
| 3601 | +void StretchFlickablePrivate::setViewportX(qreal x) |
| 3602 | +{ |
| 3603 | + Q_Q(StretchFlickable); |
| 3604 | + qreal effectiveX = pixelAligned ? -std::round(-x) : x; |
| 3605 | + |
| 3606 | + const qreal maxX = q->maxXExtent(); |
| 3607 | + const qreal minX = q->minXExtent(); |
| 3608 | + |
| 3609 | + if (boundsMovement == int(StretchFlickable::StopAtBounds)) |
| 3610 | + effectiveX = qBound(maxX, effectiveX, minX); |
| 3611 | + |
| 3612 | + contentItem->setX(effectiveX); |
| 3613 | + if (contentItem->x() != effectiveX) |
| 3614 | + return; // reentered |
| 3615 | + |
| 3616 | + qreal overshoot = 0.0; |
| 3617 | + if (x <= maxX) |
| 3618 | + overshoot = maxX - x; |
| 3619 | + else if (x >= minX) |
| 3620 | + overshoot = minX - x; |
| 3621 | + |
| 3622 | + if (overshoot != hData.overshoot) { |
| 3623 | + hData.overshoot = overshoot; |
| 3624 | + emit q->horizontalOvershootChanged(); |
| 3625 | + } |
| 3626 | +} |
| 3627 | + |
| 3628 | +/*! |
| 3629 | + \internal |
| 3630 | + |
| 3631 | + This function is called from the timeline, |
| 3632 | + when advancement in the timeline is modifying the vData.move value. |
| 3633 | + The \a y argument is the newly updated value in vData.move. |
| 3634 | + The purpose of the function is to update the y position of the contentItem. |
| 3635 | +*/ |
| 3636 | +void StretchFlickablePrivate::setViewportY(qreal y) |
| 3637 | +{ |
| 3638 | + Q_Q(StretchFlickable); |
| 3639 | + qreal effectiveY = pixelAligned ? -std::round(-y) : y; |
| 3640 | + |
| 3641 | + const qreal maxY = q->maxYExtent(); |
| 3642 | + const qreal minY = q->minYExtent(); |
| 3643 | + |
| 3644 | + if (boundsMovement == int(StretchFlickable::StopAtBounds)) |
| 3645 | + effectiveY = qBound(maxY, effectiveY, minY); |
| 3646 | + |
| 3647 | + contentItem->setY(effectiveY); |
| 3648 | + if (contentItem->y() != effectiveY) |
| 3649 | + return; // reentered |
| 3650 | + |
| 3651 | + qreal overshoot = 0.0; |
| 3652 | + if (y <= maxY) |
| 3653 | + overshoot = maxY - y; |
| 3654 | + else if (y >= minY) |
| 3655 | + overshoot = minY - y; |
| 3656 | + |
| 3657 | + if (overshoot != vData.overshoot) { |
| 3658 | + vData.overshoot = overshoot; |
| 3659 | + emit q->verticalOvershootChanged(); |
| 3660 | + } |
| 3661 | +} |
| 3662 | + |
| 3663 | +void StretchFlickable::timerEvent(QTimerEvent *event) |
| 3664 | +{ |
| 3665 | + Q_D(StretchFlickable); |
| 3666 | + if (event->timerId() == d->delayedPressTimer.timerId()) { |
| 3667 | + d->delayedPressTimer.stop(); |
| 3668 | + if (d->delayedPressEvent) { |
| 3669 | + d->replayDelayedPress(); |
| 3670 | + } |
| 3671 | + } |
| 3672 | +} |
| 3673 | + |
| 3674 | +qreal StretchFlickable::minYExtent() const |
| 3675 | +{ |
| 3676 | + Q_D(const StretchFlickable); |
| 3677 | + return d->vData.startMargin; |
| 3678 | +} |
| 3679 | + |
| 3680 | +qreal StretchFlickable::minXExtent() const |
| 3681 | +{ |
| 3682 | + Q_D(const StretchFlickable); |
| 3683 | + return d->hData.startMargin; |
| 3684 | +} |
| 3685 | + |
| 3686 | +/* returns -ve */ |
| 3687 | +qreal StretchFlickable::maxXExtent() const |
| 3688 | +{ |
| 3689 | + Q_D(const StretchFlickable); |
| 3690 | + return qMin<qreal>(minXExtent(), width() - vWidth() - d->hData.endMargin); |
| 3691 | +} |
| 3692 | +/* returns -ve */ |
| 3693 | +qreal StretchFlickable::maxYExtent() const |
| 3694 | +{ |
| 3695 | + Q_D(const StretchFlickable); |
| 3696 | + return qMin<qreal>(minYExtent(), height() - vHeight() - d->vData.endMargin); |
| 3697 | +} |
| 3698 | + |
| 3699 | +void StretchFlickable::componentComplete() |
| 3700 | +{ |
| 3701 | + Q_D(StretchFlickable); |
| 3702 | + QQuickItem::componentComplete(); |
| 3703 | + if (!d->hData.explicitValue && d->hData.startMargin != 0.) |
| 3704 | + setContentX(-minXExtent()); |
| 3705 | + if (!d->vData.explicitValue && d->vData.startMargin != 0.) |
| 3706 | + setContentY(-minYExtent()); |
| 3707 | + if (lcWheel().isDebugEnabled() || lcVel().isDebugEnabled()) { |
| 3708 | + d->timeline.setObjectName(QLatin1String("timeline for Flickable ") + objectName()); |
| 3709 | + d->velocityTimeline.setObjectName(QLatin1String("velocity timeline for Flickable ") + objectName()); |
| 3710 | + } |
| 3711 | +} |
| 3712 | + |
| 3713 | +void StretchFlickable::viewportMoved(Qt::Orientations orient) |
| 3714 | +{ |
| 3715 | + Q_D(StretchFlickable); |
| 3716 | + if (orient & Qt::Vertical) |
| 3717 | + d->viewportAxisMoved(d->vData, minYExtent(), maxYExtent(), d->fixupY_callback); |
| 3718 | + if (orient & Qt::Horizontal) |
| 3719 | + d->viewportAxisMoved(d->hData, minXExtent(), maxXExtent(), d->fixupX_callback); |
| 3720 | + d->updateBeginningEnd(); |
| 3721 | +} |
| 3722 | + |
| 3723 | +void StretchFlickablePrivate::viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent, |
| 3724 | + StretchTimeLineCallback::Callback fixupCallback) |
| 3725 | +{ |
| 3726 | + if (!scrollingPhase && (pressed || calcVelocity)) { |
| 3727 | + int elapsed = data.velocityTime.restart(); |
| 3728 | + if (elapsed > 0) { |
| 3729 | + qreal velocity = (data.lastPos - data.move.value()) * 1000 / elapsed; |
| 3730 | + if (qAbs(velocity) > 0) { |
| 3731 | + velocityTimeline.reset(data.smoothVelocity); |
| 3732 | + velocityTimeline.set(data.smoothVelocity, velocity); |
| 3733 | + qCDebug(lcVel) << "touchpad scroll phase: velocity" << velocity; |
| 3734 | + } |
| 3735 | + } |
| 3736 | + } else { |
| 3737 | + if (timeline.time() > data.vTime) { |
| 3738 | + velocityTimeline.reset(data.smoothVelocity); |
| 3739 | + int dt = timeline.time() - data.vTime; |
| 3740 | + if (dt > 2) { |
| 3741 | + qreal velocity = (data.lastPos - data.move.value()) * 1000 / dt; |
| 3742 | + if (!qFuzzyCompare(data.smoothVelocity.value(), velocity)) |
| 3743 | + qCDebug(lcVel) << "velocity" << data.smoothVelocity.value() << "->" << velocity |
| 3744 | + << "computed as (" << data.lastPos << "-" << data.move.value() << ") * 1000 / (" |
| 3745 | + << timeline.time() << "-" << data.vTime << ")"; |
| 3746 | + data.smoothVelocity.setValue(velocity); |
| 3747 | + } |
| 3748 | + } |
| 3749 | + } |
| 3750 | + |
| 3751 | + if (!data.inOvershoot && !data.fixingUp && data.flicking |
| 3752 | + && (data.move.value() > minExtent || data.move.value() < maxExtent) |
| 3753 | + && qAbs(data.smoothVelocity.value()) > 10) { |
| 3754 | + // Increase deceleration if we've passed a bound |
| 3755 | + qreal overBound = data.move.value() > minExtent |
| 3756 | + ? data.move.value() - minExtent |
| 3757 | + : maxExtent - data.move.value(); |
| 3758 | + data.inOvershoot = true; |
| 3759 | + qreal maxDistance = overShootDistance(qAbs(data.smoothVelocity.value())) - overBound; |
| 3760 | + resetTimeline(data); |
| 3761 | + if (maxDistance > 0) |
| 3762 | + timeline.accel(data.move, -data.smoothVelocity.value(), deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); |
| 3763 | + timeline.callback(StretchTimeLineCallback(&data.move, fixupCallback, this)); |
| 3764 | + } |
| 3765 | + |
| 3766 | + data.lastPos = data.move.value(); |
| 3767 | + data.vTime = timeline.time(); |
| 3768 | +} |
| 3769 | + |
| 3770 | +void StretchFlickable::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
| 3771 | +{ |
| 3772 | + Q_D(StretchFlickable); |
| 3773 | + QQuickItem::geometryChange(newGeometry, oldGeometry); |
| 3774 | + |
| 3775 | + bool changed = false; |
| 3776 | + if (newGeometry.width() != oldGeometry.width()) { |
| 3777 | + changed = true; // we must update visualArea.widthRatio |
| 3778 | + if (d->hData.viewSize < 0) |
| 3779 | + d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin); |
| 3780 | + // Make sure that we're entirely in view. |
| 3781 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 3782 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 3783 | + d->fixupX(); |
| 3784 | + } |
| 3785 | + } |
| 3786 | + if (newGeometry.height() != oldGeometry.height()) { |
| 3787 | + changed = true; // we must update visualArea.heightRatio |
| 3788 | + if (d->vData.viewSize < 0) |
| 3789 | + d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin); |
| 3790 | + // Make sure that we're entirely in view. |
| 3791 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 3792 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 3793 | + d->fixupY(); |
| 3794 | + } |
| 3795 | + } |
| 3796 | + |
| 3797 | + if (changed) |
| 3798 | + d->updateBeginningEnd(); |
| 3799 | +} |
| 3800 | + |
| 3801 | +/*! |
| 3802 | + \qmlmethod QtQuick::Flickable::flick(qreal xVelocity, qreal yVelocity) |
| 3803 | + |
| 3804 | + Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec. |
| 3805 | + |
| 3806 | + Calling this method will update the corresponding moving and flicking properties and signals, |
| 3807 | + just like a real touchscreen flick. |
| 3808 | +*/ |
| 3809 | + |
| 3810 | +void StretchFlickable::flick(qreal xVelocity, qreal yVelocity) |
| 3811 | +{ |
| 3812 | + Q_D(StretchFlickable); |
| 3813 | + d->hData.reset(); |
| 3814 | + d->vData.reset(); |
| 3815 | + d->hData.velocity = xVelocity; |
| 3816 | + d->vData.velocity = yVelocity; |
| 3817 | + d->hData.vTime = d->vData.vTime = d->timeline.time(); |
| 3818 | + |
| 3819 | + const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(QEvent::TouchUpdate, xVelocity); |
| 3820 | + const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(QEvent::TouchUpdate, yVelocity); |
| 3821 | + |
| 3822 | + if (flickedX) |
| 3823 | + d->hMoved = true; |
| 3824 | + if (flickedY) |
| 3825 | + d->vMoved = true; |
| 3826 | + movementStarting(); |
| 3827 | + d->flickingStarted(flickedX, flickedY); |
| 3828 | +} |
| 3829 | + |
| 3830 | +void StretchFlickablePrivate::flickingStarted(bool flickingH, bool flickingV) |
| 3831 | +{ |
| 3832 | + Q_Q(StretchFlickable); |
| 3833 | + if (!flickingH && !flickingV) |
| 3834 | + return; |
| 3835 | + |
| 3836 | + bool wasFlicking = hData.flicking || vData.flicking; |
| 3837 | + if (flickingH && !hData.flicking) { |
| 3838 | + hData.flicking = true; |
| 3839 | + emit q->flickingHorizontallyChanged(); |
| 3840 | + } |
| 3841 | + if (flickingV && !vData.flicking) { |
| 3842 | + vData.flicking = true; |
| 3843 | + emit q->flickingVerticallyChanged(); |
| 3844 | + } |
| 3845 | + if (!wasFlicking && (hData.flicking || vData.flicking)) { |
| 3846 | + emit q->flickingChanged(); |
| 3847 | + emit q->flickStarted(); |
| 3848 | + } |
| 3849 | +} |
| 3850 | + |
| 3851 | +/*! |
| 3852 | + \qmlmethod QtQuick::Flickable::cancelFlick() |
| 3853 | + |
| 3854 | + Cancels the current flick animation. |
| 3855 | +*/ |
| 3856 | + |
| 3857 | +void StretchFlickable::cancelFlick() |
| 3858 | +{ |
| 3859 | + Q_D(StretchFlickable); |
| 3860 | + d->resetTimeline(d->hData); |
| 3861 | + d->resetTimeline(d->vData); |
| 3862 | + movementEnding(); |
| 3863 | +} |
| 3864 | + |
| 3865 | +void StretchFlickablePrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) |
| 3866 | +{ |
| 3867 | + if (!prop || !prop->data) |
| 3868 | + return; |
| 3869 | + |
| 3870 | + if (QQuickItem *i = qmlobject_cast<QQuickItem *>(o)) { |
| 3871 | + i->setParentItem(static_cast<StretchFlickablePrivate*>(prop->data)->contentItem); |
| 3872 | + } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) { |
| 3873 | + static_cast<StretchFlickablePrivate*>(prop->data)->addPointerHandler(pointerHandler); |
| 3874 | + } else { |
| 3875 | + o->setParent(prop->object); // XXX todo - do we want this? |
| 3876 | + } |
| 3877 | +} |
| 3878 | + |
| 3879 | +qsizetype StretchFlickablePrivate::data_count(QQmlListProperty<QObject> *) |
| 3880 | +{ |
| 3881 | + // XXX todo |
| 3882 | + return 0; |
| 3883 | +} |
| 3884 | + |
| 3885 | +QObject *StretchFlickablePrivate::data_at(QQmlListProperty<QObject> *, qsizetype) |
| 3886 | +{ |
| 3887 | + // XXX todo |
| 3888 | + return nullptr; |
| 3889 | +} |
| 3890 | + |
| 3891 | +void StretchFlickablePrivate::data_clear(QQmlListProperty<QObject> *) |
| 3892 | +{ |
| 3893 | + // XXX todo |
| 3894 | +} |
| 3895 | + |
| 3896 | +QQmlListProperty<QObject> StretchFlickable::flickableData() |
| 3897 | +{ |
| 3898 | + Q_D(StretchFlickable); |
| 3899 | + return QQmlListProperty<QObject>(this, (void *)d, StretchFlickablePrivate::data_append, |
| 3900 | + StretchFlickablePrivate::data_count, |
| 3901 | + StretchFlickablePrivate::data_at, |
| 3902 | + StretchFlickablePrivate::data_clear); |
| 3903 | +} |
| 3904 | + |
| 3905 | +QQmlListProperty<QQuickItem> StretchFlickable::flickableChildren() |
| 3906 | +{ |
| 3907 | + Q_D(StretchFlickable); |
| 3908 | + return QQuickItemPrivate::get(d->contentItem)->children(); |
| 3909 | +} |
| 3910 | + |
| 3911 | +/*! |
| 3912 | + \qmlproperty enumeration QtQuick::Flickable::boundsBehavior |
| 3913 | + This property holds whether the surface may be dragged |
| 3914 | + beyond the Flickable's boundaries, or overshoot the |
| 3915 | + Flickable's boundaries when flicked. |
| 3916 | + |
| 3917 | + When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value |
| 3918 | + other than \c Flickable.StopAtBounds will give a feeling that the edges of |
| 3919 | + the view are soft, rather than a hard physical boundary. |
| 3920 | + |
| 3921 | + The \c boundsBehavior can be one of: |
| 3922 | + |
| 3923 | + \list |
| 3924 | + \li Flickable.StopAtBounds - the contents can not be dragged beyond the boundary |
| 3925 | + of the flickable, and flicks will not overshoot. |
| 3926 | + \li Flickable.DragOverBounds - the contents can be dragged beyond the boundary |
| 3927 | + of the Flickable, but flicks will not overshoot. |
| 3928 | + \li Flickable.OvershootBounds - the contents can overshoot the boundary when flicked, |
| 3929 | + but the content cannot be dragged beyond the boundary of the flickable. (since \c{QtQuick 2.5}) |
| 3930 | + \li Flickable.DragAndOvershootBounds (default) - the contents can be dragged |
| 3931 | + beyond the boundary of the Flickable, and can overshoot the |
| 3932 | + boundary when flicked. |
| 3933 | + \endlist |
| 3934 | + |
| 3935 | + \sa horizontalOvershoot, verticalOvershoot, boundsMovement |
| 3936 | +*/ |
| 3937 | +StretchFlickable::BoundsBehavior StretchFlickable::boundsBehavior() const |
| 3938 | +{ |
| 3939 | + Q_D(const StretchFlickable); |
| 3940 | + return d->boundsBehavior; |
| 3941 | +} |
| 3942 | + |
| 3943 | +void StretchFlickable::setBoundsBehavior(BoundsBehavior b) |
| 3944 | +{ |
| 3945 | + Q_D(StretchFlickable); |
| 3946 | + if (b == d->boundsBehavior) |
| 3947 | + return; |
| 3948 | + d->boundsBehavior = b; |
| 3949 | + emit boundsBehaviorChanged(); |
| 3950 | +} |
| 3951 | + |
| 3952 | +/*! |
| 3953 | + \qmlproperty Transition QtQuick::Flickable::rebound |
| 3954 | + |
| 3955 | + This holds the transition to be applied to the content view when |
| 3956 | + it snaps back to the bounds of the flickable. The transition is |
| 3957 | + triggered when the view is flicked or dragged past the edge of the |
| 3958 | + content area, or when returnToBounds() is called. |
| 3959 | + |
| 3960 | + \qml |
| 3961 | + import QtQuick 2.0 |
| 3962 | + |
| 3963 | + Flickable { |
| 3964 | + width: 150; height: 150 |
| 3965 | + contentWidth: 300; contentHeight: 300 |
| 3966 | + |
| 3967 | + rebound: Transition { |
| 3968 | + NumberAnimation { |
| 3969 | + properties: "x,y" |
| 3970 | + duration: 1000 |
| 3971 | + easing.type: Easing.OutBounce |
| 3972 | + } |
| 3973 | + } |
| 3974 | + |
| 3975 | + Rectangle { |
| 3976 | + width: 300; height: 300 |
| 3977 | + gradient: Gradient { |
| 3978 | + GradientStop { position: 0.0; color: "lightsteelblue" } |
| 3979 | + GradientStop { position: 1.0; color: "blue" } |
| 3980 | + } |
| 3981 | + } |
| 3982 | + } |
| 3983 | + \endqml |
| 3984 | + |
| 3985 | + When the above view is flicked beyond its bounds, it will return to its |
| 3986 | + bounds using the transition specified: |
| 3987 | + |
| 3988 | + \image flickable-rebound.gif |
| 3989 | + |
| 3990 | + If this property is not set, a default animation is applied. |
| 3991 | + */ |
| 3992 | +QQuickTransition *StretchFlickable::rebound() const |
| 3993 | +{ |
| 3994 | + Q_D(const StretchFlickable); |
| 3995 | + return d->rebound; |
| 3996 | +} |
| 3997 | + |
| 3998 | +void StretchFlickable::setRebound(QQuickTransition *transition) |
| 3999 | +{ |
| 4000 | + Q_D(StretchFlickable); |
| 4001 | + if (transition) { |
| 4002 | + if (!d->hData.transitionToBounds) |
| 4003 | + d->hData.transitionToBounds = new StretchFlickableReboundTransition(this, QLatin1String("x")); |
| 4004 | + if (!d->vData.transitionToBounds) |
| 4005 | + d->vData.transitionToBounds = new StretchFlickableReboundTransition(this, QLatin1String("y")); |
| 4006 | + } |
| 4007 | + if (d->rebound != transition) { |
| 4008 | + d->rebound = transition; |
| 4009 | + emit reboundChanged(); |
| 4010 | + } |
| 4011 | +} |
| 4012 | + |
| 4013 | +/*! |
| 4014 | + \qmlproperty real QtQuick::Flickable::contentWidth |
| 4015 | + \qmlproperty real QtQuick::Flickable::contentHeight |
| 4016 | + |
| 4017 | + The dimensions of the content (the surface controlled by Flickable). |
| 4018 | + This should typically be set to the combined size of the items placed in the |
| 4019 | + Flickable. |
| 4020 | + |
| 4021 | + The following snippet shows how these properties are used to display |
| 4022 | + an image that is larger than the Flickable item itself: |
| 4023 | + |
| 4024 | + \snippet qml/flickable.qml document |
| 4025 | + |
| 4026 | + In some cases, the content dimensions can be automatically set |
| 4027 | + based on the \l {Item::childrenRect.width}{childrenRect.width} |
| 4028 | + and \l {Item::childrenRect.height}{childrenRect.height} properties |
| 4029 | + of the \l contentItem. For example, the previous snippet could be rewritten with: |
| 4030 | + |
| 4031 | + \code |
| 4032 | + contentWidth: contentItem.childrenRect.width; contentHeight: contentItem.childrenRect.height |
| 4033 | + \endcode |
| 4034 | + |
| 4035 | + Though this assumes that the origin of the childrenRect is 0,0. |
| 4036 | +*/ |
| 4037 | +qreal StretchFlickable::contentWidth() const |
| 4038 | +{ |
| 4039 | + Q_D(const StretchFlickable); |
| 4040 | + return d->hData.viewSize; |
| 4041 | +} |
| 4042 | + |
| 4043 | +void StretchFlickable::setContentWidth(qreal w) |
| 4044 | +{ |
| 4045 | + Q_D(StretchFlickable); |
| 4046 | + if (d->hData.viewSize == w) |
| 4047 | + return; |
| 4048 | + d->hData.viewSize = w; |
| 4049 | + if (w < 0) |
| 4050 | + d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin); |
| 4051 | + else |
| 4052 | + d->contentItem->setWidth(w); |
| 4053 | + d->hData.markExtentsDirty(); |
| 4054 | + // Make sure that we're entirely in view. |
| 4055 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 4056 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 4057 | + d->fixupX(); |
| 4058 | + } else if (!d->pressed && d->hData.fixingUp) { |
| 4059 | + d->fixupMode = StretchFlickablePrivate::ExtentChanged; |
| 4060 | + d->fixupX(); |
| 4061 | + } |
| 4062 | + emit contentWidthChanged(); |
| 4063 | + d->updateBeginningEnd(); |
| 4064 | +} |
| 4065 | + |
| 4066 | +qreal StretchFlickable::contentHeight() const |
| 4067 | +{ |
| 4068 | + Q_D(const StretchFlickable); |
| 4069 | + return d->vData.viewSize; |
| 4070 | +} |
| 4071 | + |
| 4072 | +void StretchFlickable::setContentHeight(qreal h) |
| 4073 | +{ |
| 4074 | + Q_D(StretchFlickable); |
| 4075 | + if (d->vData.viewSize == h) |
| 4076 | + return; |
| 4077 | + d->vData.viewSize = h; |
| 4078 | + if (h < 0) |
| 4079 | + d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin); |
| 4080 | + else |
| 4081 | + d->contentItem->setHeight(h); |
| 4082 | + d->vData.markExtentsDirty(); |
| 4083 | + // Make sure that we're entirely in view. |
| 4084 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 4085 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 4086 | + d->fixupY(); |
| 4087 | + } else if (!d->pressed && d->vData.fixingUp) { |
| 4088 | + d->fixupMode = StretchFlickablePrivate::ExtentChanged; |
| 4089 | + d->fixupY(); |
| 4090 | + } |
| 4091 | + emit contentHeightChanged(); |
| 4092 | + d->updateBeginningEnd(); |
| 4093 | +} |
| 4094 | + |
| 4095 | +/*! |
| 4096 | + \qmlproperty real QtQuick::Flickable::topMargin |
| 4097 | + \qmlproperty real QtQuick::Flickable::leftMargin |
| 4098 | + \qmlproperty real QtQuick::Flickable::bottomMargin |
| 4099 | + \qmlproperty real QtQuick::Flickable::rightMargin |
| 4100 | + |
| 4101 | + These properties hold the margins around the content. This space is reserved |
| 4102 | + in addition to the contentWidth and contentHeight. |
| 4103 | +*/ |
| 4104 | + |
| 4105 | + |
| 4106 | +qreal StretchFlickable::topMargin() const |
| 4107 | +{ |
| 4108 | + Q_D(const StretchFlickable); |
| 4109 | + return d->vData.startMargin; |
| 4110 | +} |
| 4111 | + |
| 4112 | +void StretchFlickable::setTopMargin(qreal m) |
| 4113 | +{ |
| 4114 | + Q_D(StretchFlickable); |
| 4115 | + if (d->vData.startMargin == m) |
| 4116 | + return; |
| 4117 | + d->vData.startMargin = m; |
| 4118 | + d->vData.markExtentsDirty(); |
| 4119 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 4120 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 4121 | + d->fixupY(); |
| 4122 | + } |
| 4123 | + emit topMarginChanged(); |
| 4124 | + d->updateBeginningEnd(); |
| 4125 | +} |
| 4126 | + |
| 4127 | +qreal StretchFlickable::bottomMargin() const |
| 4128 | +{ |
| 4129 | + Q_D(const StretchFlickable); |
| 4130 | + return d->vData.endMargin; |
| 4131 | +} |
| 4132 | + |
| 4133 | +void StretchFlickable::setBottomMargin(qreal m) |
| 4134 | +{ |
| 4135 | + Q_D(StretchFlickable); |
| 4136 | + if (d->vData.endMargin == m) |
| 4137 | + return; |
| 4138 | + d->vData.endMargin = m; |
| 4139 | + d->vData.markExtentsDirty(); |
| 4140 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 4141 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 4142 | + d->fixupY(); |
| 4143 | + } |
| 4144 | + emit bottomMarginChanged(); |
| 4145 | + d->updateBeginningEnd(); |
| 4146 | +} |
| 4147 | + |
| 4148 | +qreal StretchFlickable::leftMargin() const |
| 4149 | +{ |
| 4150 | + Q_D(const StretchFlickable); |
| 4151 | + return d->hData.startMargin; |
| 4152 | +} |
| 4153 | + |
| 4154 | +void StretchFlickable::setLeftMargin(qreal m) |
| 4155 | +{ |
| 4156 | + Q_D(StretchFlickable); |
| 4157 | + if (d->hData.startMargin == m) |
| 4158 | + return; |
| 4159 | + d->hData.startMargin = m; |
| 4160 | + d->hData.markExtentsDirty(); |
| 4161 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 4162 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 4163 | + d->fixupX(); |
| 4164 | + } |
| 4165 | + emit leftMarginChanged(); |
| 4166 | + d->updateBeginningEnd(); |
| 4167 | +} |
| 4168 | + |
| 4169 | +qreal StretchFlickable::rightMargin() const |
| 4170 | +{ |
| 4171 | + Q_D(const StretchFlickable); |
| 4172 | + return d->hData.endMargin; |
| 4173 | +} |
| 4174 | + |
| 4175 | +void StretchFlickable::setRightMargin(qreal m) |
| 4176 | +{ |
| 4177 | + Q_D(StretchFlickable); |
| 4178 | + if (d->hData.endMargin == m) |
| 4179 | + return; |
| 4180 | + d->hData.endMargin = m; |
| 4181 | + d->hData.markExtentsDirty(); |
| 4182 | + if (!d->pressed && !d->hData.moving && !d->vData.moving) { |
| 4183 | + d->fixupMode = StretchFlickablePrivate::Immediate; |
| 4184 | + d->fixupX(); |
| 4185 | + } |
| 4186 | + emit rightMarginChanged(); |
| 4187 | + d->updateBeginningEnd(); |
| 4188 | +} |
| 4189 | + |
| 4190 | +/*! |
| 4191 | + \qmlproperty real QtQuick::Flickable::originX |
| 4192 | + \qmlproperty real QtQuick::Flickable::originY |
| 4193 | + |
| 4194 | + These properties hold the origin of the content. This value always refers |
| 4195 | + to the top-left position of the content regardless of layout direction. |
| 4196 | + |
| 4197 | + This is usually (0,0), however ListView and GridView may have an arbitrary |
| 4198 | + origin due to delegate size variation, or item insertion/removal outside |
| 4199 | + the visible region. |
| 4200 | + |
| 4201 | + \sa contentX, contentY |
| 4202 | +*/ |
| 4203 | + |
| 4204 | +qreal StretchFlickable::originY() const |
| 4205 | +{ |
| 4206 | + Q_D(const StretchFlickable); |
| 4207 | + return -minYExtent() + d->vData.startMargin; |
| 4208 | +} |
| 4209 | + |
| 4210 | +qreal StretchFlickable::originX() const |
| 4211 | +{ |
| 4212 | + Q_D(const StretchFlickable); |
| 4213 | + return -minXExtent() + d->hData.startMargin; |
| 4214 | +} |
| 4215 | + |
| 4216 | + |
| 4217 | +/*! |
| 4218 | + \qmlmethod QtQuick::Flickable::resizeContent(real width, real height, QPointF center) |
| 4219 | + |
| 4220 | + Resizes the content to \a width x \a height about \a center. |
| 4221 | + |
| 4222 | + This does not scale the contents of the Flickable - it only resizes the \l contentWidth |
| 4223 | + and \l contentHeight. |
| 4224 | + |
| 4225 | + Resizing the content may result in the content being positioned outside |
| 4226 | + the bounds of the Flickable. Calling \l returnToBounds() will |
| 4227 | + move the content back within legal bounds. |
| 4228 | +*/ |
| 4229 | +void StretchFlickable::resizeContent(qreal w, qreal h, QPointF center) |
| 4230 | +{ |
| 4231 | + Q_D(StretchFlickable); |
| 4232 | + const qreal oldHSize = d->hData.viewSize; |
| 4233 | + const qreal oldVSize = d->vData.viewSize; |
| 4234 | + const bool needToUpdateWidth = w != oldHSize; |
| 4235 | + const bool needToUpdateHeight = h != oldVSize; |
| 4236 | + d->hData.viewSize = w; |
| 4237 | + d->vData.viewSize = h; |
| 4238 | + d->contentItem->setSize(QSizeF(w, h)); |
| 4239 | + if (needToUpdateWidth) |
| 4240 | + emit contentWidthChanged(); |
| 4241 | + if (needToUpdateHeight) |
| 4242 | + emit contentHeightChanged(); |
| 4243 | + |
| 4244 | + if (center.x() != 0) { |
| 4245 | + qreal pos = center.x() * w / oldHSize; |
| 4246 | + setContentX(contentX() + pos - center.x()); |
| 4247 | + } |
| 4248 | + if (center.y() != 0) { |
| 4249 | + qreal pos = center.y() * h / oldVSize; |
| 4250 | + setContentY(contentY() + pos - center.y()); |
| 4251 | + } |
| 4252 | + d->updateBeginningEnd(); |
| 4253 | +} |
| 4254 | + |
| 4255 | +/*! |
| 4256 | + \qmlmethod QtQuick::Flickable::returnToBounds() |
| 4257 | + |
| 4258 | + Ensures the content is within legal bounds. |
| 4259 | + |
| 4260 | + This may be called to ensure that the content is within legal bounds |
| 4261 | + after manually positioning the content. |
| 4262 | +*/ |
| 4263 | +void StretchFlickable::returnToBounds() |
| 4264 | +{ |
| 4265 | + Q_D(StretchFlickable); |
| 4266 | + d->fixupX(); |
| 4267 | + d->fixupY(); |
| 4268 | +} |
| 4269 | + |
| 4270 | +qreal StretchFlickable::vWidth() const |
| 4271 | +{ |
| 4272 | + Q_D(const StretchFlickable); |
| 4273 | + if (d->hData.viewSize < 0) |
| 4274 | + return width(); |
| 4275 | + else |
| 4276 | + return d->hData.viewSize; |
| 4277 | +} |
| 4278 | + |
| 4279 | +qreal StretchFlickable::vHeight() const |
| 4280 | +{ |
| 4281 | + Q_D(const StretchFlickable); |
| 4282 | + if (d->vData.viewSize < 0) |
| 4283 | + return height(); |
| 4284 | + else |
| 4285 | + return d->vData.viewSize; |
| 4286 | +} |
| 4287 | + |
| 4288 | +/*! |
| 4289 | + \internal |
| 4290 | + |
| 4291 | + The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along. |
| 4292 | + |
| 4293 | + \return true if the flickable is allowed to flick in the horizontal direction, otherwise returns false |
| 4294 | +*/ |
| 4295 | +bool StretchFlickable::xflick() const |
| 4296 | +{ |
| 4297 | + Q_D(const StretchFlickable); |
| 4298 | + const int contentWidthWithMargins = d->contentItem->width() + d->hData.startMargin + d->hData.endMargin; |
| 4299 | + if ((d->flickableDirection & StretchFlickable::AutoFlickIfNeeded) && (contentWidthWithMargins > width())) |
| 4300 | + return true; |
| 4301 | + if (d->flickableDirection == StretchFlickable::AutoFlickDirection) |
| 4302 | + return std::floor(qAbs(contentWidthWithMargins - width())); |
| 4303 | + return d->flickableDirection & StretchFlickable::HorizontalFlick; |
| 4304 | +} |
| 4305 | + |
| 4306 | +/*! |
| 4307 | + \internal |
| 4308 | + |
| 4309 | + The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along. |
| 4310 | + |
| 4311 | + \return true if the flickable is allowed to flick in the vertical direction, otherwise returns false. |
| 4312 | +*/ |
| 4313 | +bool StretchFlickable::yflick() const |
| 4314 | +{ |
| 4315 | + Q_D(const StretchFlickable); |
| 4316 | + const int contentHeightWithMargins = d->contentItem->height() + d->vData.startMargin + d->vData.endMargin; |
| 4317 | + if ((d->flickableDirection & StretchFlickable::AutoFlickIfNeeded) && (contentHeightWithMargins > height())) |
| 4318 | + return true; |
| 4319 | + if (d->flickableDirection == StretchFlickable::AutoFlickDirection) |
| 4320 | + return std::floor(qAbs(contentHeightWithMargins - height())); |
| 4321 | + return d->flickableDirection & StretchFlickable::VerticalFlick; |
| 4322 | +} |
| 4323 | + |
| 4324 | +void StretchFlickable::mouseUngrabEvent() |
| 4325 | +{ |
| 4326 | + Q_D(StretchFlickable); |
| 4327 | + // if our mouse grab has been removed (probably by another Flickable), |
| 4328 | + // fix our state |
| 4329 | + if (!d->replayingPressEvent) |
| 4330 | + d->cancelInteraction(); |
| 4331 | +} |
| 4332 | + |
| 4333 | +void StretchFlickablePrivate::cancelInteraction() |
| 4334 | +{ |
| 4335 | + Q_Q(StretchFlickable); |
| 4336 | + if (pressed) { |
| 4337 | + clearDelayedPress(); |
| 4338 | + pressed = false; |
| 4339 | + draggingEnding(); |
| 4340 | + stealMouse = false; |
| 4341 | + q->setKeepMouseGrab(false); |
| 4342 | + fixupX(); |
| 4343 | + fixupY(); |
| 4344 | + if (!isViewMoving()) |
| 4345 | + q->movementEnding(); |
| 4346 | + } |
| 4347 | +} |
| 4348 | + |
| 4349 | +void StretchFlickablePrivate::addPointerHandler(QQuickPointerHandler *h) |
| 4350 | +{ |
| 4351 | + Q_Q(const StretchFlickable); |
| 4352 | + qCDebug(lcStretchHandlerParent) << "reparenting handler" << h << "to contentItem of" << q; |
| 4353 | + h->setParent(contentItem); |
| 4354 | + QQuickItemPrivate::get(contentItem)->addPointerHandler(h); |
| 4355 | +} |
| 4356 | + |
| 4357 | +/*! \internal |
| 4358 | + StretchFlickable::filterPointerEvent filters pointer events intercepted on the way |
| 4359 | + to the child \a receiver, and potentially steals the exclusive grab. |
| 4360 | + |
| 4361 | + This is how flickable takes over the handling of events from child items. |
| 4362 | + |
| 4363 | + Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver. |
| 4364 | +*/ |
| 4365 | +bool StretchFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event) |
| 4366 | +{ |
| 4367 | + Q_D(StretchFlickable); |
| 4368 | + const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(event); |
| 4369 | + const bool isMouse = QQuickDeliveryAgentPrivate::isMouseEvent(event); |
| 4370 | + if (isMouse || QQuickDeliveryAgentPrivate::isTabletEvent(event)) { |
| 4371 | + if (!d->buttonsAccepted(static_cast<QSinglePointEvent *>(event))) |
| 4372 | + return QQuickItem::childMouseEventFilter(receiver, event); |
| 4373 | + } else if (!isTouch) { |
| 4374 | + return false; // don't filter hover events or wheel events, for example |
| 4375 | + } |
| 4376 | + Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself"); |
| 4377 | + // If a touch event contains a new press point, don't steal right away: watch the movements for a while |
| 4378 | + if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed)) |
| 4379 | + d->stealMouse = false; |
| 4380 | + // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child |
| 4381 | + if (event->pointCount() > 1) { |
| 4382 | + qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver; |
| 4383 | + d->stealMouse = false; |
| 4384 | + return false; |
| 4385 | + } else { |
| 4386 | + qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; |
| 4387 | + } |
| 4388 | + |
| 4389 | + const auto &firstPoint = event->points().first(); |
| 4390 | + |
| 4391 | + if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) { |
| 4392 | + // We have an exclusive grab (since we're e.g dragging), but at the same time, we have |
| 4393 | + // a child with a passive grab (which is why this filter is being called). And because |
| 4394 | + // of that, we end up getting the same pointer events twice; First in our own event |
| 4395 | + // handlers (because of the grab), then once more in here, since we filter the child. |
| 4396 | + // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more |
| 4397 | + // from below), we mark the event as filtered, and simply return. |
| 4398 | + event->setAccepted(true); |
| 4399 | + return true; |
| 4400 | + } |
| 4401 | + |
| 4402 | + QPointF localPos = mapFromScene(firstPoint.scenePosition()); |
| 4403 | + bool receiverDisabled = receiver && !receiver->isEnabled(); |
| 4404 | + bool stealThisEvent = d->stealMouse; |
| 4405 | + bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); |
| 4406 | + bool receiverRelinquishGrab = false; |
| 4407 | + |
| 4408 | + // Special case for MouseArea, try to guess what it does with the event |
| 4409 | + if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(receiver)) { |
| 4410 | + bool preventStealing = mouseArea->preventStealing(); |
| 4411 | +#if QT_CONFIG(quick_draganddrop) |
| 4412 | + if (mouseArea->drag() && mouseArea->drag()->target()) |
| 4413 | + preventStealing = true; |
| 4414 | +#endif |
| 4415 | + if (!preventStealing && receiverKeepsGrab) { |
| 4416 | + receiverRelinquishGrab = !receiverDisabled || (isMouse |
| 4417 | + && firstPoint.state() == QEventPoint::State::Pressed |
| 4418 | + && (receiver->acceptedMouseButtons() & static_cast<QMouseEvent *>(event)->button())); |
| 4419 | + if (receiverRelinquishGrab) |
| 4420 | + receiverKeepsGrab = false; |
| 4421 | + } |
| 4422 | + } |
| 4423 | + |
| 4424 | + if ((stealThisEvent || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) { |
| 4425 | + QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos)); |
| 4426 | + localizedEvent->setAccepted(false); |
| 4427 | + switch (firstPoint.state()) { |
| 4428 | + case QEventPoint::State::Updated: |
| 4429 | + d->handleMoveEvent(localizedEvent.data()); |
| 4430 | + break; |
| 4431 | + case QEventPoint::State::Pressed: |
| 4432 | + d->handlePressEvent(localizedEvent.data()); |
| 4433 | + d->captureDelayedPress(receiver, event); |
| 4434 | + // never grab the pointing device on press during filtering: do it later, during a move |
| 4435 | + d->stealMouse = false; |
| 4436 | + stealThisEvent = false; |
| 4437 | + break; |
| 4438 | + case QEventPoint::State::Released: |
| 4439 | + d->handleReleaseEvent(localizedEvent.data()); |
| 4440 | + stealThisEvent = d->stealMouse; |
| 4441 | + break; |
| 4442 | + case QEventPoint::State::Stationary: |
| 4443 | + case QEventPoint::State::Unknown: |
| 4444 | + break; |
| 4445 | + } |
| 4446 | + if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) { |
| 4447 | + d->clearDelayedPress(); |
| 4448 | + event->setExclusiveGrabber(firstPoint, this); |
| 4449 | + } else if (d->delayedPressEvent) { |
| 4450 | + event->setExclusiveGrabber(firstPoint, this); |
| 4451 | + } |
| 4452 | + /* |
| 4453 | + Note that d->stealMouse can be false before the call to d->handleMoveEvent(), but true |
| 4454 | + afterwards. That means we detected a drag. But even so, we deliberately don't filter |
| 4455 | + the move event that cause this to happen, since the user might actually be dragging on |
| 4456 | + a child item, such as a Slider, in which case the child should get a chance to detect |
| 4457 | + the drag instead, and take the grab. Only if we receive another move event after this, |
| 4458 | + without the child grabbing the mouse in-between, do we initiate a flick. |
| 4459 | + An exception is if the user is already in a flicking session started from an earlier |
| 4460 | + drag, meaning that the content item is still moving (isMoving() == true). In that |
| 4461 | + case, we see any subsequent drags as being a continuation of the flicking. |
| 4462 | + Then we filter all events straight away, to avoid triggering any taps or drags in a child. |
| 4463 | + */ |
| 4464 | + if (isMoving() || (!receiverRelinquishGrab && (stealThisEvent || d->delayedPressEvent || receiverDisabled))) { |
| 4465 | + // Filter the event |
| 4466 | + event->setAccepted(true); |
| 4467 | + return true; |
| 4468 | + } |
| 4469 | + |
| 4470 | + // Don't filter the event |
| 4471 | + return false; |
| 4472 | + } else if (d->lastPosTime != -1) { |
| 4473 | + d->lastPosTime = -1; |
| 4474 | + returnToBounds(); |
| 4475 | + } |
| 4476 | + if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) { |
| 4477 | + // mouse released, or another item has claimed the grab |
| 4478 | + d->lastPosTime = -1; |
| 4479 | + d->clearDelayedPress(); |
| 4480 | + d->stealMouse = false; |
| 4481 | + d->pressed = false; |
| 4482 | + } |
| 4483 | + return false; |
| 4484 | +} |
| 4485 | + |
| 4486 | +/*! \internal |
| 4487 | + Despite the name, this function filters all pointer events on their way to any child within. |
| 4488 | + Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver. |
| 4489 | +*/ |
| 4490 | +bool StretchFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e) |
| 4491 | +{ |
| 4492 | + Q_D(StretchFlickable); |
| 4493 | + QPointerEvent *pointerEvent = e->isPointerEvent() ? static_cast<QPointerEvent *>(e) : nullptr; |
| 4494 | + |
| 4495 | + auto wantsPointerEvent_helper = [this, d, i, pointerEvent]() { |
| 4496 | + Q_ASSERT(pointerEvent); |
| 4497 | + QQuickDeliveryAgentPrivate::localizePointerEvent(pointerEvent, this); |
| 4498 | + const bool wants = d->wantsPointerEvent(pointerEvent); |
| 4499 | + // re-localize event back to \a i before returning |
| 4500 | + QQuickDeliveryAgentPrivate::localizePointerEvent(pointerEvent, i); |
| 4501 | + return wants; |
| 4502 | + }; |
| 4503 | + |
| 4504 | + if (!isVisible() || !isEnabled() || !isInteractive() || |
| 4505 | + (pointerEvent && !wantsPointerEvent_helper())) { |
| 4506 | + d->cancelInteraction(); |
| 4507 | + return QQuickItem::childMouseEventFilter(i, e); |
| 4508 | + } |
| 4509 | + |
| 4510 | + if (e->type() == QEvent::UngrabMouse) { |
| 4511 | + Q_ASSERT(e->isSinglePointEvent()); |
| 4512 | + auto spe = static_cast<QSinglePointEvent *>(e); |
| 4513 | + const QObject *grabber = spe->exclusiveGrabber(spe->points().first()); |
| 4514 | + qCDebug(lcFilter) << "filtering UngrabMouse" << spe->points().first() << "for" << i << "grabber is" << grabber; |
| 4515 | + if (grabber != this) |
| 4516 | + mouseUngrabEvent(); // A child has been ungrabbed |
| 4517 | + } else if (pointerEvent) { |
| 4518 | + return filterPointerEvent(i, pointerEvent); |
| 4519 | + } |
| 4520 | + |
| 4521 | + return QQuickItem::childMouseEventFilter(i, e); |
| 4522 | +} |
| 4523 | + |
| 4524 | +/*! |
| 4525 | + \qmlproperty real QtQuick::Flickable::maximumFlickVelocity |
| 4526 | + This property holds the maximum velocity that the user can flick the view in pixels/second. |
| 4527 | + |
| 4528 | + The default value is platform dependent. |
| 4529 | +*/ |
| 4530 | +qreal StretchFlickable::maximumFlickVelocity() const |
| 4531 | +{ |
| 4532 | + Q_D(const StretchFlickable); |
| 4533 | + return d->maxVelocity; |
| 4534 | +} |
| 4535 | + |
| 4536 | +void StretchFlickable::setMaximumFlickVelocity(qreal v) |
| 4537 | +{ |
| 4538 | + Q_D(StretchFlickable); |
| 4539 | + if (v == d->maxVelocity) |
| 4540 | + return; |
| 4541 | + d->maxVelocity = v; |
| 4542 | + emit maximumFlickVelocityChanged(); |
| 4543 | +} |
| 4544 | + |
| 4545 | +/*! |
| 4546 | + \qmlproperty real QtQuick::Flickable::flickDeceleration |
| 4547 | + This property holds the rate at which a flick will decelerate: |
| 4548 | + the higher the number, the faster it slows down when the user stops |
| 4549 | + flicking via touch. For example 0.0001 is nearly |
| 4550 | + "frictionless", and 10000 feels quite "sticky". |
| 4551 | + |
| 4552 | + The default value is platform dependent. Values of zero or less are not allowed. |
| 4553 | +*/ |
| 4554 | +qreal StretchFlickable::flickDeceleration() const |
| 4555 | +{ |
| 4556 | + Q_D(const StretchFlickable); |
| 4557 | + return d->deceleration; |
| 4558 | +} |
| 4559 | + |
| 4560 | +void StretchFlickable::setFlickDeceleration(qreal deceleration) |
| 4561 | +{ |
| 4562 | + Q_D(StretchFlickable); |
| 4563 | + if (deceleration == d->deceleration) |
| 4564 | + return; |
| 4565 | + d->deceleration = qMax(0.001, deceleration); |
| 4566 | + emit flickDecelerationChanged(); |
| 4567 | +} |
| 4568 | + |
| 4569 | +qreal StretchFlickable::dragResistanceRange() const |
| 4570 | +{ |
| 4571 | + Q_D(const StretchFlickable); |
| 4572 | + return d->dragResistanceRange; |
| 4573 | +} |
| 4574 | + |
| 4575 | +void StretchFlickable::setDragResistanceRange(qreal range) |
| 4576 | +{ |
| 4577 | + Q_D(StretchFlickable); |
| 4578 | + range = qMax(qreal(1), range); |
| 4579 | + if (range == d->dragResistanceRange) |
| 4580 | + return; |
| 4581 | + d->dragResistanceRange = range; |
| 4582 | + emit dragResistanceRangeChanged(); |
| 4583 | +} |
| 4584 | + |
| 4585 | +qreal StretchFlickable::dragOvershootMax() const |
| 4586 | +{ |
| 4587 | + Q_D(const StretchFlickable); |
| 4588 | + return d->dragOvershootMax; |
| 4589 | +} |
| 4590 | + |
| 4591 | +void StretchFlickable::setDragOvershootMax(qreal maxOvershoot) |
| 4592 | +{ |
| 4593 | + Q_D(StretchFlickable); |
| 4594 | + maxOvershoot = qMax(qreal(0), maxOvershoot); |
| 4595 | + if (maxOvershoot == d->dragOvershootMax) |
| 4596 | + return; |
| 4597 | + d->dragOvershootMax = maxOvershoot; |
| 4598 | + emit dragOvershootMaxChanged(); |
| 4599 | +} |
| 4600 | + |
| 4601 | +bool StretchFlickable::isFlicking() const |
| 4602 | +{ |
| 4603 | + Q_D(const StretchFlickable); |
| 4604 | + return d->hData.flicking || d->vData.flicking; |
| 4605 | +} |
| 4606 | + |
| 4607 | +/*! |
| 4608 | + \qmlproperty bool QtQuick::Flickable::flicking |
| 4609 | + \qmlproperty bool QtQuick::Flickable::flickingHorizontally |
| 4610 | + \qmlproperty bool QtQuick::Flickable::flickingVertically |
| 4611 | + |
| 4612 | + These properties describe whether the view is currently moving horizontally, |
| 4613 | + vertically or in either direction, due to the user flicking the view. |
| 4614 | +*/ |
| 4615 | +bool StretchFlickable::isFlickingHorizontally() const |
| 4616 | +{ |
| 4617 | + Q_D(const StretchFlickable); |
| 4618 | + return d->hData.flicking; |
| 4619 | +} |
| 4620 | + |
| 4621 | +bool StretchFlickable::isFlickingVertically() const |
| 4622 | +{ |
| 4623 | + Q_D(const StretchFlickable); |
| 4624 | + return d->vData.flicking; |
| 4625 | +} |
| 4626 | + |
| 4627 | +/*! |
| 4628 | + \qmlproperty bool QtQuick::Flickable::dragging |
| 4629 | + \qmlproperty bool QtQuick::Flickable::draggingHorizontally |
| 4630 | + \qmlproperty bool QtQuick::Flickable::draggingVertically |
| 4631 | + |
| 4632 | + These properties describe whether the view is currently moving horizontally, |
| 4633 | + vertically or in either direction, due to the user dragging the view. |
| 4634 | +*/ |
| 4635 | +bool StretchFlickable::isDragging() const |
| 4636 | +{ |
| 4637 | + Q_D(const StretchFlickable); |
| 4638 | + return d->hData.dragging || d->vData.dragging; |
| 4639 | +} |
| 4640 | + |
| 4641 | +bool StretchFlickable::isDraggingHorizontally() const |
| 4642 | +{ |
| 4643 | + Q_D(const StretchFlickable); |
| 4644 | + return d->hData.dragging; |
| 4645 | +} |
| 4646 | + |
| 4647 | +bool StretchFlickable::isDraggingVertically() const |
| 4648 | +{ |
| 4649 | + Q_D(const StretchFlickable); |
| 4650 | + return d->vData.dragging; |
| 4651 | +} |
| 4652 | + |
| 4653 | +void StretchFlickablePrivate::draggingStarting() |
| 4654 | +{ |
| 4655 | + Q_Q(StretchFlickable); |
| 4656 | + bool wasDragging = hData.dragging || vData.dragging; |
| 4657 | + if (hMoved && !hData.dragging) { |
| 4658 | + hData.dragging = true; |
| 4659 | + emit q->draggingHorizontallyChanged(); |
| 4660 | + } |
| 4661 | + if (vMoved && !vData.dragging) { |
| 4662 | + vData.dragging = true; |
| 4663 | + emit q->draggingVerticallyChanged(); |
| 4664 | + } |
| 4665 | + if (!wasDragging && (hData.dragging || vData.dragging)) { |
| 4666 | + emit q->draggingChanged(); |
| 4667 | + emit q->dragStarted(); |
| 4668 | + } |
| 4669 | +} |
| 4670 | + |
| 4671 | +void StretchFlickablePrivate::draggingEnding() |
| 4672 | +{ |
| 4673 | + Q_Q(StretchFlickable); |
| 4674 | + const bool wasDragging = hData.dragging || vData.dragging; |
| 4675 | + if (hData.dragging) { |
| 4676 | + hData.dragging = false; |
| 4677 | + emit q->draggingHorizontallyChanged(); |
| 4678 | + } |
| 4679 | + if (vData.dragging) { |
| 4680 | + vData.dragging = false; |
| 4681 | + emit q->draggingVerticallyChanged(); |
| 4682 | + } |
| 4683 | + if (wasDragging) { |
| 4684 | + if (!hData.dragging && !vData.dragging) { |
| 4685 | + emit q->draggingChanged(); |
| 4686 | + emit q->dragEnded(); |
| 4687 | + } |
| 4688 | + hData.inRebound = false; |
| 4689 | + vData.inRebound = false; |
| 4690 | + } |
| 4691 | +} |
| 4692 | + |
| 4693 | +bool StretchFlickablePrivate::isViewMoving() const |
| 4694 | +{ |
| 4695 | + if (timeline.isActive() |
| 4696 | + || (hData.transitionToBounds && hData.transitionToBounds->isActive()) |
| 4697 | + || (vData.transitionToBounds && vData.transitionToBounds->isActive()) ) { |
| 4698 | + return true; |
| 4699 | + } |
| 4700 | + return false; |
| 4701 | +} |
| 4702 | + |
| 4703 | +/*! |
| 4704 | + \qmlproperty int QtQuick::Flickable::pressDelay |
| 4705 | + |
| 4706 | + This property holds the time to delay (ms) delivering a press to |
| 4707 | + children of the Flickable. This can be useful where reacting |
| 4708 | + to a press before a flicking action has undesirable effects. |
| 4709 | + |
| 4710 | + If the flickable is dragged/flicked before the delay times out |
| 4711 | + the press event will not be delivered. If the button is released |
| 4712 | + within the timeout, both the press and release will be delivered. |
| 4713 | + |
| 4714 | + Note that for nested Flickables with pressDelay set, the pressDelay of |
| 4715 | + outer Flickables is overridden by the innermost Flickable. If the drag |
| 4716 | + exceeds the platform drag threshold, the press event will be delivered |
| 4717 | + regardless of this property. |
| 4718 | + |
| 4719 | + \sa QStyleHints |
| 4720 | +*/ |
| 4721 | +int StretchFlickable::pressDelay() const |
| 4722 | +{ |
| 4723 | + Q_D(const StretchFlickable); |
| 4724 | + return d->pressDelay; |
| 4725 | +} |
| 4726 | + |
| 4727 | +void StretchFlickable::setPressDelay(int delay) |
| 4728 | +{ |
| 4729 | + Q_D(StretchFlickable); |
| 4730 | + if (d->pressDelay == delay) |
| 4731 | + return; |
| 4732 | + d->pressDelay = delay; |
| 4733 | + emit pressDelayChanged(); |
| 4734 | +} |
| 4735 | + |
| 4736 | +/*! |
| 4737 | + \qmlproperty bool QtQuick::Flickable::moving |
| 4738 | + \qmlproperty bool QtQuick::Flickable::movingHorizontally |
| 4739 | + \qmlproperty bool QtQuick::Flickable::movingVertically |
| 4740 | + |
| 4741 | + These properties describe whether the view is currently moving horizontally, |
| 4742 | + vertically or in either direction, due to the user either dragging or |
| 4743 | + flicking the view. |
| 4744 | +*/ |
| 4745 | + |
| 4746 | +bool StretchFlickable::isMoving() const |
| 4747 | +{ |
| 4748 | + Q_D(const StretchFlickable); |
| 4749 | + return d->hData.moving || d->vData.moving; |
| 4750 | +} |
| 4751 | + |
| 4752 | +bool StretchFlickable::isMovingHorizontally() const |
| 4753 | +{ |
| 4754 | + Q_D(const StretchFlickable); |
| 4755 | + return d->hData.moving; |
| 4756 | +} |
| 4757 | + |
| 4758 | +bool StretchFlickable::isMovingVertically() const |
| 4759 | +{ |
| 4760 | + Q_D(const StretchFlickable); |
| 4761 | + return d->vData.moving; |
| 4762 | +} |
| 4763 | + |
| 4764 | +void StretchFlickable::velocityTimelineCompleted() |
| 4765 | +{ |
| 4766 | + Q_D(StretchFlickable); |
| 4767 | + if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive()) |
| 4768 | + || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) { |
| 4769 | + return; |
| 4770 | + } |
| 4771 | + // With subclasses such as GridView, velocityTimeline.completed is emitted repeatedly: |
| 4772 | + // for example setting currentIndex results in a visual "flick" which the user |
| 4773 | + // didn't initiate directly. We don't want to end movement repeatedly, and in |
| 4774 | + // that case movementEnding will happen after the sequence of movements ends. |
| 4775 | + if (d->vData.flicking) |
| 4776 | + movementEnding(); |
| 4777 | + d->updateBeginningEnd(); |
| 4778 | +} |
| 4779 | + |
| 4780 | +void StretchFlickable::timelineCompleted() |
| 4781 | +{ |
| 4782 | + Q_D(StretchFlickable); |
| 4783 | + if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive()) |
| 4784 | + || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) { |
| 4785 | + return; |
| 4786 | + } |
| 4787 | + movementEnding(); |
| 4788 | + d->updateBeginningEnd(); |
| 4789 | +} |
| 4790 | + |
| 4791 | +void StretchFlickable::movementStarting() |
| 4792 | +{ |
| 4793 | + Q_D(StretchFlickable); |
| 4794 | + bool wasMoving = d->hData.moving || d->vData.moving; |
| 4795 | + if (d->hMoved && !d->hData.moving) { |
| 4796 | + d->hData.moving = true; |
| 4797 | + emit movingHorizontallyChanged(); |
| 4798 | + } |
| 4799 | + if (d->vMoved && !d->vData.moving) { |
| 4800 | + d->vData.moving = true; |
| 4801 | + emit movingVerticallyChanged(); |
| 4802 | + } |
| 4803 | + |
| 4804 | + if (!wasMoving && (d->hData.moving || d->vData.moving)) { |
| 4805 | + emit movingChanged(); |
| 4806 | + emit movementStarted(); |
| 4807 | +#if QT_CONFIG(accessibility) |
| 4808 | + if (QAccessible::isActive()) { |
| 4809 | + QAccessibleEvent ev(this, QAccessible::ScrollingStart); |
| 4810 | + QAccessible::updateAccessibility(&ev); |
| 4811 | + } |
| 4812 | +#endif |
| 4813 | + } |
| 4814 | +} |
| 4815 | + |
| 4816 | +void StretchFlickable::movementEnding() |
| 4817 | +{ |
| 4818 | + movementEnding(true, true); |
| 4819 | +} |
| 4820 | + |
| 4821 | +void StretchFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding) |
| 4822 | +{ |
| 4823 | + Q_D(StretchFlickable); |
| 4824 | + |
| 4825 | + // emit flicking signals |
| 4826 | + const bool wasFlicking = d->hData.flicking || d->vData.flicking; |
| 4827 | + if (hMovementEnding && d->hData.flicking) { |
| 4828 | + d->hData.flicking = false; |
| 4829 | + emit flickingHorizontallyChanged(); |
| 4830 | + } |
| 4831 | + if (vMovementEnding && d->vData.flicking) { |
| 4832 | + d->vData.flicking = false; |
| 4833 | + emit flickingVerticallyChanged(); |
| 4834 | + } |
| 4835 | + if (wasFlicking && (!d->hData.flicking || !d->vData.flicking)) { |
| 4836 | + emit flickingChanged(); |
| 4837 | + emit flickEnded(); |
| 4838 | + } else if (d->hData.flickingWhenDragBegan || d->vData.flickingWhenDragBegan) { |
| 4839 | + d->hData.flickingWhenDragBegan = !hMovementEnding; |
| 4840 | + d->vData.flickingWhenDragBegan = !vMovementEnding; |
| 4841 | + emit flickEnded(); |
| 4842 | + } |
| 4843 | + |
| 4844 | + // emit moving signals |
| 4845 | + bool wasMoving = isMoving(); |
| 4846 | + if (hMovementEnding && d->hData.moving |
| 4847 | + && (!d->pressed && !d->stealMouse)) { |
| 4848 | + d->hData.moving = false; |
| 4849 | + d->hMoved = false; |
| 4850 | + emit movingHorizontallyChanged(); |
| 4851 | + } |
| 4852 | + if (vMovementEnding && d->vData.moving |
| 4853 | + && (!d->pressed && !d->stealMouse)) { |
| 4854 | + d->vData.moving = false; |
| 4855 | + d->vMoved = false; |
| 4856 | + emit movingVerticallyChanged(); |
| 4857 | + } |
| 4858 | + if (wasMoving && !isMoving()) { |
| 4859 | + emit movingChanged(); |
| 4860 | + emit movementEnded(); |
| 4861 | +#if QT_CONFIG(accessibility) |
| 4862 | + if (QAccessible::isActive()) { |
| 4863 | + QAccessibleEvent ev(this, QAccessible::ScrollingEnd); |
| 4864 | + QAccessible::updateAccessibility(&ev); |
| 4865 | + } |
| 4866 | +#endif |
| 4867 | + } |
| 4868 | + |
| 4869 | + if (hMovementEnding) { |
| 4870 | + d->hData.fixingUp = false; |
| 4871 | + d->hData.smoothVelocity.setValue(0); |
| 4872 | + d->hData.previousDragDelta = 0.0; |
| 4873 | + } |
| 4874 | + if (vMovementEnding) { |
| 4875 | + d->vData.fixingUp = false; |
| 4876 | + d->vData.smoothVelocity.setValue(0); |
| 4877 | + d->vData.previousDragDelta = 0.0; |
| 4878 | + } |
| 4879 | +} |
| 4880 | + |
| 4881 | +void StretchFlickablePrivate::updateVelocity() |
| 4882 | +{ |
| 4883 | + Q_Q(StretchFlickable); |
| 4884 | + emit q->horizontalVelocityChanged(); |
| 4885 | + emit q->verticalVelocityChanged(); |
| 4886 | +} |
| 4887 | + |
| 4888 | +/*! |
| 4889 | + \qmlproperty real QtQuick::Flickable::horizontalOvershoot |
| 4890 | + \since 5.9 |
| 4891 | + |
| 4892 | + This property holds the horizontal overshoot, that is, the horizontal distance by |
| 4893 | + which the contents has been dragged or flicked past the bounds of the flickable. |
| 4894 | + The value is negative when the content is dragged or flicked beyond the beginning, |
| 4895 | + and positive when beyond the end; \c 0.0 otherwise. |
| 4896 | + |
| 4897 | + Whether the values are reported for dragging and/or flicking is determined by |
| 4898 | + \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement |
| 4899 | + is \c Flickable.StopAtBounds. |
| 4900 | + |
| 4901 | + \sa verticalOvershoot, boundsBehavior, boundsMovement |
| 4902 | +*/ |
| 4903 | +qreal StretchFlickable::horizontalOvershoot() const |
| 4904 | +{ |
| 4905 | + Q_D(const StretchFlickable); |
| 4906 | + return d->hData.overshoot; |
| 4907 | +} |
| 4908 | + |
| 4909 | +/*! |
| 4910 | + \qmlproperty real QtQuick::Flickable::verticalOvershoot |
| 4911 | + \since 5.9 |
| 4912 | + |
| 4913 | + This property holds the vertical overshoot, that is, the vertical distance by |
| 4914 | + which the contents has been dragged or flicked past the bounds of the flickable. |
| 4915 | + The value is negative when the content is dragged or flicked beyond the beginning, |
| 4916 | + and positive when beyond the end; \c 0.0 otherwise. |
| 4917 | + |
| 4918 | + Whether the values are reported for dragging and/or flicking is determined by |
| 4919 | + \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement |
| 4920 | + is \c Flickable.StopAtBounds. |
| 4921 | + |
| 4922 | + \sa horizontalOvershoot, boundsBehavior, boundsMovement |
| 4923 | +*/ |
| 4924 | +qreal StretchFlickable::verticalOvershoot() const |
| 4925 | +{ |
| 4926 | + Q_D(const StretchFlickable); |
| 4927 | + return d->vData.overshoot; |
| 4928 | +} |
| 4929 | + |
| 4930 | +/*! |
| 4931 | + \qmlproperty enumeration QtQuick::Flickable::boundsMovement |
| 4932 | + \since 5.10 |
| 4933 | + |
| 4934 | + This property holds whether the flickable will give a feeling that the edges of the |
| 4935 | + view are soft, rather than a hard physical boundary. |
| 4936 | + |
| 4937 | + The \c boundsMovement can be one of: |
| 4938 | + |
| 4939 | + \list |
| 4940 | + \li Flickable.StopAtBounds - this allows implementing custom edge effects where the |
| 4941 | + contents do not follow drags or flicks beyond the bounds of the flickable. The values |
| 4942 | + of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom |
| 4943 | + edge effects. |
| 4944 | + \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or |
| 4945 | + flicks beyond the bounds of the flickable is determined by \l boundsBehavior. |
| 4946 | + \endlist |
| 4947 | + |
| 4948 | + The following example keeps the contents within bounds and instead applies a flip |
| 4949 | + effect when flicked over horizontal bounds: |
| 4950 | + \code |
| 4951 | + Flickable { |
| 4952 | + id: flickable |
| 4953 | + boundsMovement: Flickable.StopAtBounds |
| 4954 | + boundsBehavior: Flickable.DragAndOvershootBounds |
| 4955 | + transform: Rotation { |
| 4956 | + axis { x: 0; y: 1; z: 0 } |
| 4957 | + origin.x: flickable.width / 2 |
| 4958 | + origin.y: flickable.height / 2 |
| 4959 | + angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot)) |
| 4960 | + } |
| 4961 | + } |
| 4962 | + \endcode |
| 4963 | + |
| 4964 | + The following example keeps the contents within bounds and instead applies an opacity |
| 4965 | + effect when dragged over vertical bounds: |
| 4966 | + \code |
| 4967 | + Flickable { |
| 4968 | + boundsMovement: Flickable.StopAtBounds |
| 4969 | + boundsBehavior: Flickable.DragOverBounds |
| 4970 | + opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height) |
| 4971 | + } |
| 4972 | + \endcode |
| 4973 | + |
| 4974 | + \sa boundsBehavior, verticalOvershoot, horizontalOvershoot |
| 4975 | +*/ |
| 4976 | +StretchFlickable::BoundsMovement StretchFlickable::boundsMovement() const |
| 4977 | +{ |
| 4978 | + Q_D(const StretchFlickable); |
| 4979 | + return d->boundsMovement; |
| 4980 | +} |
| 4981 | + |
| 4982 | +void StretchFlickable::setBoundsMovement(BoundsMovement movement) |
| 4983 | +{ |
| 4984 | + Q_D(StretchFlickable); |
| 4985 | + if (d->boundsMovement == movement) |
| 4986 | + return; |
| 4987 | + |
| 4988 | + d->boundsMovement = movement; |
| 4989 | + emit boundsMovementChanged(); |
| 4990 | +} |
| 4991 | + |
| 4992 | +QT_END_NAMESPACE |
| 4993 | + |
| 4994 | +#include "moc_stretchflickable_p_p.cpp" |
| 4995 | + |
| 4996 | +#include "moc_stretchflickable_p.cpp" |
| 4997 | diff --git a/src/stretchflickable_p.h b/src/stretchflickable_p.h |
| 4998 | new file mode 100644 |
| 4999 | index 0000000..9889721 |
| 5000 | --- /dev/null |
| 5001 | +++ b/src/stretchflickable_p.h |
| 5002 | @@ -0,0 +1,311 @@ |
| 5003 | +// Copyright (C) 2019 The Qt Company Ltd. |
| 5004 | +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 5005 | + |
| 5006 | +#ifndef STRETCHFLICKABLE_P_H |
| 5007 | +#define STRETCHFLICKABLE_P_H |
| 5008 | + |
| 5009 | +// |
| 5010 | +// W A R N I N G |
| 5011 | +// ------------- |
| 5012 | +// |
| 5013 | +// This file is not part of the Qt API. It exists purely as an |
| 5014 | +// implementation detail. This header file may change from version to |
| 5015 | +// version without notice, or even be removed. |
| 5016 | +// |
| 5017 | +// We mean it. |
| 5018 | +// |
| 5019 | + |
| 5020 | +#include <QtQuick/qquickitem.h> |
| 5021 | +#include <private/qtquickglobal_p.h> |
| 5022 | + |
| 5023 | +QT_BEGIN_NAMESPACE |
| 5024 | + |
| 5025 | +class StretchFlickablePrivate; |
| 5026 | +class StretchFlickableVisibleArea; |
| 5027 | +class QPointerEvent; |
| 5028 | +class StretchFlickable : public QQuickItem |
| 5029 | +{ |
| 5030 | + Q_OBJECT |
| 5031 | + |
| 5032 | + Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth NOTIFY contentWidthChanged) |
| 5033 | + Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged) |
| 5034 | + Q_PROPERTY(qreal contentX READ contentX WRITE setContentX NOTIFY contentXChanged) |
| 5035 | + Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged) |
| 5036 | + Q_PROPERTY(QQuickItem *contentItem READ contentItem CONSTANT) |
| 5037 | + |
| 5038 | + Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged) |
| 5039 | + Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged) |
| 5040 | + Q_PROPERTY(qreal originY READ originY NOTIFY originYChanged) |
| 5041 | + |
| 5042 | + Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged) |
| 5043 | + Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged) |
| 5044 | + Q_PROPERTY(qreal originX READ originX NOTIFY originXChanged) |
| 5045 | + |
| 5046 | + Q_PROPERTY(qreal horizontalVelocity READ horizontalVelocity NOTIFY horizontalVelocityChanged) |
| 5047 | + Q_PROPERTY(qreal verticalVelocity READ verticalVelocity NOTIFY verticalVelocityChanged) |
| 5048 | + |
| 5049 | + Q_PROPERTY(BoundsBehavior boundsBehavior READ boundsBehavior WRITE setBoundsBehavior NOTIFY boundsBehaviorChanged) |
| 5050 | + Q_PROPERTY(BoundsMovement boundsMovement READ boundsMovement WRITE setBoundsMovement NOTIFY boundsMovementChanged) |
| 5051 | + Q_PROPERTY(QQuickTransition *rebound READ rebound WRITE setRebound NOTIFY reboundChanged) |
| 5052 | + Q_PROPERTY(qreal maximumFlickVelocity READ maximumFlickVelocity WRITE setMaximumFlickVelocity NOTIFY maximumFlickVelocityChanged) |
| 5053 | + Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) |
| 5054 | + // characteristic drag distance (in px) of the progressive overshoot |
| 5055 | + // resistance curve: small overshoots track the finger closely, larger |
| 5056 | + // ones increasingly resist, asymptoting as the drag approaches this range |
| 5057 | + Q_PROPERTY(qreal dragResistanceRange READ dragResistanceRange WRITE setDragResistanceRange NOTIFY dragResistanceRangeChanged) |
| 5058 | + // hard cap (in px) on how far drag-over-bounds can stretch the content - |
| 5059 | + // the drag never visually overshoots past this, no matter how far you pull |
| 5060 | + Q_PROPERTY(qreal dragOvershootMax READ dragOvershootMax WRITE setDragOvershootMax NOTIFY dragOvershootMaxChanged) |
| 5061 | + Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged) |
| 5062 | + Q_PROPERTY(bool movingHorizontally READ isMovingHorizontally NOTIFY movingHorizontallyChanged) |
| 5063 | + Q_PROPERTY(bool movingVertically READ isMovingVertically NOTIFY movingVerticallyChanged) |
| 5064 | + Q_PROPERTY(bool flicking READ isFlicking NOTIFY flickingChanged) |
| 5065 | + Q_PROPERTY(bool flickingHorizontally READ isFlickingHorizontally NOTIFY flickingHorizontallyChanged) |
| 5066 | + Q_PROPERTY(bool flickingVertically READ isFlickingVertically NOTIFY flickingVerticallyChanged) |
| 5067 | + Q_PROPERTY(bool dragging READ isDragging NOTIFY draggingChanged) |
| 5068 | + Q_PROPERTY(bool draggingHorizontally READ isDraggingHorizontally NOTIFY draggingHorizontallyChanged) |
| 5069 | + Q_PROPERTY(bool draggingVertically READ isDraggingVertically NOTIFY draggingVerticallyChanged) |
| 5070 | + Q_PROPERTY(FlickableDirection flickableDirection READ flickableDirection WRITE setFlickableDirection NOTIFY flickableDirectionChanged) |
| 5071 | + |
| 5072 | + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged) |
| 5073 | + Q_PROPERTY(int pressDelay READ pressDelay WRITE setPressDelay NOTIFY pressDelayChanged) |
| 5074 | + |
| 5075 | + Q_PROPERTY(bool atXEnd READ isAtXEnd NOTIFY atXEndChanged) |
| 5076 | + Q_PROPERTY(bool atYEnd READ isAtYEnd NOTIFY atYEndChanged) |
| 5077 | + Q_PROPERTY(bool atXBeginning READ isAtXBeginning NOTIFY atXBeginningChanged) |
| 5078 | + Q_PROPERTY(bool atYBeginning READ isAtYBeginning NOTIFY atYBeginningChanged) |
| 5079 | + |
| 5080 | + Q_PROPERTY(StretchFlickableVisibleArea *visibleArea READ visibleArea CONSTANT) |
| 5081 | + |
| 5082 | + Q_PROPERTY(bool pixelAligned READ pixelAligned WRITE setPixelAligned NOTIFY pixelAlignedChanged) |
| 5083 | + Q_PROPERTY(bool synchronousDrag READ synchronousDrag WRITE setSynchronousDrag NOTIFY synchronousDragChanged) |
| 5084 | + |
| 5085 | + Q_PROPERTY(qreal horizontalOvershoot READ horizontalOvershoot NOTIFY horizontalOvershootChanged) |
| 5086 | + Q_PROPERTY(qreal verticalOvershoot READ verticalOvershoot NOTIFY verticalOvershootChanged) |
| 5087 | + |
| 5088 | + Q_PROPERTY(Qt::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY acceptedButtonsChanged FINAL) |
| 5089 | + |
| 5090 | + Q_PROPERTY(QQmlListProperty<QObject> flickableData READ flickableData) |
| 5091 | + Q_PROPERTY(QQmlListProperty<QQuickItem> flickableChildren READ flickableChildren) |
| 5092 | + Q_CLASSINFO("DefaultProperty", "flickableData") |
| 5093 | + QML_NAMED_ELEMENT(StretchFlickable) |
| 5094 | + |
| 5095 | +public: |
| 5096 | + StretchFlickable(QQuickItem *parent=nullptr); |
| 5097 | + ~StretchFlickable() override; |
| 5098 | + |
| 5099 | + QQmlListProperty<QObject> flickableData(); |
| 5100 | + QQmlListProperty<QQuickItem> flickableChildren(); |
| 5101 | + |
| 5102 | + enum BoundsBehaviorFlag { |
| 5103 | + StopAtBounds = 0x0, |
| 5104 | + DragOverBounds = 0x1, |
| 5105 | + OvershootBounds = 0x2, |
| 5106 | + DragAndOvershootBounds = DragOverBounds | OvershootBounds |
| 5107 | + }; |
| 5108 | + Q_DECLARE_FLAGS(BoundsBehavior, BoundsBehaviorFlag) |
| 5109 | + Q_FLAG(BoundsBehavior) |
| 5110 | + |
| 5111 | + BoundsBehavior boundsBehavior() const; |
| 5112 | + void setBoundsBehavior(BoundsBehavior); |
| 5113 | + |
| 5114 | + enum BoundsMovement { |
| 5115 | + // StopAtBounds = 0x0, |
| 5116 | + FollowBoundsBehavior = 0x1 |
| 5117 | + }; |
| 5118 | + Q_ENUM(BoundsMovement) |
| 5119 | + |
| 5120 | + BoundsMovement boundsMovement() const; |
| 5121 | + void setBoundsMovement(BoundsMovement movement); |
| 5122 | + |
| 5123 | + QQuickTransition *rebound() const; |
| 5124 | + void setRebound(QQuickTransition *transition); |
| 5125 | + |
| 5126 | + qreal contentWidth() const; |
| 5127 | + void setContentWidth(qreal); |
| 5128 | + |
| 5129 | + qreal contentHeight() const; |
| 5130 | + void setContentHeight(qreal); |
| 5131 | + |
| 5132 | + qreal contentX() const; |
| 5133 | + virtual void setContentX(qreal pos); |
| 5134 | + |
| 5135 | + qreal contentY() const; |
| 5136 | + virtual void setContentY(qreal pos); |
| 5137 | + |
| 5138 | + qreal topMargin() const; |
| 5139 | + void setTopMargin(qreal m); |
| 5140 | + |
| 5141 | + qreal bottomMargin() const; |
| 5142 | + void setBottomMargin(qreal m); |
| 5143 | + |
| 5144 | + qreal leftMargin() const; |
| 5145 | + void setLeftMargin(qreal m); |
| 5146 | + |
| 5147 | + qreal rightMargin() const; |
| 5148 | + void setRightMargin(qreal m); |
| 5149 | + |
| 5150 | + virtual qreal originY() const; |
| 5151 | + virtual qreal originX() const; |
| 5152 | + |
| 5153 | + bool isMoving() const; |
| 5154 | + bool isMovingHorizontally() const; |
| 5155 | + bool isMovingVertically() const; |
| 5156 | + bool isFlicking() const; |
| 5157 | + bool isFlickingHorizontally() const; |
| 5158 | + bool isFlickingVertically() const; |
| 5159 | + bool isDragging() const; |
| 5160 | + bool isDraggingHorizontally() const; |
| 5161 | + bool isDraggingVertically() const; |
| 5162 | + |
| 5163 | + int pressDelay() const; |
| 5164 | + void setPressDelay(int delay); |
| 5165 | + |
| 5166 | + qreal maximumFlickVelocity() const; |
| 5167 | + void setMaximumFlickVelocity(qreal); |
| 5168 | + |
| 5169 | + qreal flickDeceleration() const; |
| 5170 | + void setFlickDeceleration(qreal); |
| 5171 | + |
| 5172 | + qreal dragResistanceRange() const; |
| 5173 | + void setDragResistanceRange(qreal); |
| 5174 | + |
| 5175 | + qreal dragOvershootMax() const; |
| 5176 | + void setDragOvershootMax(qreal); |
| 5177 | + |
| 5178 | + bool isInteractive() const; |
| 5179 | + void setInteractive(bool); |
| 5180 | + |
| 5181 | + qreal horizontalVelocity() const; |
| 5182 | + qreal verticalVelocity() const; |
| 5183 | + |
| 5184 | + bool isAtXEnd() const; |
| 5185 | + bool isAtXBeginning() const; |
| 5186 | + bool isAtYEnd() const; |
| 5187 | + bool isAtYBeginning() const; |
| 5188 | + |
| 5189 | + QQuickItem *contentItem() const; |
| 5190 | + |
| 5191 | + enum FlickableDirection { AutoFlickDirection=0x0, HorizontalFlick=0x1, VerticalFlick=0x2, HorizontalAndVerticalFlick=0x3, |
| 5192 | + AutoFlickIfNeeded=0xc }; |
| 5193 | + Q_ENUM(FlickableDirection) |
| 5194 | + FlickableDirection flickableDirection() const; |
| 5195 | + void setFlickableDirection(FlickableDirection); |
| 5196 | + |
| 5197 | + bool pixelAligned() const; |
| 5198 | + void setPixelAligned(bool align); |
| 5199 | + |
| 5200 | + bool synchronousDrag() const; |
| 5201 | + void setSynchronousDrag(bool v); |
| 5202 | + |
| 5203 | + Qt::MouseButtons acceptedButtons() const; |
| 5204 | + void setAcceptedButtons(Qt::MouseButtons buttons); |
| 5205 | + |
| 5206 | + qreal horizontalOvershoot() const; |
| 5207 | + qreal verticalOvershoot() const; |
| 5208 | + |
| 5209 | + Q_INVOKABLE void resizeContent(qreal w, qreal h, QPointF center); |
| 5210 | + Q_INVOKABLE void returnToBounds(); |
| 5211 | + Q_INVOKABLE void flick(qreal xVelocity, qreal yVelocity); |
| 5212 | + Q_INVOKABLE void cancelFlick(); |
| 5213 | + |
| 5214 | +Q_SIGNALS: |
| 5215 | + void contentWidthChanged(); |
| 5216 | + void contentHeightChanged(); |
| 5217 | + void contentXChanged(); |
| 5218 | + void contentYChanged(); |
| 5219 | + void topMarginChanged(); |
| 5220 | + void bottomMarginChanged(); |
| 5221 | + void leftMarginChanged(); |
| 5222 | + void rightMarginChanged(); |
| 5223 | + void originYChanged(); |
| 5224 | + void originXChanged(); |
| 5225 | + void movingChanged(); |
| 5226 | + void movingHorizontallyChanged(); |
| 5227 | + void movingVerticallyChanged(); |
| 5228 | + void flickingChanged(); |
| 5229 | + void flickingHorizontallyChanged(); |
| 5230 | + void flickingVerticallyChanged(); |
| 5231 | + void draggingChanged(); |
| 5232 | + void draggingHorizontallyChanged(); |
| 5233 | + void draggingVerticallyChanged(); |
| 5234 | + void horizontalVelocityChanged(); |
| 5235 | + void verticalVelocityChanged(); |
| 5236 | + void isAtBoundaryChanged(); |
| 5237 | + void flickableDirectionChanged(); |
| 5238 | + void interactiveChanged(); |
| 5239 | + void boundsBehaviorChanged(); |
| 5240 | + void boundsMovementChanged(); |
| 5241 | + void reboundChanged(); |
| 5242 | + void maximumFlickVelocityChanged(); |
| 5243 | + void flickDecelerationChanged(); |
| 5244 | + void dragResistanceRangeChanged(); |
| 5245 | + void dragOvershootMaxChanged(); |
| 5246 | + void pressDelayChanged(); |
| 5247 | + void movementStarted(); |
| 5248 | + void movementEnded(); |
| 5249 | + void flickStarted(); |
| 5250 | + void flickEnded(); |
| 5251 | + void dragStarted(); |
| 5252 | + void dragEnded(); |
| 5253 | + void pixelAlignedChanged(); |
| 5254 | + void synchronousDragChanged(); |
| 5255 | + void horizontalOvershootChanged(); |
| 5256 | + void verticalOvershootChanged(); |
| 5257 | + |
| 5258 | + void atXEndChanged(); |
| 5259 | + void atYEndChanged(); |
| 5260 | + void atXBeginningChanged(); |
| 5261 | + void atYBeginningChanged(); |
| 5262 | + |
| 5263 | + void acceptedButtonsChanged(); |
| 5264 | + |
| 5265 | +protected: |
| 5266 | + bool childMouseEventFilter(QQuickItem *, QEvent *) override; |
| 5267 | + void mousePressEvent(QMouseEvent *event) override; |
| 5268 | + void mouseMoveEvent(QMouseEvent *event) override; |
| 5269 | + void mouseReleaseEvent(QMouseEvent *event) override; |
| 5270 | + void touchEvent(QTouchEvent *event) override; |
| 5271 | +#if QT_CONFIG(wheelevent) |
| 5272 | + void wheelEvent(QWheelEvent *event) override; |
| 5273 | +#endif |
| 5274 | + void timerEvent(QTimerEvent *event) override; |
| 5275 | + |
| 5276 | + StretchFlickableVisibleArea *visibleArea(); |
| 5277 | + |
| 5278 | +protected Q_SLOTS: |
| 5279 | + void movementStarting(); |
| 5280 | + void movementEnding(); |
| 5281 | + void movementEnding(bool hMovementEnding, bool vMovementEnding); |
| 5282 | + void velocityTimelineCompleted(); |
| 5283 | + void timelineCompleted(); |
| 5284 | + |
| 5285 | +protected: |
| 5286 | + virtual qreal minXExtent() const; |
| 5287 | + virtual qreal minYExtent() const; |
| 5288 | + virtual qreal maxXExtent() const; |
| 5289 | + virtual qreal maxYExtent() const; |
| 5290 | + qreal vWidth() const; |
| 5291 | + qreal vHeight() const; |
| 5292 | + void componentComplete() override; |
| 5293 | + virtual void viewportMoved(Qt::Orientations orient); |
| 5294 | + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; |
| 5295 | + void mouseUngrabEvent() override; |
| 5296 | + bool filterPointerEvent(QQuickItem *receiver, QPointerEvent *event); |
| 5297 | + |
| 5298 | + bool xflick() const; |
| 5299 | + bool yflick() const; |
| 5300 | + |
| 5301 | +protected: |
| 5302 | + StretchFlickable(StretchFlickablePrivate &dd, QQuickItem *parent); |
| 5303 | + |
| 5304 | +private: |
| 5305 | + Q_DISABLE_COPY(StretchFlickable) |
| 5306 | + Q_DECLARE_PRIVATE(StretchFlickable) |
| 5307 | + friend class StretchFlickableVisibleArea; |
| 5308 | + friend class StretchFlickableReboundTransition; |
| 5309 | +}; |
| 5310 | + |
| 5311 | +QT_END_NAMESPACE |
| 5312 | + |
| 5313 | +#endif // STRETCHFLICKABLE_P_H |
| 5314 | diff --git a/src/stretchflickable_p_p.h b/src/stretchflickable_p_p.h |
| 5315 | new file mode 100644 |
| 5316 | index 0000000..08242c7 |
| 5317 | --- /dev/null |
| 5318 | +++ b/src/stretchflickable_p_p.h |
| 5319 | @@ -0,0 +1,300 @@ |
| 5320 | +// Copyright (C) 2019 The Qt Company Ltd. |
| 5321 | +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 5322 | + |
| 5323 | +#ifndef STRETCHFLICKABLE_P_P_H |
| 5324 | +#define STRETCHFLICKABLE_P_P_H |
| 5325 | + |
| 5326 | +// |
| 5327 | +// W A R N I N G |
| 5328 | +// ------------- |
| 5329 | +// |
| 5330 | +// This file is not part of the Qt API. It exists purely as an |
| 5331 | +// implementation detail. This header file may change from version to |
| 5332 | +// version without notice, or even be removed. |
| 5333 | +// |
| 5334 | +// We mean it. |
| 5335 | +// |
| 5336 | + |
| 5337 | +#include "stretchflickable_p.h" |
| 5338 | +#include <QtQuick/private/qquickitem_p.h> |
| 5339 | +#include <QtQuick/private/qquickitemchangelistener_p.h> |
| 5340 | + |
| 5341 | +#include <QtQml/qqml.h> |
| 5342 | +#include <QtCore/qdatetime.h> |
| 5343 | + |
| 5344 | +#include "stretchtimeline_p_p.h" |
| 5345 | +#include <private/qquickanimation_p_p.h> |
| 5346 | +#include <private/qquicktransitionmanager_p_p.h> |
| 5347 | +#include <private/qpodvector_p.h> |
| 5348 | + |
| 5349 | +QT_BEGIN_NAMESPACE |
| 5350 | + |
| 5351 | +class StretchFlickableVisibleArea; |
| 5352 | +class QQuickTransition; |
| 5353 | +class StretchFlickableReboundTransition; |
| 5354 | + |
| 5355 | +class StretchFlickablePrivate : public QQuickItemPrivate, |
| 5356 | + public QSafeQuickItemChangeListener<StretchFlickablePrivate> |
| 5357 | +{ |
| 5358 | + Q_DECLARE_PUBLIC(StretchFlickable) |
| 5359 | + |
| 5360 | +public: |
| 5361 | + static inline StretchFlickablePrivate *get(StretchFlickable *o) { return o->d_func(); } |
| 5362 | + |
| 5363 | + StretchFlickablePrivate(); |
| 5364 | + void init(); |
| 5365 | + |
| 5366 | + struct Velocity : public StretchTimeLineValue |
| 5367 | + { |
| 5368 | + Velocity(StretchFlickablePrivate *p) |
| 5369 | + : parent(p) {} |
| 5370 | + void setValue(qreal v) override { |
| 5371 | + if (v != value()) { |
| 5372 | + StretchTimeLineValue::setValue(v); |
| 5373 | + parent->updateVelocity(); |
| 5374 | + } |
| 5375 | + } |
| 5376 | + StretchFlickablePrivate *parent; |
| 5377 | + }; |
| 5378 | + |
| 5379 | + enum MovementReason { Other, SetIndex, Mouse }; |
| 5380 | + |
| 5381 | + struct AxisData { |
| 5382 | + AxisData(StretchFlickablePrivate *fp, void (StretchFlickablePrivate::*func)(qreal)) |
| 5383 | + : move(fp, func) |
| 5384 | + , smoothVelocity(fp), atEnd(false), atBeginning(true) |
| 5385 | + , transitionToSet(false) |
| 5386 | + , fixingUp(false), inOvershoot(false), inRebound(false), moving(false), flicking(false) |
| 5387 | + , flickingWhenDragBegan(false), dragging(false), extentsChanged(false) |
| 5388 | + , explicitValue(false), contentPositionChangedExternallyDuringDrag(false) |
| 5389 | + , minExtentDirty(true), maxExtentDirty(true) |
| 5390 | + {} |
| 5391 | + |
| 5392 | + ~AxisData(); |
| 5393 | + |
| 5394 | + void reset() { |
| 5395 | + velocityBuffer.clear(); |
| 5396 | + dragStartOffset = 0; |
| 5397 | + fixingUp = false; |
| 5398 | + inOvershoot = false; |
| 5399 | + contentPositionChangedExternallyDuringDrag = false; |
| 5400 | + } |
| 5401 | + |
| 5402 | + void markExtentsDirty() { |
| 5403 | + minExtentDirty = true; |
| 5404 | + maxExtentDirty = true; |
| 5405 | + extentsChanged = true; |
| 5406 | + } |
| 5407 | + |
| 5408 | + void resetTransitionTo() { |
| 5409 | + transitionTo = 0; |
| 5410 | + transitionToSet = false; |
| 5411 | + } |
| 5412 | + |
| 5413 | + void addVelocitySample(qreal v, qreal maxVelocity); |
| 5414 | + void updateVelocity(); |
| 5415 | + |
| 5416 | + StretchTimeLineValueProxy<StretchFlickablePrivate> move; |
| 5417 | + StretchFlickableReboundTransition *transitionToBounds = nullptr; |
| 5418 | + qreal viewSize = -1; |
| 5419 | + qreal pressPos = 0; |
| 5420 | + qreal lastPos = 0; |
| 5421 | + qreal dragStartOffset = 0; |
| 5422 | + qreal dragMinBound = 0; |
| 5423 | + qreal dragMaxBound = 0; |
| 5424 | + qreal previousDragDelta = 0; |
| 5425 | + qreal velocity = 0; |
| 5426 | + qreal flickTarget = 0; |
| 5427 | + qreal startMargin = 0; |
| 5428 | + qreal endMargin = 0; |
| 5429 | + qreal origin = 0; |
| 5430 | + qreal overshoot = 0; |
| 5431 | + qreal transitionTo = 0; |
| 5432 | + qreal continuousFlickVelocity = 0; |
| 5433 | + QElapsedTimer velocityTime; |
| 5434 | + int vTime = 0; |
| 5435 | + StretchFlickablePrivate::Velocity smoothVelocity; |
| 5436 | + QPODVector<qreal,10> velocityBuffer; |
| 5437 | + // bitfield |
| 5438 | + uint atEnd : 1; |
| 5439 | + uint atBeginning : 1; |
| 5440 | + uint transitionToSet : 1; |
| 5441 | + uint fixingUp : 1; |
| 5442 | + uint inOvershoot : 1; |
| 5443 | + uint inRebound : 1; |
| 5444 | + uint moving : 1; |
| 5445 | + uint flicking : 1; |
| 5446 | + uint flickingWhenDragBegan : 1; |
| 5447 | + uint dragging : 1; |
| 5448 | + uint extentsChanged : 1; |
| 5449 | + uint explicitValue : 1; |
| 5450 | + uint contentPositionChangedExternallyDuringDrag : 1; |
| 5451 | + // mutable portion of bitfield |
| 5452 | + mutable uint minExtentDirty : 1; |
| 5453 | + mutable uint maxExtentDirty : 1; |
| 5454 | + // end bitfield |
| 5455 | + }; |
| 5456 | + |
| 5457 | + bool flickX(QEvent::Type eventType, qreal velocity); |
| 5458 | + bool flickY(QEvent::Type eventType, qreal velocity); |
| 5459 | + virtual bool flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, |
| 5460 | + StretchTimeLineCallback::Callback fixupCallback, |
| 5461 | + QEvent::Type eventType, qreal velocity); |
| 5462 | + void flickingStarted(bool flickingH, bool flickingV); |
| 5463 | + |
| 5464 | + void fixupX(); |
| 5465 | + void fixupY(); |
| 5466 | + virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent); |
| 5467 | + void adjustContentPos(AxisData &data, qreal toPos); |
| 5468 | + void resetTimeline(AxisData &data); |
| 5469 | + void clearTimeline(); |
| 5470 | + |
| 5471 | + void updateBeginningEnd(); |
| 5472 | + |
| 5473 | + bool isInnermostPressDelay(QQuickItem *item) const; |
| 5474 | + void captureDelayedPress(QQuickItem *item, QPointerEvent *event); |
| 5475 | + void clearDelayedPress(); |
| 5476 | + void replayDelayedPress(); |
| 5477 | + |
| 5478 | + void setViewportX(qreal x); |
| 5479 | + void setViewportY(qreal y); |
| 5480 | + |
| 5481 | + qreal overShootDistance(qreal velocity) const; |
| 5482 | + |
| 5483 | + // characteristic distance (in device pixels) of the progressive drag |
| 5484 | + // resistance curve used in drag() - see StretchFlickable::dragResistanceRange. |
| 5485 | + // Large by design: dragging the full distance only gets you partway to |
| 5486 | + // dragOvershootMax, which is what makes the resistance feel "heavy". |
| 5487 | + qreal dragOvershootRange() const; |
| 5488 | + qreal dragResistanceRange = 600; |
| 5489 | + |
| 5490 | + // hard cap (in device pixels) on how far drag-over-bounds can stretch |
| 5491 | + // the content - see StretchFlickable::dragOvershootMax |
| 5492 | + qreal dragOvershootMaxPixels() const; |
| 5493 | + qreal dragOvershootMax = 50; |
| 5494 | + |
| 5495 | + void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &) override; |
| 5496 | + |
| 5497 | + void draggingStarting(); |
| 5498 | + void draggingEnding(); |
| 5499 | + |
| 5500 | + bool isViewMoving() const; |
| 5501 | + |
| 5502 | + void cancelInteraction(); |
| 5503 | + |
| 5504 | + void addPointerHandler(QQuickPointerHandler *h) override; |
| 5505 | + |
| 5506 | + virtual bool wantsPointerEvent(const QPointerEvent *) { return true; } |
| 5507 | + |
| 5508 | +public: |
| 5509 | + QQuickItem *contentItem; |
| 5510 | + |
| 5511 | + AxisData hData; |
| 5512 | + AxisData vData; |
| 5513 | + |
| 5514 | + MovementReason moveReason = Other; |
| 5515 | + |
| 5516 | + StretchTimeLine timeline; |
| 5517 | + bool hMoved : 1; |
| 5518 | + bool vMoved : 1; |
| 5519 | + bool stealMouse : 1; |
| 5520 | + bool pressed : 1; |
| 5521 | + bool scrollingPhase : 1; |
| 5522 | + bool interactive : 1; |
| 5523 | + bool calcVelocity : 1; |
| 5524 | + bool pixelAligned : 1; |
| 5525 | + bool syncDrag : 1; |
| 5526 | + Qt::MouseButtons acceptedButtons = Qt::LeftButton; |
| 5527 | + QElapsedTimer timer; |
| 5528 | + qint64 lastPosTime; |
| 5529 | + qint64 lastPressTime; |
| 5530 | + QPointF lastPos; |
| 5531 | + QPointF pressPos; |
| 5532 | + QVector2D accumulatedWheelPixelDelta; |
| 5533 | + qreal deceleration; |
| 5534 | + qreal wheelDeceleration; |
| 5535 | + qreal maxVelocity; |
| 5536 | + QPointerEvent *delayedPressEvent; |
| 5537 | + QBasicTimer delayedPressTimer; |
| 5538 | + int pressDelay; |
| 5539 | + int fixupDuration; |
| 5540 | + qreal flickBoost; |
| 5541 | + qreal initialWheelFlickDistance; |
| 5542 | + |
| 5543 | + enum FixupMode { Normal, Immediate, ExtentChanged }; |
| 5544 | + FixupMode fixupMode; |
| 5545 | + |
| 5546 | + static void fixupY_callback(void *); |
| 5547 | + static void fixupX_callback(void *); |
| 5548 | + |
| 5549 | + void updateVelocity(); |
| 5550 | + int vTime; |
| 5551 | + StretchTimeLine velocityTimeline; |
| 5552 | + StretchFlickableVisibleArea *visibleArea; |
| 5553 | + StretchFlickable::FlickableDirection flickableDirection; |
| 5554 | + StretchFlickable::BoundsBehavior boundsBehavior; |
| 5555 | + StretchFlickable::BoundsMovement boundsMovement; |
| 5556 | + QQuickTransition *rebound; |
| 5557 | + |
| 5558 | + void viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent, |
| 5559 | + StretchTimeLineCallback::Callback fixupCallback); |
| 5560 | + |
| 5561 | + void handlePressEvent(QPointerEvent *); |
| 5562 | + void handleMoveEvent(QPointerEvent *); |
| 5563 | + void handleReleaseEvent(QPointerEvent *); |
| 5564 | + bool buttonsAccepted(const QSinglePointEvent *event); |
| 5565 | + |
| 5566 | + void maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn, |
| 5567 | + Qt::MouseButtons buttons = Qt::NoButton); |
| 5568 | + void drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, |
| 5569 | + const QVector2D &deltas, bool overThreshold, bool momentum, |
| 5570 | + bool velocitySensitiveOverBounds, const QVector2D &velocity); |
| 5571 | + |
| 5572 | + QVector2D firstPointLocalVelocity(QPointerEvent *event); |
| 5573 | + qint64 computeCurrentTime(QInputEvent *event) const; |
| 5574 | + qreal devicePixelRatio() const; |
| 5575 | + |
| 5576 | + // flickableData property |
| 5577 | + static void data_append(QQmlListProperty<QObject> *, QObject *); |
| 5578 | + static qsizetype data_count(QQmlListProperty<QObject> *); |
| 5579 | + static QObject *data_at(QQmlListProperty<QObject> *, qsizetype); |
| 5580 | + static void data_clear(QQmlListProperty<QObject> *); |
| 5581 | +}; |
| 5582 | + |
| 5583 | +class StretchFlickableVisibleArea : public QObject |
| 5584 | +{ |
| 5585 | + Q_OBJECT |
| 5586 | + |
| 5587 | + Q_PROPERTY(qreal xPosition READ xPosition NOTIFY xPositionChanged FINAL) |
| 5588 | + Q_PROPERTY(qreal yPosition READ yPosition NOTIFY yPositionChanged FINAL) |
| 5589 | + Q_PROPERTY(qreal widthRatio READ widthRatio NOTIFY widthRatioChanged FINAL) |
| 5590 | + Q_PROPERTY(qreal heightRatio READ heightRatio NOTIFY heightRatioChanged FINAL) |
| 5591 | + QML_ANONYMOUS |
| 5592 | + |
| 5593 | +public: |
| 5594 | + StretchFlickableVisibleArea(StretchFlickable *parent=nullptr); |
| 5595 | + |
| 5596 | + qreal xPosition() const; |
| 5597 | + qreal widthRatio() const; |
| 5598 | + qreal yPosition() const; |
| 5599 | + qreal heightRatio() const; |
| 5600 | + |
| 5601 | + void updateVisible(); |
| 5602 | + |
| 5603 | +Q_SIGNALS: |
| 5604 | + void xPositionChanged(qreal xPosition); |
| 5605 | + void yPositionChanged(qreal yPosition); |
| 5606 | + void widthRatioChanged(qreal widthRatio); |
| 5607 | + void heightRatioChanged(qreal heightRatio); |
| 5608 | + |
| 5609 | +private: |
| 5610 | + StretchFlickable *flickable; |
| 5611 | + qreal m_xPosition; |
| 5612 | + qreal m_widthRatio; |
| 5613 | + qreal m_yPosition; |
| 5614 | + qreal m_heightRatio; |
| 5615 | +}; |
| 5616 | + |
| 5617 | +QT_END_NAMESPACE |
| 5618 | + |
| 5619 | +#endif // STRETCHFLICKABLE_P_P_H |
| 5620 | diff --git a/src/stretchflickablebehavior_p.h b/src/stretchflickablebehavior_p.h |
| 5621 | new file mode 100644 |
| 5622 | index 0000000..9d95aa1 |
| 5623 | --- /dev/null |
| 5624 | +++ b/src/stretchflickablebehavior_p.h |
| 5625 | @@ -0,0 +1,72 @@ |
| 5626 | +// Copyright (C) 2016 Research In Motion. |
| 5627 | +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 5628 | + |
| 5629 | +#ifndef STRETCHFLICKABLEBEHAVIOR_H |
| 5630 | +#define STRETCHFLICKABLEBEHAVIOR_H |
| 5631 | + |
| 5632 | +// |
| 5633 | +// W A R N I N G |
| 5634 | +// ------------- |
| 5635 | +// |
| 5636 | +// This file is not part of the Qt API. It exists purely as an |
| 5637 | +// implementation detail. This header file may change from version to |
| 5638 | +// version without notice, or even be removed. |
| 5639 | +// |
| 5640 | +// We mean it. |
| 5641 | +// |
| 5642 | + |
| 5643 | +/* ### Platform specific flickable mechanics are defined either here, or in |
| 5644 | + mkspec files. Long-term (QtQuick 3) Flickable needs to allow such |
| 5645 | + mechanic details to be controlled via QML so that platforms can easily |
| 5646 | + load custom behavior at QML compile time. |
| 5647 | +*/ |
| 5648 | + |
| 5649 | +// The maximum number of pixels a flick can overshoot |
| 5650 | +#ifndef QML_FLICK_OVERSHOOT |
| 5651 | +#define QML_FLICK_OVERSHOOT 150 |
| 5652 | +#endif |
| 5653 | + |
| 5654 | +// The number of samples to use in calculating the velocity of a flick |
| 5655 | +#ifndef QML_FLICK_SAMPLEBUFFER |
| 5656 | +#define QML_FLICK_SAMPLEBUFFER 3 |
| 5657 | +#endif |
| 5658 | + |
| 5659 | +// The number of samples to discard when calculating the flick velocity. |
| 5660 | +// Touch panels often produce inaccurate results as the finger is lifted. |
| 5661 | +#ifndef QML_FLICK_DISCARDSAMPLES |
| 5662 | +#define QML_FLICK_DISCARDSAMPLES 0 |
| 5663 | +#endif |
| 5664 | + |
| 5665 | +// How much faster to decelerate when overshooting |
| 5666 | +#ifndef QML_FLICK_OVERSHOOTFRICTION |
| 5667 | +#define QML_FLICK_OVERSHOOTFRICTION 8 |
| 5668 | +#endif |
| 5669 | + |
| 5670 | +// Multiflick acceleration minimum flick velocity threshold |
| 5671 | +#ifndef QML_FLICK_MULTIFLICK_THRESHOLD |
| 5672 | +#define QML_FLICK_MULTIFLICK_THRESHOLD 1250 |
| 5673 | +#endif |
| 5674 | + |
| 5675 | +// If the time (ms) between the last move and the release exceeds this, then velocity will be zero. |
| 5676 | +#ifndef QML_FLICK_VELOCITY_DECAY_TIME |
| 5677 | +#define QML_FLICK_VELOCITY_DECAY_TIME 50 |
| 5678 | +#endif |
| 5679 | + |
| 5680 | +// Multiflick acceleration minimum contentSize/viewSize ratio |
| 5681 | +#ifndef QML_FLICK_MULTIFLICK_RATIO |
| 5682 | +#define QML_FLICK_MULTIFLICK_RATIO 10 |
| 5683 | +#endif |
| 5684 | + |
| 5685 | +// Multiflick acceleration maximum velocity multiplier |
| 5686 | +#ifndef QML_FLICK_MULTIFLICK_MAXBOOST |
| 5687 | +#define QML_FLICK_MULTIFLICK_MAXBOOST 3.0 |
| 5688 | +#endif |
| 5689 | + |
| 5690 | +// Really slow flicks can be annoying. |
| 5691 | +const qreal _q_MinimumFlickVelocity = 75.0; |
| 5692 | + |
| 5693 | +// If StretchFlickablePrivate::wheelDeceleration (perhaps overridden via QT_QUICK_FLICKABLE_WHEEL_DECELERATION) |
| 5694 | +// is greater than this, we switch to proportional wheel scrolling: no "acceleration" at all. |
| 5695 | +const qreal _q_MaximumWheelDeceleration = 14999; |
| 5696 | + |
| 5697 | +#endif //STRETCHFLICKABLEBEHAVIOR_H |
| 5698 | diff --git a/src/stretchtimeline.cpp b/src/stretchtimeline.cpp |
| 5699 | new file mode 100644 |
| 5700 | index 0000000..81c7622 |
| 5701 | --- /dev/null |
| 5702 | +++ b/src/stretchtimeline.cpp |
| 5703 | @@ -0,0 +1,931 @@ |
| 5704 | +// Copyright (C) 2016 The Qt Company Ltd. |
| 5705 | +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 5706 | + |
| 5707 | +#include "stretchtimeline_p_p.h" |
| 5708 | + |
| 5709 | +#include <QDebug> |
| 5710 | +#include <QMutex> |
| 5711 | +#include <QThread> |
| 5712 | +#include <QWaitCondition> |
| 5713 | +#include <QEvent> |
| 5714 | +#include <QCoreApplication> |
| 5715 | +#include <QEasingCurve> |
| 5716 | +#include <QTime> |
| 5717 | +#include <QtCore/private/qnumeric_p.h> |
| 5718 | +#include <QHash> |
| 5719 | + |
| 5720 | +#include <algorithm> |
| 5721 | + |
| 5722 | +QT_BEGIN_NAMESPACE |
| 5723 | + |
| 5724 | +Q_STATIC_LOGGING_CATEGORY(lcTl, "qt.quick.timeline") |
| 5725 | + |
| 5726 | +struct Update { |
| 5727 | + Update(StretchTimeLineValue *_g, qreal _v) |
| 5728 | + : g(_g), v(_v) {} |
| 5729 | + Update(const StretchTimeLineCallback &_e) |
| 5730 | + : g(nullptr), v(0), e(_e) {} |
| 5731 | + |
| 5732 | + StretchTimeLineValue *g; |
| 5733 | + qreal v; |
| 5734 | + StretchTimeLineCallback e; |
| 5735 | +}; |
| 5736 | + |
| 5737 | +struct StretchTimeLinePrivate |
| 5738 | +{ |
| 5739 | + StretchTimeLinePrivate(StretchTimeLine *); |
| 5740 | + |
| 5741 | + struct Op { |
| 5742 | + enum Type { |
| 5743 | + Pause, // Pauses any value updates |
| 5744 | + Set, // Instantly changes the value to a target value |
| 5745 | + Move, // Moves towards a target value over time |
| 5746 | + MoveBy, // Same as Move, but target value is now an offset from the starting value |
| 5747 | + Accel, // Moves towards a target value over time with a constant acceleration |
| 5748 | + AccelDistance, |
| 5749 | + Execute // Calls back a function |
| 5750 | + }; |
| 5751 | + Op() {} |
| 5752 | + Op(Type t, int l, qreal v, qreal v2, int o, |
| 5753 | + const StretchTimeLineCallback &ev = StretchTimeLineCallback(), const QEasingCurve &es = QEasingCurve()) |
| 5754 | + : type(t), length(l), value(v), value2(v2), order(o), event(ev), |
| 5755 | + easing(es) {} |
| 5756 | + Op(const Op &o) |
| 5757 | + : type(o.type), length(o.length), value(o.value), value2(o.value2), |
| 5758 | + order(o.order), event(o.event), easing(o.easing) {} |
| 5759 | + Op &operator=(const Op &o) { |
| 5760 | + type = o.type; length = o.length; value = o.value; |
| 5761 | + value2 = o.value2; order = o.order; event = o.event; |
| 5762 | + easing = o.easing; |
| 5763 | + return *this; |
| 5764 | + } |
| 5765 | + |
| 5766 | + Type type; |
| 5767 | + int length; |
| 5768 | + qreal value; |
| 5769 | + qreal value2; |
| 5770 | + |
| 5771 | + int order; |
| 5772 | + StretchTimeLineCallback event; |
| 5773 | + QEasingCurve easing; |
| 5774 | + }; |
| 5775 | + struct TimeLine |
| 5776 | + { |
| 5777 | + TimeLine() {} |
| 5778 | + QList<Op> ops; |
| 5779 | + int length = 0; |
| 5780 | + int consumedOpLength = 0; |
| 5781 | + qreal base = 0.; |
| 5782 | + }; |
| 5783 | + |
| 5784 | + int length; |
| 5785 | + int syncPoint; |
| 5786 | + typedef QHash<StretchTimeLineObject *, TimeLine> Ops; |
| 5787 | + Ops ops; |
| 5788 | + StretchTimeLine *q; |
| 5789 | + |
| 5790 | + void add(StretchTimeLineObject &, const Op &); |
| 5791 | + qreal value(const Op &op, int time, qreal base, bool *) const; |
| 5792 | + |
| 5793 | + int advance(int); |
| 5794 | + |
| 5795 | + bool clockRunning; |
| 5796 | + int prevTime; |
| 5797 | + |
| 5798 | + int order; |
| 5799 | + |
| 5800 | + StretchTimeLine::SyncMode syncMode; |
| 5801 | + int syncAdj; |
| 5802 | + QList<QPair<int, Update> > *updateQueue; |
| 5803 | +}; |
| 5804 | + |
| 5805 | +StretchTimeLinePrivate::StretchTimeLinePrivate(StretchTimeLine *parent) |
| 5806 | +: length(0), syncPoint(0), q(parent), clockRunning(false), prevTime(0), order(0), syncMode(StretchTimeLine::LocalSync), syncAdj(0), updateQueue(nullptr) |
| 5807 | +{ |
| 5808 | +} |
| 5809 | + |
| 5810 | +void StretchTimeLinePrivate::add(StretchTimeLineObject &g, const Op &o) |
| 5811 | +{ |
| 5812 | + if (g._t && g._t != q) { |
| 5813 | + qWarning() << "StretchTimeLine: Cannot modify a StretchTimeLineValue owned by" |
| 5814 | + << "another timeline."; |
| 5815 | + return; |
| 5816 | + } |
| 5817 | + g._t = q; |
| 5818 | + |
| 5819 | + Ops::Iterator iter = ops.find(&g); |
| 5820 | + if (iter == ops.end()) { |
| 5821 | + iter = ops.insert(&g, TimeLine()); |
| 5822 | + if (syncPoint > 0) |
| 5823 | + q->pause(g, syncPoint); |
| 5824 | + } |
| 5825 | + if (!iter->ops.isEmpty() && |
| 5826 | + o.type == Op::Pause && |
| 5827 | + iter->ops.constLast().type == Op::Pause) { |
| 5828 | + // If the last operation was a pause, and we're adding another, simply prolong it. |
| 5829 | + iter->ops.last().length += o.length; |
| 5830 | + iter->length += o.length; |
| 5831 | + } else { |
| 5832 | + // Add to the list of operations |
| 5833 | + iter->ops.append(o); |
| 5834 | + iter->length += o.length; |
| 5835 | + } |
| 5836 | + |
| 5837 | + if (iter->length > length) |
| 5838 | + length = iter->length; |
| 5839 | + |
| 5840 | + if (!clockRunning) { |
| 5841 | + q->stop(); |
| 5842 | + prevTime = 0; |
| 5843 | + clockRunning = true; |
| 5844 | + |
| 5845 | + if (syncMode == StretchTimeLine::LocalSync) { |
| 5846 | + syncAdj = -1; |
| 5847 | + } else { |
| 5848 | + syncAdj = 0; |
| 5849 | + } |
| 5850 | + q->start(); |
| 5851 | +/* q->tick(0); |
| 5852 | + if (syncMode == StretchTimeLine::LocalSync) { |
| 5853 | + syncAdj = -1; |
| 5854 | + } else { |
| 5855 | + syncAdj = 0; |
| 5856 | + } |
| 5857 | + */ |
| 5858 | + } |
| 5859 | +} |
| 5860 | + |
| 5861 | +qreal StretchTimeLinePrivate::value(const Op &op, int time, qreal base, bool *changed) const |
| 5862 | +{ |
| 5863 | + Q_ASSERT(time >= 0); |
| 5864 | + Q_ASSERT(time <= op.length); |
| 5865 | + *changed = true; |
| 5866 | + |
| 5867 | + switch(op.type) { |
| 5868 | + case Op::Pause: |
| 5869 | + *changed = false; |
| 5870 | + return base; |
| 5871 | + case Op::Set: |
| 5872 | + return op.value; |
| 5873 | + case Op::Move: |
| 5874 | + if (time == 0) { |
| 5875 | + return base; |
| 5876 | + } else if (time == (op.length)) { |
| 5877 | + return op.value; |
| 5878 | + } else { |
| 5879 | + qreal delta = op.value - base; |
| 5880 | + qreal pTime = (qreal)(time) / (qreal)op.length; |
| 5881 | + if (op.easing.type() == QEasingCurve::Linear) |
| 5882 | + return base + delta * pTime; |
| 5883 | + else |
| 5884 | + return base + delta * op.easing.valueForProgress(pTime); |
| 5885 | + } |
| 5886 | + case Op::MoveBy: |
| 5887 | + if (time == 0) { |
| 5888 | + return base; |
| 5889 | + } else if (time == (op.length)) { |
| 5890 | + return base + op.value; |
| 5891 | + } else { |
| 5892 | + qreal delta = op.value; |
| 5893 | + qreal pTime = (qreal)(time) / (qreal)op.length; |
| 5894 | + if (op.easing.type() == QEasingCurve::Linear) |
| 5895 | + return base + delta * pTime; |
| 5896 | + else |
| 5897 | + return base + delta * op.easing.valueForProgress(pTime); |
| 5898 | + } |
| 5899 | + case Op::Accel: |
| 5900 | + if (time == 0) { |
| 5901 | + return base; |
| 5902 | + } else { |
| 5903 | + qreal t = (qreal)(time) / 1000.0f; |
| 5904 | + qreal delta = op.value * t + 0.5f * op.value2 * t * t; |
| 5905 | + return base + delta; |
| 5906 | + } |
| 5907 | + case Op::AccelDistance: |
| 5908 | + if (time == 0) { |
| 5909 | + return base; |
| 5910 | + } else if (time == (op.length)) { |
| 5911 | + return base + op.value2; |
| 5912 | + } else { |
| 5913 | + qreal t = (qreal)(time) / 1000.0f; |
| 5914 | + qreal accel = -1.0f * 1000.0f * op.value / (qreal)op.length; |
| 5915 | + qreal delta = op.value * t + 0.5f * accel * t * t; |
| 5916 | + return base + delta; |
| 5917 | + |
| 5918 | + } |
| 5919 | + case Op::Execute: |
| 5920 | + op.event.d0(op.event.d1); |
| 5921 | + *changed = false; |
| 5922 | + return -1; |
| 5923 | + } |
| 5924 | + |
| 5925 | + return base; |
| 5926 | +} |
| 5927 | + |
| 5928 | +/*! |
| 5929 | + \internal |
| 5930 | + \class StretchTimeLine |
| 5931 | + \brief The StretchTimeLine class provides a timeline for controlling animations. |
| 5932 | + |
| 5933 | + StretchTimeLine is similar to QTimeLine except: |
| 5934 | + \list |
| 5935 | + \li It updates StretchTimeLineValue instances directly, rather than maintaining a single |
| 5936 | + current value. |
| 5937 | + |
| 5938 | + For example, the following animates a simple value over 200 milliseconds: |
| 5939 | + \code |
| 5940 | + StretchTimeLineValue v(<starting value>); |
| 5941 | + StretchTimeLine tl; |
| 5942 | + tl.move(v, 100., 200); |
| 5943 | + tl.start() |
| 5944 | + \endcode |
| 5945 | + |
| 5946 | + If your program needs to know when values are changed, it can either |
| 5947 | + connect to the StretchTimeLine's updated() signal, or inherit from StretchTimeLineValue |
| 5948 | + and reimplement the StretchTimeLineValue::setValue() method. |
| 5949 | + |
| 5950 | + \li Supports multiple StretchTimeLineValue, arbitrary start and end values and allows |
| 5951 | + animations to be strung together for more complex effects. |
| 5952 | + |
| 5953 | + For example, the following animation moves the x and y coordinates of |
| 5954 | + an object from wherever they are to the position (100, 100) in 50 |
| 5955 | + milliseconds and then further animates them to (100, 200) in 50 |
| 5956 | + milliseconds: |
| 5957 | + |
| 5958 | + \code |
| 5959 | + StretchTimeLineValue x(<starting value>); |
| 5960 | + StretchTimeLineValue y(<starting value>); |
| 5961 | + |
| 5962 | + StretchTimeLine tl; |
| 5963 | + tl.start(); |
| 5964 | + |
| 5965 | + tl.move(x, 100., 50); |
| 5966 | + tl.move(y, 100., 50); |
| 5967 | + tl.move(y, 200., 50); |
| 5968 | + \endcode |
| 5969 | + |
| 5970 | + \li All StretchTimeLine instances share a single, synchronized clock. |
| 5971 | + |
| 5972 | + Actions scheduled within the same event loop tick are scheduled |
| 5973 | + synchronously against each other, regardless of the wall time between the |
| 5974 | + scheduling. Synchronized scheduling applies both to within the same |
| 5975 | + StretchTimeLine and across separate StretchTimeLine's within the same process. |
| 5976 | + |
| 5977 | + \endlist |
| 5978 | + |
| 5979 | + Currently easing functions are not supported. |
| 5980 | +*/ |
| 5981 | + |
| 5982 | + |
| 5983 | +/*! |
| 5984 | + Construct a new StretchTimeLine with the specified \a parent. |
| 5985 | +*/ |
| 5986 | +StretchTimeLine::StretchTimeLine(QObject *parent) |
| 5987 | + : QObject(parent) |
| 5988 | +{ |
| 5989 | + d = new StretchTimeLinePrivate(this); |
| 5990 | +} |
| 5991 | + |
| 5992 | +/*! |
| 5993 | + Destroys the time line. Any inprogress animations are canceled, but not |
| 5994 | + completed. |
| 5995 | +*/ |
| 5996 | +StretchTimeLine::~StretchTimeLine() |
| 5997 | +{ |
| 5998 | + for (StretchTimeLinePrivate::Ops::Iterator iter = d->ops.begin(); |
| 5999 | + iter != d->ops.end(); |
| 6000 | + ++iter) |
| 6001 | + iter.key()->_t = nullptr; |
| 6002 | + |
| 6003 | + delete d; d = nullptr; |
| 6004 | +} |
| 6005 | + |
| 6006 | +/*! |
| 6007 | + \enum StretchTimeLine::SyncMode |
| 6008 | + */ |
| 6009 | + |
| 6010 | +/*! |
| 6011 | + Return the timeline's synchronization mode. |
| 6012 | + */ |
| 6013 | +StretchTimeLine::SyncMode StretchTimeLine::syncMode() const |
| 6014 | +{ |
| 6015 | + return d->syncMode; |
| 6016 | +} |
| 6017 | + |
| 6018 | +/*! |
| 6019 | + Set the timeline's synchronization mode to \a syncMode. |
| 6020 | + */ |
| 6021 | +void StretchTimeLine::setSyncMode(SyncMode syncMode) |
| 6022 | +{ |
| 6023 | + d->syncMode = syncMode; |
| 6024 | +} |
| 6025 | + |
| 6026 | +/*! |
| 6027 | + Pause \a obj for \a time milliseconds. |
| 6028 | +*/ |
| 6029 | +void StretchTimeLine::pause(StretchTimeLineObject &obj, int time) |
| 6030 | +{ |
| 6031 | + if (time <= 0) return; |
| 6032 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Pause, time, 0., 0., d->order++); |
| 6033 | + d->add(obj, op); |
| 6034 | +} |
| 6035 | + |
| 6036 | +/*! |
| 6037 | + Execute the \a event. |
| 6038 | + */ |
| 6039 | +void StretchTimeLine::callback(const StretchTimeLineCallback &callback) |
| 6040 | +{ |
| 6041 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Execute, 0, 0, 0., d->order++, callback); |
| 6042 | + d->add(*callback.callbackObject(), op); |
| 6043 | +} |
| 6044 | + |
| 6045 | +/*! |
| 6046 | + Set the \a value of \a timeLineValue. |
| 6047 | +*/ |
| 6048 | +void StretchTimeLine::set(StretchTimeLineValue &timeLineValue, qreal value) |
| 6049 | +{ |
| 6050 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Set, 0, value, 0., d->order++); |
| 6051 | + d->add(timeLineValue, op); |
| 6052 | +} |
| 6053 | + |
| 6054 | +/*! |
| 6055 | + Decelerate \a timeLineValue from the starting \a velocity to zero at the |
| 6056 | + given \a acceleration rate. Although the \a acceleration is technically |
| 6057 | + a deceleration, it should always be positive. The StretchTimeLine will ensure |
| 6058 | + that the deceleration is in the opposite direction to the initial velocity. |
| 6059 | +*/ |
| 6060 | +int StretchTimeLine::accel(StretchTimeLineValue &timeLineValue, qreal velocity, qreal acceleration) |
| 6061 | +{ |
| 6062 | + if (qFuzzyIsNull(acceleration) || qt_is_nan(acceleration)) |
| 6063 | + return -1; |
| 6064 | + |
| 6065 | + if ((velocity > 0.0f) == (acceleration > 0.0f)) |
| 6066 | + acceleration = acceleration * -1.0f; |
| 6067 | + |
| 6068 | + int time = static_cast<int>(-1000 * velocity / acceleration); |
| 6069 | + if (time <= 0) return -1; |
| 6070 | + |
| 6071 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Accel, time, velocity, acceleration, d->order++); |
| 6072 | + d->add(timeLineValue, op); |
| 6073 | + |
| 6074 | + return time; |
| 6075 | +} |
| 6076 | + |
| 6077 | +/*! |
| 6078 | + \overload |
| 6079 | + |
| 6080 | + Decelerate \a timeLineValue from the starting \a velocity to zero at the |
| 6081 | + given \a acceleration rate over a maximum distance of maxDistance. |
| 6082 | + |
| 6083 | + If necessary, StretchTimeLine will reduce the acceleration to ensure that the |
| 6084 | + entire operation does not require a move of more than \a maxDistance. |
| 6085 | + \a maxDistance should always be positive. |
| 6086 | +*/ |
| 6087 | +int StretchTimeLine::accel(StretchTimeLineValue &timeLineValue, qreal velocity, qreal acceleration, qreal maxDistance) |
| 6088 | +{ |
| 6089 | + if (qFuzzyIsNull(maxDistance) || qt_is_nan(maxDistance) || qFuzzyIsNull(acceleration) || qt_is_nan(acceleration)) |
| 6090 | + return -1; |
| 6091 | + |
| 6092 | + Q_ASSERT(acceleration > 0.0f && maxDistance > 0.0f); |
| 6093 | + |
| 6094 | + qreal maxAccel = (velocity * velocity) / (2.0f * maxDistance); |
| 6095 | + if (maxAccel > acceleration) |
| 6096 | + acceleration = maxAccel; |
| 6097 | + |
| 6098 | + if ((velocity > 0.0f) == (acceleration > 0.0f)) |
| 6099 | + acceleration = acceleration * -1.0f; |
| 6100 | + |
| 6101 | + int time = static_cast<int>(-1000 * velocity / acceleration); |
| 6102 | + if (time <= 0) return -1; |
| 6103 | + |
| 6104 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Accel, time, velocity, acceleration, d->order++); |
| 6105 | + d->add(timeLineValue, op); |
| 6106 | + |
| 6107 | + return time; |
| 6108 | +} |
| 6109 | + |
| 6110 | +/*! |
| 6111 | + Decelerate \a timeLineValue from the starting \a velocity to zero over the given |
| 6112 | + \a distance. This is like accel(), but the StretchTimeLine calculates the exact |
| 6113 | + deceleration to use. |
| 6114 | + |
| 6115 | + \a distance should be positive. |
| 6116 | +*/ |
| 6117 | +int StretchTimeLine::accelDistance(StretchTimeLineValue &timeLineValue, qreal velocity, qreal distance) |
| 6118 | +{ |
| 6119 | + if (qFuzzyIsNull(distance) || qt_is_nan(distance) || qFuzzyIsNull(velocity) || qt_is_nan(velocity)) |
| 6120 | + return -1; |
| 6121 | + |
| 6122 | + Q_ASSERT((distance >= 0.0f) == (velocity >= 0.0f)); |
| 6123 | + |
| 6124 | + int time = static_cast<int>(1000 * (2.0f * distance) / velocity); |
| 6125 | + if (time <= 0) return -1; |
| 6126 | + |
| 6127 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::AccelDistance, time, velocity, distance, d->order++); |
| 6128 | + d->add(timeLineValue, op); |
| 6129 | + |
| 6130 | + return time; |
| 6131 | +} |
| 6132 | + |
| 6133 | +/*! |
| 6134 | + Linearly change the \a timeLineValue from its current value to the given |
| 6135 | + \a destination value over \a time milliseconds. |
| 6136 | +*/ |
| 6137 | +void StretchTimeLine::move(StretchTimeLineValue &timeLineValue, qreal destination, int time) |
| 6138 | +{ |
| 6139 | + if (time <= 0) return; |
| 6140 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Move, time, destination, 0.0f, d->order++); |
| 6141 | + d->add(timeLineValue, op); |
| 6142 | +} |
| 6143 | + |
| 6144 | +/*! |
| 6145 | + Change the \a timeLineValue from its current value to the given \a destination |
| 6146 | + value over \a time milliseconds using the \a easing curve. |
| 6147 | + */ |
| 6148 | +void StretchTimeLine::move(StretchTimeLineValue &timeLineValue, qreal destination, const QEasingCurve &easing, int time) |
| 6149 | +{ |
| 6150 | + if (time <= 0) return; |
| 6151 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::Move, time, destination, 0.0f, d->order++, StretchTimeLineCallback(), easing); |
| 6152 | + d->add(timeLineValue, op); |
| 6153 | +} |
| 6154 | + |
| 6155 | +/*! |
| 6156 | + Linearly change the \a timeLineValue from its current value by the \a change amount |
| 6157 | + over \a time milliseconds. |
| 6158 | +*/ |
| 6159 | +void StretchTimeLine::moveBy(StretchTimeLineValue &timeLineValue, qreal change, int time) |
| 6160 | +{ |
| 6161 | + if (time <= 0) return; |
| 6162 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::MoveBy, time, change, 0.0f, d->order++); |
| 6163 | + d->add(timeLineValue, op); |
| 6164 | +} |
| 6165 | + |
| 6166 | +/*! |
| 6167 | + Change the \a timeLineValue from its current value by the \a change amount over |
| 6168 | + \a time milliseconds using the \a easing curve. |
| 6169 | + */ |
| 6170 | +void StretchTimeLine::moveBy(StretchTimeLineValue &timeLineValue, qreal change, const QEasingCurve &easing, int time) |
| 6171 | +{ |
| 6172 | + if (time <= 0) return; |
| 6173 | + StretchTimeLinePrivate::Op op(StretchTimeLinePrivate::Op::MoveBy, time, change, 0.0f, d->order++, StretchTimeLineCallback(), easing); |
| 6174 | + d->add(timeLineValue, op); |
| 6175 | +} |
| 6176 | + |
| 6177 | +/*! |
| 6178 | + Cancel (but don't complete) all scheduled actions for \a timeLineValue. |
| 6179 | +*/ |
| 6180 | +void StretchTimeLine::reset(StretchTimeLineValue &timeLineValue) |
| 6181 | +{ |
| 6182 | + if (!timeLineValue._t) |
| 6183 | + return; |
| 6184 | + if (timeLineValue._t != this) { |
| 6185 | + qWarning() << "StretchTimeLine: Cannot reset a StretchTimeLineValue owned by another timeline."; |
| 6186 | + return; |
| 6187 | + } |
| 6188 | + qCDebug(lcTl) << static_cast<QObject*>(this) << timeLineValue.value(); |
| 6189 | + remove(&timeLineValue); |
| 6190 | + timeLineValue._t = nullptr; |
| 6191 | +} |
| 6192 | + |
| 6193 | +int StretchTimeLine::duration() const |
| 6194 | +{ |
| 6195 | + return -1; |
| 6196 | +} |
| 6197 | + |
| 6198 | +/*! |
| 6199 | + Synchronize the end point of \a timeLineValue to the endpoint of \a syncTo |
| 6200 | + within this timeline. |
| 6201 | + |
| 6202 | + Following operations on \a timeLineValue in this timeline will be scheduled after |
| 6203 | + all the currently scheduled actions on \a syncTo are complete. In |
| 6204 | + pseudo-code this is equivalent to: |
| 6205 | + \code |
| 6206 | + StretchTimeLine::pause(timeLineValue, min(0, length_of(syncTo) - length_of(timeLineValue))) |
| 6207 | + \endcode |
| 6208 | +*/ |
| 6209 | +void StretchTimeLine::sync(StretchTimeLineValue &timeLineValue, StretchTimeLineValue &syncTo) |
| 6210 | +{ |
| 6211 | + StretchTimeLinePrivate::Ops::Iterator iter = d->ops.find(&syncTo); |
| 6212 | + if (iter == d->ops.end()) |
| 6213 | + return; |
| 6214 | + int length = iter->length; |
| 6215 | + |
| 6216 | + iter = d->ops.find(&timeLineValue); |
| 6217 | + if (iter == d->ops.end()) { |
| 6218 | + pause(timeLineValue, length); |
| 6219 | + } else { |
| 6220 | + int glength = iter->length; |
| 6221 | + pause(timeLineValue, length - glength); |
| 6222 | + } |
| 6223 | +} |
| 6224 | + |
| 6225 | +/*! |
| 6226 | + Synchronize the end point of \a timeLineValue to the endpoint of the longest |
| 6227 | + action cursrently scheduled in the timeline. |
| 6228 | + |
| 6229 | + In pseudo-code, this is equivalent to: |
| 6230 | + \code |
| 6231 | + StretchTimeLine::pause(timeLineValue, length_of(timeline) - length_of(timeLineValue)) |
| 6232 | + \endcode |
| 6233 | +*/ |
| 6234 | +void StretchTimeLine::sync(StretchTimeLineValue &timeLineValue) |
| 6235 | +{ |
| 6236 | + StretchTimeLinePrivate::Ops::Iterator iter = d->ops.find(&timeLineValue); |
| 6237 | + if (iter == d->ops.end()) { |
| 6238 | + pause(timeLineValue, d->length); |
| 6239 | + } else { |
| 6240 | + pause(timeLineValue, d->length - iter->length); |
| 6241 | + } |
| 6242 | +} |
| 6243 | + |
| 6244 | +/* |
| 6245 | + Synchronize all currently and future scheduled values in this timeline to |
| 6246 | + the longest action currently scheduled. |
| 6247 | + |
| 6248 | + For example: |
| 6249 | + \code |
| 6250 | + value1->setValue(0.); |
| 6251 | + value2->setValue(0.); |
| 6252 | + value3->setValue(0.); |
| 6253 | + StretchTimeLine tl; |
| 6254 | + ... |
| 6255 | + tl.move(value1, 10, 200); |
| 6256 | + tl.move(value2, 10, 100); |
| 6257 | + tl.sync(); |
| 6258 | + tl.move(value2, 20, 100); |
| 6259 | + tl.move(value3, 20, 100); |
| 6260 | + \endcode |
| 6261 | + |
| 6262 | + will result in: |
| 6263 | + |
| 6264 | + \table |
| 6265 | + \header \li \li 0ms \li 50ms \li 100ms \li 150ms \li 200ms \li 250ms \li 300ms |
| 6266 | + \row \li value1 \li 0 \li 2.5 \li 5.0 \li 7.5 \li 10 \li 10 \li 10 |
| 6267 | + \row \li value2 \li 0 \li 5.0 \li 10.0 \li 10.0 \li 10.0 \li 15.0 \li 20.0 |
| 6268 | + \row \li value2 \li 0 \li 0 \li 0 \li 0 \li 0 \li 10.0 \li 20.0 |
| 6269 | + \endtable |
| 6270 | +*/ |
| 6271 | + |
| 6272 | +/*void StretchTimeLine::sync() |
| 6273 | +{ |
| 6274 | + for (StretchTimeLinePrivate::Ops::Iterator iter = d->ops.begin(); |
| 6275 | + iter != d->ops.end(); |
| 6276 | + ++iter) |
| 6277 | + pause(*iter.key(), d->length - iter->length); |
| 6278 | + d->syncPoint = d->length; |
| 6279 | +}*/ |
| 6280 | + |
| 6281 | +/*! |
| 6282 | + \internal |
| 6283 | + |
| 6284 | + Temporary hack. |
| 6285 | + */ |
| 6286 | +void StretchTimeLine::setSyncPoint(int sp) |
| 6287 | +{ |
| 6288 | + d->syncPoint = sp; |
| 6289 | +} |
| 6290 | + |
| 6291 | +/*! |
| 6292 | + \internal |
| 6293 | + |
| 6294 | + Temporary hack. |
| 6295 | + */ |
| 6296 | +int StretchTimeLine::syncPoint() const |
| 6297 | +{ |
| 6298 | + return d->syncPoint; |
| 6299 | +} |
| 6300 | + |
| 6301 | +/*! |
| 6302 | + Returns true if the timeline is active. An active timeline is one where |
| 6303 | + StretchTimeLineValue actions are still pending. |
| 6304 | +*/ |
| 6305 | +bool StretchTimeLine::isActive() const |
| 6306 | +{ |
| 6307 | + return !d->ops.isEmpty(); |
| 6308 | +} |
| 6309 | + |
| 6310 | +/*! |
| 6311 | + Completes the timeline. All queued actions are played to completion, and then discarded. For example, |
| 6312 | + \code |
| 6313 | + StretchTimeLineValue v(0.); |
| 6314 | + StretchTimeLine tl; |
| 6315 | + tl.move(v, 100., 1000.); |
| 6316 | + // 500 ms passes |
| 6317 | + // v.value() == 50. |
| 6318 | + tl.complete(); |
| 6319 | + // v.value() == 100. |
| 6320 | + \endcode |
| 6321 | +*/ |
| 6322 | +void StretchTimeLine::complete() |
| 6323 | +{ |
| 6324 | + d->advance(d->length); |
| 6325 | +} |
| 6326 | + |
| 6327 | +/*! |
| 6328 | + Resets the timeline. All queued actions are discarded and StretchTimeLineValue's retain their current value. For example, |
| 6329 | + \code |
| 6330 | + StretchTimeLineValue v(0.); |
| 6331 | + StretchTimeLine tl; |
| 6332 | + tl.move(v, 100., 1000.); |
| 6333 | + // 500 ms passes |
| 6334 | + // v.value() == 50. |
| 6335 | + tl.clear(); |
| 6336 | + // v.value() == 50. |
| 6337 | + \endcode |
| 6338 | +*/ |
| 6339 | +void StretchTimeLine::clear() |
| 6340 | +{ |
| 6341 | + for (StretchTimeLinePrivate::Ops::const_iterator iter = d->ops.cbegin(), cend = d->ops.cend(); iter != cend; ++iter) |
| 6342 | + iter.key()->_t = nullptr; |
| 6343 | + d->ops.clear(); |
| 6344 | + d->length = 0; |
| 6345 | + d->syncPoint = 0; |
| 6346 | + //XXX need stop here? |
| 6347 | +} |
| 6348 | + |
| 6349 | +int StretchTimeLine::time() const |
| 6350 | +{ |
| 6351 | + return d->prevTime; |
| 6352 | +} |
| 6353 | + |
| 6354 | +/*! |
| 6355 | + \fn void StretchTimeLine::updated() |
| 6356 | + |
| 6357 | + Emitted each time the timeline modifies StretchTimeLineValues. Even if multiple |
| 6358 | + StretchTimeLineValues are changed, this signal is only emitted once for each clock tick. |
| 6359 | +*/ |
| 6360 | + |
| 6361 | +void StretchTimeLine::updateCurrentTime(int v) |
| 6362 | +{ |
| 6363 | + if (d->syncAdj == -1) |
| 6364 | + d->syncAdj = v; |
| 6365 | + v -= d->syncAdj; |
| 6366 | + |
| 6367 | + int timeChanged = v - d->prevTime; |
| 6368 | +#if 0 |
| 6369 | + if (!timeChanged) |
| 6370 | + return; |
| 6371 | +#endif |
| 6372 | + d->prevTime = v; |
| 6373 | + d->advance(timeChanged); |
| 6374 | + emit updated(); |
| 6375 | + |
| 6376 | + // Do we need to stop the clock? |
| 6377 | + if (d->ops.isEmpty()) { |
| 6378 | + stop(); |
| 6379 | + d->prevTime = 0; |
| 6380 | + d->clockRunning = false; |
| 6381 | + emit completed(); |
| 6382 | + } /*else if (pauseTime > 0) { |
| 6383 | + GfxClock::cancelClock(); |
| 6384 | + d->prevTime = 0; |
| 6385 | + GfxClock::pauseFor(pauseTime); |
| 6386 | + d->syncAdj = 0; |
| 6387 | + d->clockRunning = false; |
| 6388 | + }*/ else if (/*!GfxClock::isActive()*/ state() != Running) { |
| 6389 | + stop(); |
| 6390 | + d->prevTime = 0; |
| 6391 | + d->clockRunning = true; |
| 6392 | + d->syncAdj = 0; |
| 6393 | + start(); |
| 6394 | + } |
| 6395 | +} |
| 6396 | + |
| 6397 | +void StretchTimeLine::debugAnimation(QDebug d) const |
| 6398 | +{ |
| 6399 | + d << "QuickTimeLine(" << Qt::hex << (const void *) this << Qt::dec << ")"; |
| 6400 | +} |
| 6401 | + |
| 6402 | +bool operator<(const QPair<int, Update> &lhs, |
| 6403 | + const QPair<int, Update> &rhs) |
| 6404 | +{ |
| 6405 | + return lhs.first < rhs.first; |
| 6406 | +} |
| 6407 | + |
| 6408 | +int StretchTimeLinePrivate::advance(int t) |
| 6409 | +{ |
| 6410 | + int pauseTime = -1; |
| 6411 | + |
| 6412 | + // XXX - surely there is a more efficient way? |
| 6413 | + do { |
| 6414 | + pauseTime = -1; |
| 6415 | + // Minimal advance time |
| 6416 | + int advanceTime = t; |
| 6417 | + for (Ops::const_iterator iter = ops.constBegin(), cend = ops.constEnd(); iter != cend; ++iter) { |
| 6418 | + const TimeLine &tl = *iter; |
| 6419 | + const Op &op = tl.ops.first(); |
| 6420 | + int length = op.length - tl.consumedOpLength; |
| 6421 | + |
| 6422 | + if (length < advanceTime) { |
| 6423 | + advanceTime = length; |
| 6424 | + if (advanceTime == 0) |
| 6425 | + break; |
| 6426 | + } |
| 6427 | + } |
| 6428 | + t -= advanceTime; |
| 6429 | + |
| 6430 | + // Process until then. A zero length advance time will only process |
| 6431 | + // sets. |
| 6432 | + QList<QPair<int, Update> > updates; |
| 6433 | + |
| 6434 | + for (Ops::Iterator iter = ops.begin(); iter != ops.end(); ) { |
| 6435 | + StretchTimeLineValue *v = static_cast<StretchTimeLineValue *>(iter.key()); |
| 6436 | + TimeLine &tl = *iter; |
| 6437 | + Q_ASSERT(!tl.ops.isEmpty()); |
| 6438 | + |
| 6439 | + do { |
| 6440 | + Op &op = tl.ops.first(); |
| 6441 | + if (advanceTime == 0 && op.length != 0) |
| 6442 | + continue; |
| 6443 | + |
| 6444 | + if (tl.consumedOpLength == 0 && |
| 6445 | + op.type != Op::Pause && |
| 6446 | + op.type != Op::Execute) |
| 6447 | + tl.base = v->value(); |
| 6448 | + |
| 6449 | + if ((tl.consumedOpLength + advanceTime) == op.length) { |
| 6450 | + // Finishing operation, the timeline value will be the operation's target value. |
| 6451 | + if (op.type == Op::Execute) { |
| 6452 | + updates << qMakePair(op.order, Update(op.event)); |
| 6453 | + } else { |
| 6454 | + bool changed = false; |
| 6455 | + qreal val = value(op, op.length, tl.base, &changed); |
| 6456 | + if (changed) |
| 6457 | + updates << qMakePair(op.order, Update(v, val)); |
| 6458 | + } |
| 6459 | + tl.length -= qMin(advanceTime, tl.length); |
| 6460 | + tl.consumedOpLength = 0; |
| 6461 | + tl.ops.removeFirst(); |
| 6462 | + } else { |
| 6463 | + // Partially finished operation, the timeline value will be between the base |
| 6464 | + // value and the target value, depending on progress and type of operation. |
| 6465 | + tl.consumedOpLength += advanceTime; |
| 6466 | + bool changed = false; |
| 6467 | + qreal val = value(op, tl.consumedOpLength, tl.base, &changed); |
| 6468 | + if (changed) |
| 6469 | + updates << qMakePair(op.order, Update(v, val)); |
| 6470 | + tl.length -= qMin(advanceTime, tl.length); |
| 6471 | + break; |
| 6472 | + } |
| 6473 | + |
| 6474 | + } while(!tl.ops.isEmpty() && advanceTime == 0 && tl.ops.first().length == 0); |
| 6475 | + |
| 6476 | + |
| 6477 | + if (tl.ops.isEmpty()) { |
| 6478 | + iter = ops.erase(iter); |
| 6479 | + v->_t = nullptr; |
| 6480 | + } else { |
| 6481 | + if (tl.ops.first().type == Op::Pause && pauseTime != 0) { |
| 6482 | + int opPauseTime = tl.ops.first().length - tl.consumedOpLength; |
| 6483 | + if (pauseTime == -1 || opPauseTime < pauseTime) |
| 6484 | + pauseTime = opPauseTime; |
| 6485 | + } else { |
| 6486 | + pauseTime = 0; |
| 6487 | + } |
| 6488 | + ++iter; |
| 6489 | + } |
| 6490 | + } |
| 6491 | + |
| 6492 | + length -= qMin(length, advanceTime); |
| 6493 | + syncPoint -= advanceTime; |
| 6494 | + |
| 6495 | + std::sort(updates.begin(), updates.end()); |
| 6496 | + updateQueue = &updates; |
| 6497 | + for (int ii = 0; ii < updates.size(); ++ii) { |
| 6498 | + const Update &v = updates.at(ii).second; |
| 6499 | + if (v.g) { |
| 6500 | + v.g->setValue(v.v); |
| 6501 | + } else { |
| 6502 | + v.e.d0(v.e.d1); |
| 6503 | + } |
| 6504 | + } |
| 6505 | + updateQueue = nullptr; |
| 6506 | + } while(t); |
| 6507 | + |
| 6508 | + return pauseTime; |
| 6509 | +} |
| 6510 | + |
| 6511 | +void StretchTimeLine::remove(StretchTimeLineObject *v) |
| 6512 | +{ |
| 6513 | + StretchTimeLinePrivate::Ops::Iterator iter = d->ops.find(v); |
| 6514 | + Q_ASSERT(iter != d->ops.end()); |
| 6515 | + |
| 6516 | + int len = iter->length; |
| 6517 | + d->ops.erase(iter); |
| 6518 | + if (len == d->length) { |
| 6519 | + // We need to recalculate the length |
| 6520 | + d->length = 0; |
| 6521 | + for (StretchTimeLinePrivate::Ops::Iterator iter = d->ops.begin(); |
| 6522 | + iter != d->ops.end(); |
| 6523 | + ++iter) { |
| 6524 | + |
| 6525 | + if (iter->length > d->length) |
| 6526 | + d->length = iter->length; |
| 6527 | + |
| 6528 | + } |
| 6529 | + } |
| 6530 | + if (d->ops.isEmpty()) { |
| 6531 | + stop(); |
| 6532 | + d->clockRunning = false; |
| 6533 | + } else if (state() != Running) { // was !GfxClock::isActive() |
| 6534 | + stop(); |
| 6535 | + d->prevTime = 0; |
| 6536 | + d->clockRunning = true; |
| 6537 | + |
| 6538 | + if (d->syncMode == StretchTimeLine::LocalSync) { |
| 6539 | + d->syncAdj = -1; |
| 6540 | + } else { |
| 6541 | + d->syncAdj = 0; |
| 6542 | + } |
| 6543 | + start(); |
| 6544 | + } |
| 6545 | + |
| 6546 | + if (d->updateQueue) { |
| 6547 | + for (int ii = 0; ii < d->updateQueue->size(); ++ii) { |
| 6548 | + if (d->updateQueue->at(ii).second.g == v || |
| 6549 | + d->updateQueue->at(ii).second.e.callbackObject() == v) { |
| 6550 | + d->updateQueue->removeAt(ii); |
| 6551 | + --ii; |
| 6552 | + } |
| 6553 | + } |
| 6554 | + } |
| 6555 | + |
| 6556 | + |
| 6557 | +} |
| 6558 | + |
| 6559 | +/*! |
| 6560 | + \internal |
| 6561 | + \class StretchTimeLineValue |
| 6562 | + \brief The StretchTimeLineValue class provides a value that can be modified by StretchTimeLine. |
| 6563 | +*/ |
| 6564 | + |
| 6565 | +/*! |
| 6566 | + \fn StretchTimeLineValue::StretchTimeLineValue(qreal value = 0) |
| 6567 | + |
| 6568 | + Construct a new StretchTimeLineValue with an initial \a value. |
| 6569 | +*/ |
| 6570 | + |
| 6571 | +/*! |
| 6572 | + \fn qreal StretchTimeLineValue::value() const |
| 6573 | + |
| 6574 | + Return the current value. |
| 6575 | +*/ |
| 6576 | + |
| 6577 | +/*! |
| 6578 | + \fn void StretchTimeLineValue::setValue(qreal value) |
| 6579 | + |
| 6580 | + Set the current \a value. |
| 6581 | +*/ |
| 6582 | + |
| 6583 | +/*! |
| 6584 | + \fn StretchTimeLine *StretchTimeLineValue::timeLine() const |
| 6585 | + |
| 6586 | + If a StretchTimeLine is operating on this value, return a pointer to it, |
| 6587 | + otherwise return null. |
| 6588 | +*/ |
| 6589 | + |
| 6590 | + |
| 6591 | +StretchTimeLineObject::StretchTimeLineObject() |
| 6592 | +: _t(nullptr) |
| 6593 | +{ |
| 6594 | +} |
| 6595 | + |
| 6596 | +StretchTimeLineObject::~StretchTimeLineObject() |
| 6597 | +{ |
| 6598 | + if (_t) { |
| 6599 | + _t->remove(this); |
| 6600 | + _t = nullptr; |
| 6601 | + } |
| 6602 | +} |
| 6603 | + |
| 6604 | +StretchTimeLineCallback::StretchTimeLineCallback() |
| 6605 | +: d0(nullptr), d1(nullptr), d2(nullptr) |
| 6606 | +{ |
| 6607 | +} |
| 6608 | + |
| 6609 | +StretchTimeLineCallback::StretchTimeLineCallback(StretchTimeLineObject *b, Callback f, void *d) |
| 6610 | +: d0(f), d1(d), d2(b) |
| 6611 | +{ |
| 6612 | +} |
| 6613 | + |
| 6614 | +StretchTimeLineCallback::StretchTimeLineCallback(const StretchTimeLineCallback &o) |
| 6615 | +: d0(o.d0), d1(o.d1), d2(o.d2) |
| 6616 | +{ |
| 6617 | +} |
| 6618 | + |
| 6619 | +StretchTimeLineCallback &StretchTimeLineCallback::operator=(const StretchTimeLineCallback &o) |
| 6620 | +{ |
| 6621 | + d0 = o.d0; |
| 6622 | + d1 = o.d1; |
| 6623 | + d2 = o.d2; |
| 6624 | + return *this; |
| 6625 | +} |
| 6626 | + |
| 6627 | +StretchTimeLineObject *StretchTimeLineCallback::callbackObject() const |
| 6628 | +{ |
| 6629 | + return d2; |
| 6630 | +} |
| 6631 | + |
| 6632 | +QT_END_NAMESPACE |
| 6633 | + |
| 6634 | +#include "moc_stretchtimeline_p_p.cpp" |
| 6635 | diff --git a/src/stretchtimeline_p_p.h b/src/stretchtimeline_p_p.h |
| 6636 | new file mode 100644 |
| 6637 | index 0000000..9360c7f |
| 6638 | --- /dev/null |
| 6639 | +++ b/src/stretchtimeline_p_p.h |
| 6640 | @@ -0,0 +1,156 @@ |
| 6641 | +// Copyright (C) 2016 The Qt Company Ltd. |
| 6642 | +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 6643 | + |
| 6644 | +#ifndef STRETCHTIMELINE_H |
| 6645 | +#define STRETCHTIMELINE_H |
| 6646 | + |
| 6647 | +// |
| 6648 | +// W A R N I N G |
| 6649 | +// ------------- |
| 6650 | +// |
| 6651 | +// This file is not part of the Qt API. It exists purely as an |
| 6652 | +// implementation detail. This header file may change from version to |
| 6653 | +// version without notice, or even be removed. |
| 6654 | +// |
| 6655 | +// We mean it. |
| 6656 | +// |
| 6657 | + |
| 6658 | +#include <QtCore/QObject> |
| 6659 | +#include <private/qtquickglobal_p.h> |
| 6660 | +#include "private/qabstractanimationjob_p.h" |
| 6661 | + |
| 6662 | +QT_BEGIN_NAMESPACE |
| 6663 | + |
| 6664 | +class QEasingCurve; |
| 6665 | +class StretchTimeLineValue; |
| 6666 | +class StretchTimeLineCallback; |
| 6667 | +struct StretchTimeLinePrivate; |
| 6668 | +class StretchTimeLineObject; |
| 6669 | +class StretchTimeLine : public QObject, QAbstractAnimationJob |
| 6670 | +{ |
| 6671 | +Q_OBJECT |
| 6672 | +public: |
| 6673 | + StretchTimeLine(QObject *parent = nullptr); |
| 6674 | + ~StretchTimeLine(); |
| 6675 | + |
| 6676 | + enum SyncMode { LocalSync, GlobalSync }; |
| 6677 | + SyncMode syncMode() const; |
| 6678 | + void setSyncMode(SyncMode); |
| 6679 | + |
| 6680 | + void pause(StretchTimeLineObject &, int); |
| 6681 | + void callback(const StretchTimeLineCallback &); |
| 6682 | + void set(StretchTimeLineValue &, qreal); |
| 6683 | + |
| 6684 | + int accel(StretchTimeLineValue &, qreal velocity, qreal accel); |
| 6685 | + int accel(StretchTimeLineValue &, qreal velocity, qreal accel, qreal maxDistance); |
| 6686 | + int accelDistance(StretchTimeLineValue &, qreal velocity, qreal distance); |
| 6687 | + |
| 6688 | + void move(StretchTimeLineValue &, qreal destination, int time = 500); |
| 6689 | + void move(StretchTimeLineValue &, qreal destination, const QEasingCurve &, int time = 500); |
| 6690 | + void moveBy(StretchTimeLineValue &, qreal change, int time = 500); |
| 6691 | + void moveBy(StretchTimeLineValue &, qreal change, const QEasingCurve &, int time = 500); |
| 6692 | + |
| 6693 | + void sync(); |
| 6694 | + void setSyncPoint(int); |
| 6695 | + int syncPoint() const; |
| 6696 | + |
| 6697 | + void sync(StretchTimeLineValue &); |
| 6698 | + void sync(StretchTimeLineValue &, StretchTimeLineValue &); |
| 6699 | + |
| 6700 | + void reset(StretchTimeLineValue &); |
| 6701 | + |
| 6702 | + void complete(); |
| 6703 | + void clear(); |
| 6704 | + bool isActive() const; |
| 6705 | + |
| 6706 | + int time() const; |
| 6707 | + |
| 6708 | + int duration() const override; |
| 6709 | +Q_SIGNALS: |
| 6710 | + void updated(); |
| 6711 | + void completed(); |
| 6712 | + |
| 6713 | +protected: |
| 6714 | + void updateCurrentTime(int) override; |
| 6715 | + void debugAnimation(QDebug d) const override; |
| 6716 | + |
| 6717 | +private: |
| 6718 | + void remove(StretchTimeLineObject *); |
| 6719 | + friend class StretchTimeLineObject; |
| 6720 | + friend struct StretchTimeLinePrivate; |
| 6721 | + StretchTimeLinePrivate *d; |
| 6722 | +}; |
| 6723 | + |
| 6724 | +class StretchTimeLineObject |
| 6725 | +{ |
| 6726 | +public: |
| 6727 | + StretchTimeLineObject(); |
| 6728 | + virtual ~StretchTimeLineObject(); |
| 6729 | + |
| 6730 | +protected: |
| 6731 | + friend class StretchTimeLine; |
| 6732 | + friend struct StretchTimeLinePrivate; |
| 6733 | + StretchTimeLine *_t; |
| 6734 | +}; |
| 6735 | + |
| 6736 | +class StretchTimeLineValue : public StretchTimeLineObject |
| 6737 | +{ |
| 6738 | +public: |
| 6739 | + StretchTimeLineValue(qreal v = 0.) : _v(v) {} |
| 6740 | + |
| 6741 | + virtual qreal value() const { return _v; } |
| 6742 | + virtual void setValue(qreal v) { _v = v; } |
| 6743 | + |
| 6744 | + StretchTimeLine *timeLine() const { return _t; } |
| 6745 | + |
| 6746 | + operator qreal() const { return _v; } |
| 6747 | + StretchTimeLineValue &operator=(qreal v) { setValue(v); return *this; } |
| 6748 | +private: |
| 6749 | + friend class StretchTimeLine; |
| 6750 | + friend struct StretchTimeLinePrivate; |
| 6751 | + qreal _v; |
| 6752 | +}; |
| 6753 | + |
| 6754 | +class StretchTimeLineCallback |
| 6755 | +{ |
| 6756 | +public: |
| 6757 | + typedef void (*Callback)(void *); |
| 6758 | + |
| 6759 | + StretchTimeLineCallback(); |
| 6760 | + StretchTimeLineCallback(StretchTimeLineObject *b, Callback, void * = nullptr); |
| 6761 | + StretchTimeLineCallback(const StretchTimeLineCallback &o); |
| 6762 | + |
| 6763 | + StretchTimeLineCallback &operator=(const StretchTimeLineCallback &o); |
| 6764 | + StretchTimeLineObject *callbackObject() const; |
| 6765 | + |
| 6766 | +private: |
| 6767 | + friend struct StretchTimeLinePrivate; |
| 6768 | + Callback d0; |
| 6769 | + void *d1; |
| 6770 | + StretchTimeLineObject *d2; |
| 6771 | +}; |
| 6772 | + |
| 6773 | +template<class T> |
| 6774 | +class StretchTimeLineValueProxy : public StretchTimeLineValue |
| 6775 | +{ |
| 6776 | +public: |
| 6777 | + StretchTimeLineValueProxy(T *object, void (T::*func)(qreal), qreal v = 0.) |
| 6778 | + : StretchTimeLineValue(v), object(object), setter(func) |
| 6779 | + { |
| 6780 | + Q_ASSERT(object); |
| 6781 | + } |
| 6782 | + |
| 6783 | + void setValue(qreal v) override |
| 6784 | + { |
| 6785 | + StretchTimeLineValue::setValue(v); |
| 6786 | + (object->*setter)(v); |
| 6787 | + } |
| 6788 | + |
| 6789 | +private: |
| 6790 | + T *object; |
| 6791 | + void (T::*setter)(qreal); |
| 6792 | +}; |
| 6793 | + |
| 6794 | +QT_END_NAMESPACE |
| 6795 | + |
| 6796 | +#endif |