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
1commit e2b4771717dae3fe135a8191c76b5e4e7cbe29d6
2Author: Sander <sander@sanderf.nl>
3Date: Fri Sep 4 16:44:51 2026 +0300
4
5 Initial commit
6
7diff --git a/CMakeLists.txt b/CMakeLists.txt
8new file mode 100644
9index 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+)
169diff --git a/COPYING b/COPYING
170new file mode 100644
171index 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>.
849diff --git a/COPYING.LESSER b/COPYING.LESSER
850new file mode 100644
851index 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.
1020diff --git a/LICENSE b/LICENSE
1021new file mode 100644
1022index 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.
1191diff --git a/README.md b/README.md
1192new file mode 100644
1193index 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.
1323diff --git a/cmake/Qml6StretchConfig.cmake.in b/cmake/Qml6StretchConfig.cmake.in
1324new file mode 100644
1325index 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)
1349diff --git a/cmake/QtQmlInstallLayout.cmake b/cmake/QtQmlInstallLayout.cmake
1350new file mode 100644
1351index 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}")
1485diff --git a/qml/StretchListView.qml b/qml/StretchListView.qml
1486new file mode 100644
1487index 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+}
1574diff --git a/src/stretchflickable.cpp b/src/stretchflickable.cpp
1575new file mode 100644
1576index 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"
4997diff --git a/src/stretchflickable_p.h b/src/stretchflickable_p.h
4998new file mode 100644
4999index 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
5314diff --git a/src/stretchflickable_p_p.h b/src/stretchflickable_p_p.h
5315new file mode 100644
5316index 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
5620diff --git a/src/stretchflickablebehavior_p.h b/src/stretchflickablebehavior_p.h
5621new file mode 100644
5622index 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
5698diff --git a/src/stretchtimeline.cpp b/src/stretchtimeline.cpp
5699new file mode 100644
5700index 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"
6635diff --git a/src/stretchtimeline_p_p.h b/src/stretchtimeline_p_p.h
6636new file mode 100644
6637index 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