mirror of
https://github.com/Dark98/SliceBeam.git
synced 2026-07-02 16:49:02 +00:00
Public source code release
This commit is contained in:
@@ -0,0 +1 @@
|
||||
custom: ['https://boosty.to/ytkab0bp']
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
+41
@@ -0,0 +1,41 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
app/src/main/boost
|
||||
app/src/main/jni/nanosvg/nanosvg.c
|
||||
app/src/main/jni/nanosvg/nanosvgrast.c
|
||||
app/src/main/jni/expat/expat_config.h
|
||||
app/src/main/jni/LibBGCode/binarize/export.h
|
||||
app/src/main/jni/LibBGCode/core/export.h
|
||||
app/src/main/jni/nlopt/nlopt_config.h
|
||||
app/src/main/jni/nlopt/api/nlopt.hpp
|
||||
app/src/main/jni/nlopt/api/nlopt.f
|
||||
app/src/main/jni/libjpeg-turbo/jconfig.h
|
||||
app/src/main/jni/libjpeg-turbo/jconfigint.h
|
||||
app/src/main/jni/libjpeg-turbo/jversion.h
|
||||
app/src/main/jni/openvdb/version.h
|
||||
app/src/main/jni/occt_wrapper/occtwrapper_export.h
|
||||
app/release/
|
||||
|
||||
.idea/
|
||||
|
||||
app/src/main/jniImports/boost/
|
||||
app/src/main/jniImports/oneTBB/
|
||||
app/src/main/occt/jniLibs/arm64-v8a
|
||||
app/src/main/occt/jniLibs/armeabi-v7a
|
||||
app/src/main/occt/jniLibs/x86
|
||||
app/src/main/occt/jniLibs/x86_64
|
||||
app/src/main/occt/include
|
||||
app/src/main/assets/update.json
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "EventBus"]
|
||||
path = EventBus
|
||||
url = https://github.com/utkabobr/EventBus
|
||||
Submodule
+1
Submodule EventBus added at 13c1500870
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
@@ -1 +1,63 @@
|
||||
# SliceBeam
|
||||

|
||||
# Slice Beam - Portable 3D Model slicer
|
||||
Slice Beam is a 3D model slicer/G-code generator for FFF 3D printers.
|
||||
|
||||
It is based on PrusaSlicer's core and well optimized for Android touchscreen interface.
|
||||
|
||||
# Links
|
||||
- Telegram: https://t.me/ytkab0bp_channel
|
||||
- Boosty (Patreon alternative): https://boosty.to/ytkab0bp
|
||||
- K3D Chat for discussion & support (Russian language only): https://t.me/K_3_D
|
||||
|
||||
# Quick Start
|
||||
Just download APK from [Releases tab](https://github.com/utkabobr/SliceBeam/releases/latest) and follow setup instructions. Google Play builds will be available later.
|
||||
|
||||
# Some screenshots
|
||||
   
|
||||
|
||||
# Forks policy
|
||||
I'm **VERY** convinced that you should **NOT** create forks just because you want a slicer with your name.
|
||||
|
||||
Re-skins are **ruining** community's efforts (You're just bundling outdated builds with a different name), please create pull requests to mainline, so **more** people will get to use **your** features.
|
||||
|
||||
If you wish your cloud services to be integrated into Slice Beam, just create a pull request, I'll try to merge them as soon, as I get some free time. Or you could provide me with your hardware/printers and documentation so I can make it on my own _(Who would even do this?)_
|
||||
|
||||
# No Kotlin?
|
||||

|
||||
|
||||
Yes, I'm **not** using Kotlin in my projects, Slice Beam is not an exception.
|
||||
|
||||
The build process already consumes a lot of time, using Kotlin will increase it drastically.
|
||||
|
||||
Please do not create pull requests with Kotlin source code, they will be declined.
|
||||
|
||||
# License
|
||||
Slice Beam is licensed under the _GNU Affero General Public License, version 3_.
|
||||
|
||||
It inherits license from PrusaSlicer, which is originally based on Slic3r by Alessandro Ranellucci.
|
||||
|
||||
It means that you **CAN NOT** just download all the source code, re-skin it and force your printer to work only with your slicer without releasing your source code.
|
||||
|
||||
You must **disclose your source code** as required by AGPL v3. Failing to do so will result in DMCA strikes on Google Play, GitHub releases (Hi Anycubic :P), Huawei AppGallery, etc.
|
||||
|
||||
# Building tips
|
||||
## Boost
|
||||
Build from this repo: https://github.com/moritz-wundke/Boost-for-Android, then copy libs from build/out into app/src/main/boost.
|
||||
|
||||
## oneTBB
|
||||
Build from this repo: https://github.com/syoyo/openvdb-android, then copy .a libs into app/src/main/jniImports/oneTBB; .so libs into app/src/main/jniLibs
|
||||
|
||||
## GMP & MPFR
|
||||
GMP & MPFR are bundled, but anyway they're from here: https://github.com/flaktack/android-mpfr
|
||||
|
||||
## OCCT
|
||||
Use original repo: https://github.com/Open-Cascade-SAS/OCCT
|
||||
|
||||
Build using adm/scripts/android_build
|
||||
|
||||
Copy .so libs to app/src/main/occt/jniLibs; Include files to app/src/main/occt/include
|
||||
|
||||
You can also reduce size of libraries by removing unnecessary modules (Check app/src/main/occt/jniLibs/clean.py)
|
||||
|
||||
## Why not to include all of them in repo?
|
||||
They're HUGE (More than 2 Gb) binary files, so you must build them yourself.
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
+1351
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def commit = getGitCommitHash(file('.'))
|
||||
|
||||
android {
|
||||
namespace 'ru.ytkab0bp.slicebeam'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "ru.ytkab0bp.slicebeam"
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
versionCode 2
|
||||
versionName "0.1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_shared', '-DANDROID_PLATFORM=21',
|
||||
'-DCMAKE_BUILD_TYPE=Release' // Disabling this results in drastically degradation of slicing times on debug builds
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs += file('src/main/occt/jniLibs')
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
buildConfigField "boolean", "IS_GOOGLE_PLAY", "true"
|
||||
buildConfigField "String", "COMMIT", "\"" + commit + "\""
|
||||
}
|
||||
debug {
|
||||
buildConfigField "boolean", "IS_GOOGLE_PLAY", "false"
|
||||
buildConfigField "String", "COMMIT", "\"" + commit + "\""
|
||||
ndk {
|
||||
debugSymbolLevel 'NONE'
|
||||
//noinspection ChromeOsAbiSupport
|
||||
abiFilters "arm64-v8a"
|
||||
}
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path file('CMakeLists.txt')
|
||||
}
|
||||
ndkVersion "23.1.7779620"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.all {
|
||||
outputFileName = "SliceBeam_" + commit + ".apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String getGitCommitHash(File dir) {
|
||||
try {
|
||||
return Runtime.getRuntime().exec("git rev-parse HEAD", null, dir).inputStream.readLines().get(0).substring(0, 10)
|
||||
} catch (Exception e){
|
||||
println("Unable to get git commit hash:")
|
||||
e.printStackTrace()
|
||||
return "non-git build"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":eventbus")
|
||||
implementation project(":eventbus_api")
|
||||
annotationProcessor project(":eventbus_processor")
|
||||
|
||||
implementation 'com.github.mrudultora:Colorpicker:1.2.0'
|
||||
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'com.loopj.android:android-async-http:1.4.11'
|
||||
implementation 'androidx.activity:activity:1.9.1'
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature android:glEsVersion="0x00030000" android:required="true"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/icon"
|
||||
android:label="@string/AppName"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SliceBeam"
|
||||
android:name=".SliceBeam"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:allowTaskReparenting="true"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.SliceBeam">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:host="*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".SetupActivity" android:exported="false"/>
|
||||
<activity android:name=".SafeStartActivity" android:exported="false"/>
|
||||
<provider
|
||||
android:authorities="ru.ytkab0bp.slicebeam.provider"
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
localStorage.setItem('k3d_la_bedX', $['bed_x']);
|
||||
localStorage.setItem('k3d_la_bedY', $['bed_y']);
|
||||
localStorage.setItem('k3d_la_zOffset', $['z_offset']);
|
||||
|
||||
localStorage.setItem('k3d_la_hotendTemperature', $['temperature']);
|
||||
localStorage.setItem('k3d_la_bedTemperature', $['bed_temperature']);
|
||||
localStorage.setItem('k3d_la_flow', $['extrusion_multiplier'] * 100);
|
||||
localStorage.setItem('k3d_la_cooling', $['min_fan_speed']);
|
||||
localStorage.setItem('k3d_la_lineWidth', $['nozzle_diameter[0]']);
|
||||
localStorage.setItem('k3d_la_firstLayerLineWidth', Math.round($['nozzle_diameter[0]'] * 1.5 * 10) / 10);
|
||||
localStorage.setItem('k3d_la_layerHeight', Math.round($['nozzle_diameter[0]'] * 0.5 * 10) / 10);
|
||||
localStorage.setItem('k3d_la_firstLayerSpeed', $['first_layer_speed']);
|
||||
localStorage.setItem('k3d_la_travelSpeed', $['travel_speed']);
|
||||
localStorage.setItem('k3d_la_numPerimeters', $['perimeters']);
|
||||
|
||||
localStorage.setItem('k3d_la_firmwareMarlin', $['gcode_flavor'] == 'marlin2' || $['gcode_flavor'] == 'marlin');
|
||||
localStorage.setItem('k3d_la_firmwareKlipper', $['gcode_flavor'] == 'klipper');
|
||||
localStorage.setItem('k3d_la_firmwareRRF', $['gcode_flavor'] == 'reprap' || $['gcode_flavor'] == 'reprapFirmware');
|
||||
loadForm();
|
||||
|
||||
var style = document.createElement('style');
|
||||
style.textContent = `
|
||||
[data-md-color-scheme=slate][data-md-color-primary=teal] {
|
||||
--md-typeset-a-color: $['color_accent'];
|
||||
}
|
||||
[data-md-color-primary=teal] {
|
||||
--md-primary-fg-color: $['color_accent'];
|
||||
}
|
||||
[data-md-color-scheme=slate] {
|
||||
--md-default-bg-color: $['window_background_dark'];
|
||||
}
|
||||
[data-md-color-scheme=default] {
|
||||
--md-default-bg-color: $['window_background_light'];
|
||||
}
|
||||
.caliButtonTable td {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.caliButton {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
var dark = $['is_dark_theme'];
|
||||
document.getElementsByClassName('md-option')[dark ? 1 : 0].click();
|
||||
|
||||
beginSaveFile = function(filename) {
|
||||
SliceBeam.beginDownload(filename);
|
||||
}
|
||||
|
||||
writeToFile = function(data) {
|
||||
SliceBeam.writeData(btoa(unescape(encodeURIComponent(data))));
|
||||
}
|
||||
|
||||
finishFile = function() {
|
||||
SliceBeam.finishDownload();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
localStorage.setItem('bedX', $['bed_x']);
|
||||
localStorage.setItem('bedY', $['bed_y']);
|
||||
localStorage.setItem('zOffset', $['z_offset']);
|
||||
|
||||
localStorage.setItem('hotendTemperature', $['temperature']);
|
||||
localStorage.setItem('bedTemperature', $['bed_temperature']);
|
||||
localStorage.setItem('flow', $['extrusion_multiplier'] * 100);
|
||||
localStorage.setItem('cooling', $['min_fan_speed']);
|
||||
localStorage.setItem('lineWidth', $['nozzle_diameter[0]']);
|
||||
localStorage.setItem('firstLayerLineWidth', Math.round($['nozzle_diameter[0]'] * 1.5 * 10) / 10);
|
||||
localStorage.setItem('layerHeight', Math.round($['nozzle_diameter[0]'] * 0.5 * 10) / 10);
|
||||
localStorage.setItem('printSpeed', $['perimeter_speed']);
|
||||
localStorage.setItem('firstLayerPrintSpeed', $['first_layer_speed']);
|
||||
localStorage.setItem('travelSpeed', $['travel_speed']);
|
||||
|
||||
localStorage.setItem('firmwareMarlin', $['gcode_flavor'] == 'marlin2' || $['gcode_flavor'] == 'marlin');
|
||||
localStorage.setItem('firmwareKlipper', $['gcode_flavor'] == 'klipper');
|
||||
localStorage.setItem('firmwareRRF', $['gcode_flavor'] == 'reprap' || $['gcode_flavor'] == 'reprapFirmware');
|
||||
loadForm();
|
||||
|
||||
var style = document.createElement('style');
|
||||
style.textContent = `
|
||||
[data-md-color-scheme=slate][data-md-color-primary=teal] {
|
||||
--md-typeset-a-color: $['color_accent'];
|
||||
}
|
||||
[data-md-color-primary=teal] {
|
||||
--md-primary-fg-color: $['color_accent'];
|
||||
}
|
||||
[data-md-color-scheme=slate] {
|
||||
--md-default-bg-color: $['window_background_dark'];
|
||||
}
|
||||
[data-md-color-scheme=default] {
|
||||
--md-default-bg-color: $['window_background_light'];
|
||||
}
|
||||
.caliButtonTable td {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.caliButton {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
var dark = $['is_dark_theme'];
|
||||
document.getElementsByClassName('md-option')[dark ? 1 : 0].click();
|
||||
|
||||
saveTextAsFile = function(filename, text) {
|
||||
SliceBeam.beginDownload(filename);
|
||||
SliceBeam.writeData(btoa(unescape(encodeURIComponent(text))));
|
||||
SliceBeam.finishDownload();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 top_color;
|
||||
uniform vec4 bottom_color;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = mix(bottom_color, top_color, tex_coord.y);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
attribute vec3 v_position;
|
||||
attribute vec2 v_tex_coord;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
tex_coord = v_tex_coord;
|
||||
gl_Position = vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 top_color;
|
||||
uniform vec4 bottom_color;
|
||||
uniform vec2 resolution;
|
||||
uniform float time;
|
||||
uniform float progress;
|
||||
|
||||
in vec2 tex_coord;
|
||||
|
||||
out vec4 fragment_color;
|
||||
|
||||
//
|
||||
// 2-D tiling simplex noise with rotating gradients and analytical derivative.
|
||||
// "vec2 x" is the point (x,y) to evaluate,
|
||||
// "vec2 period" is the desired periods along x and y, and
|
||||
// "float alpha" is the rotation (in radians) for the swirling gradients.
|
||||
// The "float" return value is the noise value, and
|
||||
// the "out vec2 gradient" argument returns the x,y partial derivatives.
|
||||
//
|
||||
// Setting either period to 0.0 or a negative value will skip the wrapping
|
||||
// along that dimension. Setting both periods to 0.0 makes the function
|
||||
// execute about 15% faster.
|
||||
//
|
||||
// Not using the return value for the gradient will make the compiler
|
||||
// eliminate the code for computing it. This speeds up the function
|
||||
// by 10-15%.
|
||||
//
|
||||
// The rotation by alpha uses one single addition. Unlike the 3-D version
|
||||
// of psrdnoise(), setting alpha == 0.0 gives no speedup.
|
||||
//
|
||||
float psrdnoise(vec2 x, vec2 period, float alpha, out vec2 gradient) {
|
||||
|
||||
// Transform to simplex space (axis-aligned hexagonal grid)
|
||||
vec2 uv = vec2(x.x + x.y*0.5, x.y);
|
||||
|
||||
// Determine which simplex we're in, with i0 being the "base"
|
||||
vec2 i0 = floor(uv);
|
||||
vec2 f0 = fract(uv);
|
||||
// o1 is the offset in simplex space to the second corner
|
||||
float cmp = step(f0.y, f0.x);
|
||||
vec2 o1 = vec2(cmp, 1.0-cmp);
|
||||
|
||||
// Enumerate the remaining simplex corners
|
||||
vec2 i1 = i0 + o1;
|
||||
vec2 i2 = i0 + vec2(1.0, 1.0);
|
||||
|
||||
// Transform corners back to texture space
|
||||
vec2 v0 = vec2(i0.x - i0.y * 0.5, i0.y);
|
||||
vec2 v1 = vec2(v0.x + o1.x - o1.y * 0.5, v0.y + o1.y);
|
||||
vec2 v2 = vec2(v0.x + 0.5, v0.y + 1.0);
|
||||
|
||||
// Compute vectors from v to each of the simplex corners
|
||||
vec2 x0 = x - v0;
|
||||
vec2 x1 = x - v1;
|
||||
vec2 x2 = x - v2;
|
||||
|
||||
vec3 iu, iv;
|
||||
vec3 xw, yw;
|
||||
|
||||
// Wrap to periods, if desired
|
||||
if(any(greaterThan(period, vec2(0.0)))) {
|
||||
xw = vec3(v0.x, v1.x, v2.x);
|
||||
yw = vec3(v0.y, v1.y, v2.y);
|
||||
if(period.x > 0.0)
|
||||
xw = mod(vec3(v0.x, v1.x, v2.x), period.x);
|
||||
if(period.y > 0.0)
|
||||
yw = mod(vec3(v0.y, v1.y, v2.y), period.y);
|
||||
// Transform back to simplex space and fix rounding errors
|
||||
iu = floor(xw + 0.5*yw + 0.5);
|
||||
iv = floor(yw + 0.5);
|
||||
} else { // Shortcut if neither x nor y periods are specified
|
||||
iu = vec3(i0.x, i1.x, i2.x);
|
||||
iv = vec3(i0.y, i1.y, i2.y);
|
||||
}
|
||||
|
||||
// Compute one pseudo-random hash value for each corner
|
||||
vec3 hash = mod(iu, 289.0);
|
||||
hash = mod((hash*51.0 + 2.0)*hash + iv, 289.0);
|
||||
hash = mod((hash*34.0 + 10.0)*hash, 289.0);
|
||||
|
||||
// Pick a pseudo-random angle and add the desired rotation
|
||||
vec3 psi = hash * 0.07482 + alpha;
|
||||
vec3 gx = cos(psi);
|
||||
vec3 gy = sin(psi);
|
||||
|
||||
// Reorganize for dot products below
|
||||
vec2 g0 = vec2(gx.x,gy.x);
|
||||
vec2 g1 = vec2(gx.y,gy.y);
|
||||
vec2 g2 = vec2(gx.z,gy.z);
|
||||
|
||||
// Radial decay with distance from each simplex corner
|
||||
vec3 w = 0.8 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2));
|
||||
w = max(w, 0.0);
|
||||
vec3 w2 = w * w;
|
||||
vec3 w4 = w2 * w2;
|
||||
|
||||
// The value of the linear ramp from each of the corners
|
||||
vec3 gdotx = vec3(dot(g0, x0), dot(g1, x1), dot(g2, x2));
|
||||
|
||||
// Multiply by the radial decay and sum up the noise value
|
||||
float n = dot(w4, gdotx);
|
||||
|
||||
// Compute the first order partial derivatives
|
||||
vec3 w3 = w2 * w;
|
||||
vec3 dw = -8.0 * w3 * gdotx;
|
||||
vec2 dn0 = w4.x * g0 + dw.x * x0;
|
||||
vec2 dn1 = w4.y * g1 + dw.y * x1;
|
||||
vec2 dn2 = w4.z * g2 + dw.z * x2;
|
||||
gradient = 10.9 * (dn0 + dn1 + dn2);
|
||||
|
||||
// Scale the return value to fit nicely into the range [-1,1]
|
||||
return 10.9 * n;
|
||||
}
|
||||
|
||||
float bounceOut(in float t) {
|
||||
const float a = 4.0 / 11.0;
|
||||
const float b = 8.0 / 11.0;
|
||||
const float c = 9.0 / 10.0;
|
||||
|
||||
const float ca = 4356.0 / 361.0;
|
||||
const float cb = 35442.0 / 1805.0;
|
||||
const float cc = 16061.0 / 1805.0;
|
||||
|
||||
float t2 = t * t;
|
||||
|
||||
return t < a
|
||||
? 7.5625 * t2
|
||||
: t < b
|
||||
? 9.075 * t2 - 9.9 * t + 3.4
|
||||
: t < c
|
||||
? ca * t2 - cb * t + cc
|
||||
: 10.8 * t * t - 20.52 * t + 10.72;
|
||||
}
|
||||
|
||||
vec2 rot(vec2 v, float a){
|
||||
return mat2x2(
|
||||
cos(a), -sin(a),
|
||||
sin(a), cos(a)
|
||||
) * v;
|
||||
}
|
||||
|
||||
float bounceIn(in float t) {
|
||||
return 1.0 - bounceOut(1.0 - t);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float PI = radians(180.0);
|
||||
|
||||
vec2 st = tex_coord;
|
||||
st = rot(st, -PI / 2.0);
|
||||
|
||||
vec2 gradient;
|
||||
float n = psrdnoise(vec2(3.) * st, vec2(0.), 3.0 * time, gradient);
|
||||
float lines = cos((st.x + n * 0.05 + progress * 0.8 + 0.1) * PI);
|
||||
|
||||
if (tex_coord.y < 0.1 + progress * 0.9) {
|
||||
fragment_color = bottom_color;
|
||||
} else {
|
||||
fragment_color = mix(top_color, bottom_color, bounceIn(lines * 0.5 + 0.5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
|
||||
in vec3 v_position;
|
||||
in vec2 v_tex_coord;
|
||||
|
||||
out vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
tex_coord = v_tex_coord;
|
||||
gl_Position = vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
recision highp float;
|
||||
|
||||
// see as reference: https://stackoverflow.com/questions/52928678/dashed-line-in-opengl3
|
||||
|
||||
uniform float dash_size;
|
||||
uniform float gap_size;
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
varying float coord_s;
|
||||
|
||||
void main()
|
||||
{
|
||||
float inv_stride = 1.0 / (dash_size + gap_size);
|
||||
if (gap_size > 0.0 && fract(coord_s * inv_stride) > dash_size * inv_stride)
|
||||
discard;
|
||||
|
||||
gl_FragColor = uniform_color;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// see as reference: https://stackoverflow.com/questions/52928678/dashed-line-in-opengl3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
// v_position.w = coordinate along the line
|
||||
attribute vec4 v_position;
|
||||
|
||||
varying float coord_s;
|
||||
|
||||
void main()
|
||||
{
|
||||
coord_s = v_position.w;
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position.xyz, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = uniform_color;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
attribute vec3 v_position;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
precision highp float;
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
gl_FragColor = uniform_color;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
|
||||
// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane - general orientation. Used by the SLA gizmo.
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
vec4 world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D uniform_texture;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(uniform_texture, tex_coord);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec2 v_tex_coord;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
tex_coord = v_tex_coord;
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
precision highp float;
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float EPSILON = 0.0001;
|
||||
|
||||
struct PrintVolumeDetection
|
||||
{
|
||||
// 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid
|
||||
int type;
|
||||
// type = 0 (rectangle):
|
||||
// x = min.x, y = min.y, z = max.x, w = max.y
|
||||
// type = 1 (circle):
|
||||
// x = center.x, y = center.y, z = radius
|
||||
vec4 xy_data;
|
||||
// x = min z, y = max z
|
||||
vec2 z_data;
|
||||
};
|
||||
|
||||
struct SlopeDetection
|
||||
{
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform bool use_color_clip_plane;
|
||||
uniform vec4 uniform_color_clip_plane_1;
|
||||
uniform vec4 uniform_color_clip_plane_2;
|
||||
uniform SlopeDetection slope;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
uniform sampler2D environment_tex;
|
||||
uniform bool use_environment_tex;
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
|
||||
uniform PrintVolumeDetection print_volume;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying float color_clip_plane_dot;
|
||||
|
||||
// x = diffuse, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying vec4 world_pos;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec4 color;
|
||||
if (use_color_clip_plane) {
|
||||
color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb;
|
||||
color.a = uniform_color.a;
|
||||
}
|
||||
else
|
||||
color = uniform_color;
|
||||
|
||||
if (slope.actived && world_normal_z < slope.normal_z - EPSILON) {
|
||||
color.rgb = vec3(0.7, 0.7, 1.0);
|
||||
color.a = 1.0;
|
||||
}
|
||||
|
||||
// if the fragment is outside the print volume -> use darker color
|
||||
vec3 pv_check_min = ZERO;
|
||||
vec3 pv_check_max = ZERO;
|
||||
if (print_volume.type == 0) {
|
||||
// rectangle
|
||||
pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
|
||||
pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
|
||||
}
|
||||
else if (print_volume.type == 1) {
|
||||
// circle
|
||||
float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
|
||||
pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
|
||||
pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
|
||||
}
|
||||
color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
if (use_environment_tex)
|
||||
gl_FragColor = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a);
|
||||
else
|
||||
#endif
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SHININESS 5.0
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
struct SlopeDetection
|
||||
{
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
};
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform SlopeDetection slope;
|
||||
|
||||
// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane - general orientation. Used by the SLA gizmo.
|
||||
uniform vec4 clipping_plane;
|
||||
// Color clip plane - general orientation. Used by the cut gizmo.
|
||||
uniform vec4 color_clip_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
|
||||
// x = diffuse, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying float color_clip_plane_dot;
|
||||
|
||||
varying vec4 world_pos;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// Point in homogenous coordinates.
|
||||
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
|
||||
|
||||
gl_Position = projection_matrix * position;
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
color_clip_plane_dot = dot(world_pos, color_clip_plane);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform float emission_factor;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
|
||||
uniform bool volume_mirrored;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
void main()
|
||||
{
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
if (volume_mirrored)
|
||||
normal = -normal;
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
gl_Position = projection_matrix * position;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform float emission_factor;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying float clipping_planes_dot;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (clipping_planes_dot < 0.0)
|
||||
discard;
|
||||
|
||||
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
|
||||
// Clipping plane - general orientation. Used by the SLA gizmo.
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying float clipping_planes_dot;
|
||||
|
||||
void main()
|
||||
{
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 eye_position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
gl_Position = projection_matrix * eye_position;
|
||||
|
||||
// Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded.
|
||||
clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
uniform vec4 uniform_color;
|
||||
uniform float emission_factor;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
|
||||
// vertex attributes
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
// instance attributes
|
||||
attribute vec3 i_offset;
|
||||
attribute vec2 i_scales;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
void main()
|
||||
{
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 world_position = vec4(v_position * vec3(vec2(1.5 * i_scales.x), 1.5 * i_scales.y) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0);
|
||||
vec4 eye_position = view_model_matrix * world_position;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
gl_Position = projection_matrix * eye_position;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D Texture;
|
||||
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
uniform mat4 ProjMtx;
|
||||
|
||||
attribute vec2 Position;
|
||||
attribute vec2 UV;
|
||||
attribute vec4 Color;
|
||||
|
||||
varying vec2 Frag_UV;
|
||||
varying vec4 Frag_Color;
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = uniform_color;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform float offset;
|
||||
|
||||
attribute vec3 v_position;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Add small epsilon to z to solve z-fighting
|
||||
vec4 clip_position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
clip_position.z -= offset * abs(clip_position.w);
|
||||
gl_Position = clip_position;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#extension GL_OES_standard_derivatives : enable
|
||||
|
||||
precision highp float;
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float EPSILON = 0.0001;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
uniform bool volume_mirrored;
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
vec3 color = uniform_color.rgb;
|
||||
float alpha = uniform_color.a;
|
||||
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
#ifdef FLIP_TRIANGLE_NORMALS
|
||||
triangle_normal = -triangle_normal;
|
||||
#endif
|
||||
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 eye_normal = normalize(view_normal_matrix * triangle_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
// x = diffuse, y = specular;
|
||||
vec2 intensity = vec2(0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (view_model_matrix * model_pos).xyz;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
|
||||
// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane - general orientation. Used by the SLA gizmo.
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
|
||||
void main()
|
||||
{
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
// Point in homogenous coordinates.
|
||||
vec4 world_pos = volume_world_matrix * model_pos;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
precision highp float;
|
||||
|
||||
const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
|
||||
const vec3 back_color_light = vec3(0.365, 0.365, 0.365);
|
||||
|
||||
uniform sampler2D texture;
|
||||
uniform bool transparent_background;
|
||||
uniform bool svg_source;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
vec4 svg_color()
|
||||
{
|
||||
// takes foreground from texture
|
||||
vec4 fore_color = texture2D(texture, tex_coord);
|
||||
|
||||
// calculates radial gradient
|
||||
vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5)))));
|
||||
|
||||
// blends foreground with background
|
||||
return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0);
|
||||
}
|
||||
|
||||
vec4 non_svg_color()
|
||||
{
|
||||
// takes foreground from texture
|
||||
vec4 color = texture2D(texture, tex_coord);
|
||||
return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = svg_source ? svg_color() : non_svg_color();
|
||||
color.a = transparent_background ? color.a * 0.5 : color.a;
|
||||
gl_FragColor = color;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec2 v_tex_coord;
|
||||
|
||||
varying vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
tex_coord = v_tex_coord;
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
precision highp float;
|
||||
|
||||
const vec3 BLACK = vec3(0.1);
|
||||
const vec3 WHITE = vec3(0.9);
|
||||
|
||||
const float emission_factor = 0.25;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
varying vec3 position;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 color = position.x * position.y * position.z > 0.0 ? BLACK : WHITE;
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color * (intensity.x + emission_factor), 1.0);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
varying vec3 position;
|
||||
|
||||
void main()
|
||||
{
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 eye_position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
position = v_position;
|
||||
gl_Position = projection_matrix * eye_position;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#extension GL_OES_standard_derivatives : enable
|
||||
|
||||
precision highp float;
|
||||
|
||||
#define M_PI 3.1415926535897932384626433832795
|
||||
|
||||
// 2D texture (1D texture split by the rows) of color along the object Z axis.
|
||||
uniform sampler2D z_texture;
|
||||
// Scaling from the Z texture rows coordinate to the normalized texture row coordinate.
|
||||
uniform float z_to_texture_row;
|
||||
uniform float z_texture_row_to_normalized;
|
||||
uniform float z_cursor;
|
||||
uniform float z_cursor_band_width;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying float object_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
float object_z_row = z_to_texture_row * object_z;
|
||||
// Index of the row in the texture.
|
||||
float z_texture_row = floor(object_z_row);
|
||||
// Normalized coordinate from 0. to 1.
|
||||
float z_texture_col = object_z_row - z_texture_row;
|
||||
float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25;
|
||||
// Calculate level of detail from the object Z coordinate.
|
||||
// This makes the slowly sloping surfaces to be shown with high detail (with stripes),
|
||||
// and the vertical surfaces to be shown with low detail (no stripes)
|
||||
float z_in_cells = object_z_row * 190.;
|
||||
// Gradient of Z projected on the screen.
|
||||
float dx_vtc = dFdx(z_in_cells);
|
||||
float dy_vtc = dFdy(z_in_cells);
|
||||
float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.);
|
||||
// Sample the Z texture. Texture coordinates are normalized to <0, 1>.
|
||||
vec4 color = vec4(0.25, 0.25, 0.25, 1.0);
|
||||
if (z_texture_row >= 0.0)
|
||||
color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.),
|
||||
texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod);
|
||||
// Mix the final color.
|
||||
gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION)
|
||||
//#define LIGHT_FRONT_SHININESS 5.0
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform float object_max_z;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
attribute vec2 v_tex_coord;
|
||||
|
||||
// x = tainted, y = specular;
|
||||
varying vec2 intensity;
|
||||
|
||||
varying float object_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
// =====================================================
|
||||
// NOTE:
|
||||
// when object_max_z > 0.0 we are rendering the overlay
|
||||
// when object_max_z == 0.0 we are rendering the volumes
|
||||
// =====================================================
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
vec3 normal = (object_max_z > 0.0) ? vec3(0.0, 0.0, 1.0) : normalize(view_normal_matrix * v_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular)
|
||||
NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
|
||||
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// Scaled to widths of the Z texture.
|
||||
object_z = (object_max_z > 0.0) ? object_max_z * v_tex_coord.y : (volume_world_matrix * vec4(v_position, 1.0)).z;
|
||||
|
||||
gl_Position = projection_matrix * position;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#extension GL_OES_standard_derivatives : enable
|
||||
|
||||
// see for reference: https://stackoverflow.com/questions/7361582/opengl-debugging-single-pass-wireframe-rendering
|
||||
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
|
||||
varying vec3 barycentric;
|
||||
|
||||
void main()
|
||||
{
|
||||
float min_dist = min(min(barycentric.x, barycentric.y), barycentric.z);
|
||||
if (min_dist > 0.5 * fwidth(min_dist))
|
||||
discard;
|
||||
|
||||
gl_FragColor = uniform_color;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform float offset;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
attribute vec3 v_extra;
|
||||
|
||||
varying vec3 barycentric;
|
||||
|
||||
void main()
|
||||
{
|
||||
barycentric = v_extra;
|
||||
// Add small epsilon to z to solve z-fighting
|
||||
vec4 clip_position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
clip_position.z -= offset * abs(clip_position.w);
|
||||
gl_Position = clip_position;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package ru.ytkab0bp.slicebeam;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.loopj.android.http.AsyncHttpClient;
|
||||
import com.loopj.android.http.AsyncHttpResponseHandler;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import cz.msebera.android.httpclient.Header;
|
||||
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
|
||||
public class BeamServerData {
|
||||
private final static String TAG = "BeamServerData";
|
||||
private final static String DATA_URL = "https://beam3d.ru/slicebeam.php?act=get_data";
|
||||
private final static String RUSSIA_CHECK_URL = "https://beam3d.ru/check_russia.txt";
|
||||
private static AsyncHttpClient client = new AsyncHttpClient();
|
||||
|
||||
static {
|
||||
client.setUserAgent(String.format(Locale.ROOT, "SliceBeam/%s-%d", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
client.setEnableRedirects(true);
|
||||
client.setLoggingEnabled(false);
|
||||
}
|
||||
|
||||
public List<String> boostySubscribers = new ArrayList<>();
|
||||
|
||||
public BeamServerData(JSONObject obj) {
|
||||
JSONArray arr = obj.optJSONArray("boosty_subscribers");
|
||||
if (arr != null) {
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
boostySubscribers.add(arr.optString(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isBoostyAvailable() {
|
||||
return !BuildConfig.IS_GOOGLE_PLAY || Prefs.isRussianIP();
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
client.get(DATA_URL, new AsyncHttpResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
|
||||
String str = new String(responseBody, StandardCharsets.UTF_8);
|
||||
Prefs.setBeamServerData(str);
|
||||
Prefs.setLastCheckedInfo();
|
||||
|
||||
try {
|
||||
SliceBeam.SERVER_DATA = new BeamServerData(new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// Disable Boosty only for Google Play builds on non-Russian IP's
|
||||
if (BuildConfig.IS_GOOGLE_PLAY) {
|
||||
client.get(RUSSIA_CHECK_URL, new AsyncHttpResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
|
||||
setIsRussia(new String(responseBody).equals("true"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
|
||||
if (statusCode == 403) {
|
||||
setIsRussia(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIsRussia(boolean v) {
|
||||
Prefs.setRussianIP(v);
|
||||
SliceBeam.EVENT_BUS.fireEvent(new BeamServerDataUpdatedEvent());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
SliceBeam.EVENT_BUS.fireEvent(new BeamServerDataUpdatedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
|
||||
Log.e(TAG, "Failed to update server data", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
package ru.ytkab0bp.slicebeam;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.ChangeLogBottomSheet;
|
||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.navigation.MobileNavigationDelegate;
|
||||
import ru.ytkab0bp.slicebeam.navigation.NavigationDelegate;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.IOUtils;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public final static int REQUEST_CODE_OPEN_FILE = 1, REQUEST_CODE_EXPORT_GCODE = 2,
|
||||
REQUEST_CODE_IMPORT_PROFILES = 3, REQUEST_CODE_EXPORT_PROFILES = 4;
|
||||
|
||||
private static MainActivity activeInstance;
|
||||
|
||||
public static List<ConfigObject> EXPORTING_PRINTS;
|
||||
public static List<ConfigObject> EXPORTING_FILAMENTS;
|
||||
public static List<ConfigObject> EXPORTING_PRINTERS;
|
||||
|
||||
private static SparseArray<NavigationDelegate> liveDelegate = new SparseArray<>();
|
||||
private static int lastId;
|
||||
|
||||
private int id;
|
||||
private NavigationDelegate delegate;
|
||||
private boolean landscape;
|
||||
private UnfoldMenu unfoldMenu;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (Prefs.getPrefs().contains("crash")) {
|
||||
startActivity(new Intent(this, SafeStartActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (SliceBeam.CONFIG == null) {
|
||||
Prefs.setLastCommit();
|
||||
startActivity(new Intent(this, SetupActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeInstance == null) {
|
||||
activeInstance = this;
|
||||
} else {
|
||||
Intent i = new Intent(this, MainActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (getIntent() != null) {
|
||||
i.setAction(getIntent().getAction());
|
||||
i.putExtras(getIntent());
|
||||
i.setDataAndType(getIntent().getData(), getIntent().getType());
|
||||
}
|
||||
startActivity(i);
|
||||
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
id = savedInstanceState == null ? lastId++ : savedInstanceState.getInt("id");
|
||||
|
||||
if (delegate == null) {
|
||||
NavigationDelegate saved = liveDelegate.get(id);
|
||||
liveDelegate.remove(id);
|
||||
if (saved != null && isCompatible(saved)) {
|
||||
delegate = saved;
|
||||
} else {
|
||||
delegate = onCreateDelegate();
|
||||
}
|
||||
}
|
||||
delegate.setContext(this);
|
||||
|
||||
delegate.onCreate();
|
||||
View v = delegate.onCreateView(this);
|
||||
if (delegate.getContainerView() == null || delegate.getContainerView().getParent() == null) {
|
||||
throw new IllegalArgumentException("Delegate hasn't created container view!");
|
||||
}
|
||||
ViewCompat.setOnApplyWindowInsetsListener(v, (v2, insets) -> {
|
||||
Insets systemBars = insets.getSystemWindowInsets();
|
||||
v2.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||
return insets.consumeSystemWindowInsets();
|
||||
});
|
||||
setContentView(v);
|
||||
|
||||
if (getIntent() != null && getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_VIEW)) {
|
||||
loadFile(getIntent().getData());
|
||||
setIntent(null);
|
||||
}
|
||||
|
||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
landscape = dm.widthPixels > dm.heightPixels;
|
||||
View decorView = getWindow().getDecorView();
|
||||
decorView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
getWindow().setNavigationBarColor(Color.TRANSPARENT);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
|
||||
if (landscape) {
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
|
||||
decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||
visibility |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
int finalVisibility = visibility;
|
||||
ViewUtils.postOnMainThread(() -> decorView.setSystemUiVisibility(finalVisibility), 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
getWindow().setStatusBarContrastEnforced(false);
|
||||
getWindow().setNavigationBarContrastEnforced(false);
|
||||
}
|
||||
if (ColorUtils.calculateLuminance(ThemesRepo.getColor(android.R.attr.windowBackground)) >= 0.9f) {
|
||||
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
} else {
|
||||
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Objects.equals(Prefs.getLastCommit(), BuildConfig.COMMIT) && SliceBeam.hasUpdateInfo) {
|
||||
Prefs.setLastCommit();
|
||||
BeamServerData.load();
|
||||
new ChangeLogBottomSheet(this).show();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public NavigationDelegate getNavigationDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(@NonNull Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
loadFile(intent.getData());
|
||||
setIntent(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == MainActivity.REQUEST_CODE_EXPORT_GCODE) {
|
||||
try {
|
||||
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
||||
InputStream in = new FileInputStream(BedFragment.getTempGCodePath());
|
||||
byte[] buffer = new byte[10240];
|
||||
int c;
|
||||
while ((c = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, c);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (requestCode == MainActivity.REQUEST_CODE_OPEN_FILE) {
|
||||
loadFile(data.getData());
|
||||
} else if (requestCode == MainActivity.REQUEST_CODE_EXPORT_PROFILES) {
|
||||
try {
|
||||
Slic3rConfigWrapper w = new Slic3rConfigWrapper();
|
||||
w.printConfigs.addAll(EXPORTING_PRINTS);
|
||||
w.filamentConfigs.addAll(EXPORTING_FILAMENTS);
|
||||
w.printerConfigs.addAll(EXPORTING_PRINTERS);
|
||||
|
||||
EXPORTING_PRINTS = null;
|
||||
EXPORTING_FILAMENTS = null;
|
||||
EXPORTING_PRINTERS = null;
|
||||
|
||||
w.presets = new ConfigObject();
|
||||
if (w.findPrint(SliceBeam.CONFIG.presets.get("print")) != null) {
|
||||
w.presets.put("print", SliceBeam.CONFIG.presets.get("print"));
|
||||
}
|
||||
if (w.findFilament(SliceBeam.CONFIG.presets.get("filament")) != null) {
|
||||
w.presets.put("filament", SliceBeam.CONFIG.presets.get("filament"));
|
||||
}
|
||||
if (w.findPrinter(SliceBeam.CONFIG.presets.get("printer")) != null) {
|
||||
w.presets.put("printer", SliceBeam.CONFIG.presets.get("printer"));
|
||||
}
|
||||
|
||||
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
||||
out.write(w.serialize().getBytes(StandardCharsets.UTF_8));
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (requestCode == MainActivity.REQUEST_CODE_IMPORT_PROFILES) {
|
||||
Uri uri = data.getData();
|
||||
ContentResolver resolver = getContentResolver();
|
||||
|
||||
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
||||
Cursor metaCursor = resolver.query(uri, projection, null, null, null);
|
||||
String fileName = null;
|
||||
if (metaCursor != null) {
|
||||
try {
|
||||
if (metaCursor.moveToFirst()) {
|
||||
fileName = metaCursor.getString(0);
|
||||
}
|
||||
} finally {
|
||||
metaCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (fileName == null) {
|
||||
new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||
.setMessage(R.string.MenuFileOpenFileFailedNullName)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileName.endsWith(".orca_printer")) {
|
||||
Toast.makeText(MainActivity.this, R.string.OrcaConversionPleaseWait, Toast.LENGTH_SHORT).show();
|
||||
|
||||
File f = new File(SliceBeam.getModelCacheDir(), "orca_conv.zip");
|
||||
new Thread(()->{
|
||||
try {
|
||||
InputStream in = resolver.openInputStream(uri);
|
||||
FileOutputStream fos = new FileOutputStream(f);
|
||||
byte[] buffer = new byte[10240]; int c;
|
||||
while ((c = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, c);
|
||||
}
|
||||
fos.close();
|
||||
in.close();
|
||||
|
||||
ZipFile zf = new ZipFile(f);
|
||||
JSONObject bundle = new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry("bundle_structure.json"))));
|
||||
if (!bundle.get("bundle_type").equals("printer config bundle")) {
|
||||
zf.close();
|
||||
|
||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||
.setMessage(R.string.OrcaConversionNotAConfigBundle)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Slic3rConfigWrapper w = new Slic3rConfigWrapper();
|
||||
if (bundle.has("process_config")) {
|
||||
JSONArray arr = bundle.getJSONArray("process_config");
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> stripped = new ArrayList<>();
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
String v = arr.getString(i);
|
||||
names.add(v);
|
||||
stripped.add(v.substring(v.indexOf('/') + 1, v.length() - 5));
|
||||
}
|
||||
|
||||
for (String name : names) {
|
||||
w.printConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "process", Slic3rConfigWrapper.PRINT_CONFIG_KEYS, stripped));
|
||||
}
|
||||
for (ConfigObject obj : w.printConfigs) {
|
||||
String inherit = obj.get("inherits");
|
||||
while (inherit != null) {
|
||||
ConfigObject _obj = w.findPrint(inherit);
|
||||
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
||||
|
||||
obj.values.remove("inherits");
|
||||
HashMap<String, String> newMap = new HashMap<>();
|
||||
newMap.putAll(_obj.values);
|
||||
newMap.putAll(obj.values);
|
||||
obj.values = newMap;
|
||||
|
||||
inherit = obj.values.get("inherits");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bundle.has("filament_config")) {
|
||||
JSONArray arr = bundle.getJSONArray("filament_config");
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> stripped = new ArrayList<>();
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
String v = arr.getString(i);
|
||||
names.add(v);
|
||||
stripped.add(v.substring(v.indexOf('/') + 1, v.length() - 5));
|
||||
}
|
||||
|
||||
for (String name : names) {
|
||||
w.filamentConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "filament", Slic3rConfigWrapper.FILAMENT_CONFIG_KEYS, stripped));
|
||||
}
|
||||
for (ConfigObject obj : w.filamentConfigs) {
|
||||
String inherit = obj.get("inherits");
|
||||
while (inherit != null) {
|
||||
ConfigObject _obj = w.findFilament(inherit);
|
||||
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
||||
|
||||
obj.values.remove("inherits");
|
||||
HashMap<String, String> newMap = new HashMap<>();
|
||||
newMap.putAll(_obj.values);
|
||||
newMap.putAll(obj.values);
|
||||
obj.values = newMap;
|
||||
|
||||
inherit = obj.values.get("inherits");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bundle.has("printer_config")) {
|
||||
JSONArray arr = bundle.getJSONArray("printer_config");
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> stripped = new ArrayList<>();
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
String v = arr.getString(i);
|
||||
names.add(v);
|
||||
stripped.add(v.substring(v.indexOf('/') + 1));
|
||||
}
|
||||
|
||||
for (String name : names) {
|
||||
w.printerConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "machine", Slic3rConfigWrapper.PRINTER_CONFIG_KEYS, stripped));
|
||||
}
|
||||
for (ConfigObject obj : w.printerConfigs) {
|
||||
String inherit = obj.get("inherits");
|
||||
while (inherit != null) {
|
||||
ConfigObject _obj = w.findPrinter(inherit);
|
||||
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
||||
|
||||
obj.values.remove("inherits");
|
||||
HashMap<String, String> newMap = new HashMap<>();
|
||||
newMap.putAll(_obj.values);
|
||||
newMap.putAll(obj.values);
|
||||
obj.values = newMap;
|
||||
|
||||
inherit = obj.values.get("inherits");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zf.close();
|
||||
|
||||
loadIniForImport(new ByteArrayInputStream(w.serialize().getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||
.setMessage(e.toString())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.endsWith(".ini")) {
|
||||
new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||
.setMessage(R.string.MenuFileImportProfilesFailedNotIni)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loadIniForImport(resolver.openInputStream(uri));
|
||||
} catch (FileNotFoundException e) {
|
||||
new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||
.setMessage(e.toString())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadIniForImport(InputStream in) {
|
||||
new Thread(()->{
|
||||
try {
|
||||
Slic3rConfigWrapper w = new Slic3rConfigWrapper(in);
|
||||
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
CharSequence[] prints = new CharSequence[w.printConfigs.size()];
|
||||
boolean[] enabledPrints = new boolean[prints.length];
|
||||
for (int i = 0; i < prints.length; i++) {
|
||||
prints[i] = w.printConfigs.get(i).getTitle();
|
||||
enabledPrints[i] = true;
|
||||
}
|
||||
|
||||
CharSequence[] filaments = new CharSequence[w.filamentConfigs.size()];
|
||||
boolean[] enabledFilaments = new boolean[filaments.length];
|
||||
for (int i = 0; i < filaments.length; i++) {
|
||||
filaments[i] = w.filamentConfigs.get(i).getTitle();
|
||||
enabledFilaments[i] = true;
|
||||
}
|
||||
|
||||
CharSequence[] printers = new CharSequence[w.printerConfigs.size()];
|
||||
boolean[] enabledPrinters = new boolean[printers.length];
|
||||
for (int i = 0; i < printers.length; i++) {
|
||||
printers[i] = w.printerConfigs.get(i).getTitle();
|
||||
enabledPrinters[i] = true;
|
||||
}
|
||||
|
||||
new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileExportProfilesPrints)
|
||||
.setMultiChoiceItems(prints, enabledPrints, (dialog, which, isChecked) -> enabledPrints[which] = isChecked)
|
||||
.setPositiveButton(android.R.string.ok, (d1, w1) -> new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileExportProfilesFilaments)
|
||||
.setMultiChoiceItems(filaments, enabledFilaments, (dialog, which, isChecked) -> enabledFilaments[which] = isChecked)
|
||||
.setPositiveButton(android.R.string.ok, (d2, w2) -> new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileExportProfilesPrinters)
|
||||
.setMultiChoiceItems(printers, enabledPrinters, (dialog, which, isChecked) -> enabledPrinters[which] = isChecked)
|
||||
.setPositiveButton(android.R.string.ok, (d3, w3) -> {
|
||||
for (int i = 0; i < enabledPrints.length; i++) {
|
||||
if (enabledPrints[i]) {
|
||||
SliceBeam.CONFIG.importPrint(w.printConfigs.get(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < enabledFilaments.length; i++) {
|
||||
if (enabledFilaments[i]) {
|
||||
SliceBeam.CONFIG.importFilament(w.filamentConfigs.get(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < enabledPrinters.length; i++) {
|
||||
if (enabledPrinters[i]) {
|
||||
SliceBeam.CONFIG.importPrinter(w.printerConfigs.get(i));
|
||||
}
|
||||
}
|
||||
SliceBeam.saveConfig();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e("MainActivity", "Failed to read file", e);
|
||||
|
||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||
.setMessage(e.toString())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadFile(Uri uri) {
|
||||
if (uri == null) return;
|
||||
|
||||
ContentResolver resolver = getContentResolver();
|
||||
|
||||
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
||||
Cursor metaCursor = resolver.query(uri, projection, null, null, null);
|
||||
String fileName = null;
|
||||
if (metaCursor != null) {
|
||||
try {
|
||||
if (metaCursor.moveToFirst()) {
|
||||
fileName = metaCursor.getString(0);
|
||||
}
|
||||
} finally {
|
||||
metaCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (fileName == null) {
|
||||
new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileOpenFileFailed)
|
||||
.setMessage(R.string.MenuFileOpenFileFailedNullName)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
File f = new File(SliceBeam.getModelCacheDir(), fileName);
|
||||
// TODO: Check if file already exists
|
||||
new Thread(()->{
|
||||
try {
|
||||
InputStream in = resolver.openInputStream(uri);
|
||||
FileOutputStream fos = new FileOutputStream(f);
|
||||
byte[] buffer = new byte[10240]; int c;
|
||||
while ((c = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, c);
|
||||
}
|
||||
fos.close();
|
||||
in.close();
|
||||
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
if (delegate.getCurrentFragment() instanceof BedFragment) {
|
||||
BedFragment fragment = (BedFragment) delegate.getCurrentFragment();
|
||||
try {
|
||||
if (f.getName().endsWith(".gcode")) {
|
||||
fragment.loadGCode(f);
|
||||
} else {
|
||||
fragment.loadModel(f);
|
||||
SliceBeam.EVENT_BUS.fireEvent(new ObjectsListChangedEvent());
|
||||
}
|
||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileOpenFileLoaded));
|
||||
} catch (Slic3rRuntimeError e) {
|
||||
Log.e("MainActivity", "Failed to load model", e);
|
||||
f.delete();
|
||||
|
||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileOpenFileFailed)
|
||||
.setMessage(e.toString())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show());
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e("MainActivity", "Failed to write cache file", e);
|
||||
|
||||
f.delete();
|
||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||
.setTitle(R.string.MenuFileOpenFileFailed)
|
||||
.setMessage(e.toString())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if ((newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != Configuration.UI_MODE_NIGHT_UNDEFINED) {
|
||||
ThemesRepo.resetSystemResolvedTheme();
|
||||
ThemesRepo.invalidate(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void onApplyTheme() {
|
||||
delegate.onApplyTheme();
|
||||
|
||||
View decorView = getWindow().getDecorView();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ColorUtils.calculateLuminance(ThemesRepo.getColor(android.R.attr.windowBackground)) >= 0.9f) {
|
||||
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
} else {
|
||||
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
}
|
||||
decorView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (isChangingConfigurations()) {
|
||||
outState.putInt("id", id);
|
||||
liveDelegate.put(id, delegate);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCompatible(NavigationDelegate delegate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private NavigationDelegate onCreateDelegate() {
|
||||
return new MobileNavigationDelegate();
|
||||
}
|
||||
|
||||
public void showUnfoldMenu(UnfoldMenu menu, View v) {
|
||||
if (unfoldMenu != null) return;
|
||||
menu.setOnDismiss(() -> unfoldMenu = null);
|
||||
menu.show(v, delegate.getOverlayView());
|
||||
unfoldMenu = menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (unfoldMenu != null) {
|
||||
unfoldMenu.dismiss();
|
||||
return;
|
||||
}
|
||||
if (delegate.onBackPressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
delegate.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
delegate.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (activeInstance == this) {
|
||||
activeInstance = null;
|
||||
}
|
||||
if (delegate != null) {
|
||||
delegate.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package ru.ytkab0bp.slicebeam;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.BeamButton;
|
||||
|
||||
public class SafeStartActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
getWindow().setStatusBarColor(Color.WHITE);
|
||||
View v = getWindow().getDecorView();
|
||||
v.setSystemUiVisibility(v.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
|
||||
LinearLayout ll = new LinearLayout(this);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
ll.setBackgroundColor(Color.WHITE);
|
||||
TextView title = new TextView(this);
|
||||
title.setTextColor(Color.BLACK);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
|
||||
title.setText(R.string.AppCrashed);
|
||||
title.setGravity(Gravity.CENTER);
|
||||
title.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
title.setPadding(ViewUtils.dp(12), ViewUtils.dp(12), ViewUtils.dp(12), 0);
|
||||
ll.addView(title);
|
||||
|
||||
ScrollView scroll = new ScrollView(this);
|
||||
TextView desc = new TextView(this);
|
||||
desc.setTextColor(0x99000000);
|
||||
desc.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||
String log = getString(R.string.AppCrashedDesc, Build.VERSION.RELEASE, Build.BRAND + " " + Build.MODEL, Prefs.getPrefs().getString("crash", ""));
|
||||
desc.setText(log);
|
||||
desc.setPadding(0, 0, 0, ViewUtils.dp(12));
|
||||
scroll.setPadding(ViewUtils.dp(12), ViewUtils.dp(12), ViewUtils.dp(12), 0);
|
||||
scroll.addView(desc);
|
||||
ll.addView(scroll, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
BeamButton share = new BeamButton(this);
|
||||
share.setText(R.string.AppCrashedShare);
|
||||
share.setOnClickListener(v -> {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, log);
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
Intent shareIntent = Intent.createChooser(sendIntent, null);
|
||||
startActivity(shareIntent);
|
||||
});
|
||||
ll.addView(share, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
|
||||
TextView restart = new TextView(this);
|
||||
restart.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
restart.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||
restart.setGravity(Gravity.CENTER);
|
||||
restart.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
restart.setText(R.string.AppCrashedRestart);
|
||||
restart.setBackground(ViewUtils.createRipple(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorAccent), 0x21), 16));
|
||||
restart.setOnClickListener(v -> {
|
||||
Prefs.getPrefs().edit().remove("crash").apply();
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
finish();
|
||||
});
|
||||
ll.addView(restart, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||
topMargin = ViewUtils.dp(8);
|
||||
bottomMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
|
||||
setContentView(ll);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
package ru.ytkab0bp.slicebeam;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventBus;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.ConfigOptionDef;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.VibrationUtils;
|
||||
|
||||
public class SliceBeam extends Application {
|
||||
public static SliceBeam INSTANCE;
|
||||
public static EventBus EVENT_BUS = EventBus.newBus("main");
|
||||
public static Slic3rConfigWrapper CONFIG;
|
||||
public static int CONFIG_UID = 0;
|
||||
public static BeamServerData SERVER_DATA;
|
||||
public static boolean hasUpdateInfo;
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
INSTANCE = this;
|
||||
EventBus.registerImpl(this);
|
||||
Prefs.init(this);
|
||||
VibrationUtils.init(this);
|
||||
tryCheckInfo();
|
||||
PrintConfigDef.getInstance();
|
||||
try {
|
||||
getAssets().open("update.json").close();
|
||||
hasUpdateInfo = true;
|
||||
} catch (IOException e) {
|
||||
hasUpdateInfo = false;
|
||||
}
|
||||
|
||||
File cache = SliceBeam.getModelCacheDir();
|
||||
if (cache.exists()) {
|
||||
for (File f : cache.listFiles()) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
File cfgFile = getConfigFile();
|
||||
getCurrentConfigFile().delete();
|
||||
if (cfgFile.exists()) {
|
||||
try {
|
||||
CONFIG = new Slic3rConfigWrapper(cfgFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
|
||||
Prefs.getPrefs().edit().putString("crash", sw.toString()).commit();
|
||||
Intent intent = new Intent(this, SafeStartActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
Runtime.getRuntime().exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
private static void tryCheckInfo() {
|
||||
try {
|
||||
SERVER_DATA = new BeamServerData(new JSONObject(Prefs.getBeamServerData()));
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (System.currentTimeMillis() - Prefs.getLastCheckedInfo() >= 86400000L) {
|
||||
BeamServerData.load();
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveConfig() {
|
||||
SliceBeam.CONFIG_UID++;
|
||||
File f = getConfigFile();
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(f);
|
||||
fos.write(CONFIG.serialize().getBytes(StandardCharsets.UTF_8));
|
||||
fos.close();
|
||||
|
||||
getCurrentConfigFile().delete();
|
||||
} catch (Exception e) {
|
||||
Log.e("Config", "Failed to save config", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static File getModelCacheDir() {
|
||||
File f = new File(INSTANCE.getCacheDir(), "model");
|
||||
if (!f.exists()) f.mkdirs();
|
||||
return f;
|
||||
}
|
||||
|
||||
public static File getConfigFile() {
|
||||
return new File(INSTANCE.getFilesDir(), "slic3r.ini");
|
||||
}
|
||||
|
||||
public static ConfigObject buildCurrentConfigObject() {
|
||||
ConfigObject singleObject = new ConfigObject();
|
||||
singleObject.values.putAll(SliceBeam.CONFIG.findPrinter(SliceBeam.CONFIG.presets.get("printer")).values);
|
||||
if (SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print")) != null) {
|
||||
singleObject.values.putAll(SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print")).values);
|
||||
}
|
||||
if (SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")) != null) {
|
||||
singleObject.values.putAll(SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")).values);
|
||||
}
|
||||
|
||||
PrintConfigDef def = PrintConfigDef.getInstance();
|
||||
for (Map.Entry<String, ConfigOptionDef> en : def.options.entrySet()) {
|
||||
if (singleObject.get(en.getKey()) == null && !PrintConfigDef.SKIP_DEFAULT_OPTIONS.contains(en.getKey()) && en.getValue().defaultValue != null) {
|
||||
singleObject.put(en.getKey(), en.getValue().defaultValue);
|
||||
}
|
||||
}
|
||||
return singleObject;
|
||||
}
|
||||
|
||||
public static void genCurrentConfig() throws IOException {
|
||||
File cfg = getCurrentConfigFile();
|
||||
if (!cfg.exists()) {
|
||||
FileOutputStream fos = new FileOutputStream(cfg);
|
||||
ConfigObject singleObject = buildCurrentConfigObject();
|
||||
fos.write(singleObject.serialize().getBytes(StandardCharsets.UTF_8));
|
||||
fos.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static File getCurrentConfigFile() {
|
||||
return new File(INSTANCE.getFilesDir(), "slic3r_current.ini");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package ru.ytkab0bp.slicebeam.components;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.AppCompatCheckedTextView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class BeamAlertDialogBuilder extends MaterialAlertDialogBuilder {
|
||||
public BeamAlertDialogBuilder(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public BeamAlertDialogBuilder(@NonNull Context context, int overrideThemeResId) {
|
||||
super(context, overrideThemeResId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
AlertDialog dialog = super.create();
|
||||
dialog.getWindow().setBackgroundDrawable(new GradientDrawable() {{
|
||||
setCornerRadius(ViewUtils.dp(32));
|
||||
setColor(ThemesRepo.getColor(R.attr.dialogBackground));
|
||||
}});
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
AlertDialog dialog = super.show();
|
||||
View v = dialog.getWindow().getDecorView();
|
||||
TextView v2;
|
||||
int id = getContext().getResources().getIdentifier("alertTitle", "id", getContext().getPackageName());
|
||||
if (id > 0) {
|
||||
v2 = v.findViewById(id);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
}
|
||||
}
|
||||
v2 = v.findViewById(android.R.id.message);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
}
|
||||
|
||||
v2 = v.findViewById(android.R.id.button1);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
}
|
||||
v2 = v.findViewById(android.R.id.button2);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
}
|
||||
v2 = v.findViewById(android.R.id.button3);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
}
|
||||
id = getContext().getResources().getIdentifier("select_dialog_listview", "id", getContext().getPackageName());
|
||||
if (id > 0) {
|
||||
ListView lv = v.findViewById(id);
|
||||
if (lv != null) {
|
||||
ListAdapter wrapped = lv.getAdapter();
|
||||
SparseBooleanArray checked = lv.getCheckedItemPositions() != null ? lv.getCheckedItemPositions().clone() : null;
|
||||
lv.setAdapter(new ListAdapter() {
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return wrapped.areAllItemsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return wrapped.isEnabled(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
wrapped.registerDataSetObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
wrapped.unregisterDataSetObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return wrapped.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return wrapped.getItem(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return wrapped.getItemId(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return wrapped.hasStableIds();
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = wrapped.getView(position, convertView, parent);
|
||||
TextView text = v.findViewById(android.R.id.text1);
|
||||
if (text != null) {
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
if (text instanceof AppCompatCheckedTextView) {
|
||||
((AppCompatCheckedTextView) text).setSupportCompoundDrawablesTintList(new ColorStateList(new int[][]{
|
||||
{android.R.attr.state_enabled, android.R.attr.state_checked},
|
||||
{android.R.attr.state_enabled, -android.R.attr.state_checked}
|
||||
}, new int[]{
|
||||
ThemesRepo.getColor(android.R.attr.colorAccent),
|
||||
ThemesRepo.getColor(R.attr.dividerContrastColor)
|
||||
}));
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return wrapped.getItemViewType(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return wrapped.getViewTypeCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return wrapped.isEmpty();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence[] getAutofillOptions() {
|
||||
return wrapped.getAutofillOptions();
|
||||
}
|
||||
});
|
||||
if (checked != null) {
|
||||
for (int i = 0; i < checked.size(); i++) {
|
||||
lv.setItemChecked(checked.keyAt(i), checked.valueAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package ru.ytkab0bp.slicebeam.components;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.mrudultora.colorpicker.ColorPickerPopUp;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class BeamColorPickerPopUp extends ColorPickerPopUp {
|
||||
public BeamColorPickerPopUp(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
Dialog dialog = getDialog();
|
||||
dialog.getWindow().setBackgroundDrawable(new GradientDrawable() {{
|
||||
setCornerRadius(ViewUtils.dp(32));
|
||||
setColor(ThemesRepo.getColor(R.attr.dialogBackground));
|
||||
}});
|
||||
View v = dialog.getWindow().getDecorView();
|
||||
TextView v2;
|
||||
int id = getResources().getIdentifier("alertTitle", "id", "android");
|
||||
if (id != -1) {
|
||||
v2 = v.findViewById(id);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
}
|
||||
}
|
||||
v2 = v.findViewById(android.R.id.button1);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
}
|
||||
v2 = v.findViewById(android.R.id.button2);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
}
|
||||
v2 = v.findViewById(android.R.id.button3);
|
||||
if (v2 != null) {
|
||||
v2.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package ru.ytkab0bp.slicebeam.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.net.Uri;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Scroller;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.BeamServerData;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.BeamButton;
|
||||
import ru.ytkab0bp.slicebeam.view.BoostySubsView;
|
||||
|
||||
public class ChangeLogBottomSheet extends BottomSheetDialog {
|
||||
private ScrollView scrollView;
|
||||
private ViewPager pager;
|
||||
|
||||
public ChangeLogBottomSheet(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
LinearLayout ll = new LinearLayout(context);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setCornerRadii(new float[] {
|
||||
ViewUtils.dp(28), ViewUtils.dp(28),
|
||||
ViewUtils.dp(28), ViewUtils.dp(28),
|
||||
0, 0,
|
||||
0, 0
|
||||
});
|
||||
gd.setColor(ThemesRepo.getColor(R.attr.dialogBackground));
|
||||
ll.setBackground(gd);
|
||||
ll.setPadding(0, ViewUtils.dp(12), 0, ViewUtils.dp(12));
|
||||
|
||||
FrameLayout fl = new FrameLayout(context);
|
||||
TextView titleA = new TextView(context);
|
||||
titleA.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
|
||||
titleA.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
titleA.setText(R.string.Changelog);
|
||||
titleA.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
titleA.setGravity(Gravity.CENTER);
|
||||
titleA.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
fl.addView(titleA);
|
||||
|
||||
TextView titleB = new TextView(context);
|
||||
titleB.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
|
||||
titleB.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
titleB.setText(R.string.ChangelogBoosty);
|
||||
titleB.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||
titleB.setGravity(Gravity.CENTER);
|
||||
titleB.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
titleB.setAlpha(0f);
|
||||
fl.addView(titleB);
|
||||
|
||||
ll.addView(fl);
|
||||
|
||||
scrollView = new ScrollView(context);
|
||||
TextView text = new TextView(context);
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
text.setPadding(ViewUtils.dp(16), ViewUtils.dp(12), ViewUtils.dp(16), ViewUtils.dp(12));
|
||||
|
||||
try {
|
||||
InputStream in = getContext().getAssets().open("update.json");
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[10240]; int c;
|
||||
while ((c = in.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, c);
|
||||
}
|
||||
bos.close();
|
||||
in.close();
|
||||
|
||||
JSONObject obj = new JSONObject(bos.toString());
|
||||
String code = Locale.getDefault().getLanguage();
|
||||
if (obj.has(code)) {
|
||||
text.setText(obj.getString(code));
|
||||
} else {
|
||||
text.setText(obj.getString("en"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("Changelog", "Failed to open update file", e);
|
||||
}
|
||||
scrollView.addView(text);
|
||||
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
|
||||
pager = new ViewPager(context) {{
|
||||
try {
|
||||
Field scroller = ViewPager.class.getDeclaredField("mScroller");
|
||||
scroller.setAccessible(true);
|
||||
|
||||
Scroller mScroller = new Scroller(getContext(), ViewUtils.CUBIC_INTERPOLATOR::getInterpolation);
|
||||
scroller.set(this, mScroller);
|
||||
} catch (Exception ignored) {}
|
||||
}};
|
||||
pager.setAdapter(new PagerAdapter() {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return BeamServerData.isBoostyAvailable() ? 2 : 1;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
View v;
|
||||
if (position == 0) {
|
||||
v = scrollView;
|
||||
} else {
|
||||
LinearLayout ll = new LinearLayout(context);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
TextView subtitle = new TextView(context);
|
||||
subtitle.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||
subtitle.setText(R.string.ChangelogBoostyDescription);
|
||||
subtitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||
subtitle.setGravity(Gravity.CENTER);
|
||||
subtitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
subtitle.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||
ll.addView(subtitle);
|
||||
|
||||
BoostySubsView subsView = new BoostySubsView(context);
|
||||
if (SliceBeam.SERVER_DATA != null) {
|
||||
List<String> list = new ArrayList<>(SliceBeam.SERVER_DATA.boostySubscribers);
|
||||
Collections.shuffle(list);
|
||||
subsView.setStrings(list);
|
||||
}
|
||||
ll.addView(subsView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
TextView subscribeButton = new TextView(context);
|
||||
subscribeButton.setText(R.string.IntroBoostySupport);
|
||||
subscribeButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
subscribeButton.setTextColor(ThemesRepo.getColor(R.attr.boostyColorTop));
|
||||
subscribeButton.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
subscribeButton.setGravity(Gravity.CENTER);
|
||||
subscribeButton.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
||||
subscribeButton.setOnClickListener(v2 -> context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp"))));
|
||||
ll.addView(subscribeButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
v = ll;
|
||||
}
|
||||
|
||||
container.addView(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
container.removeView((View) object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return view == object;
|
||||
}
|
||||
});
|
||||
BeamButton btn = new BeamButton(context);
|
||||
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
if (position == pager.getAdapter().getCount() - 1) {
|
||||
btn.setText(R.string.ChangelogOK);
|
||||
} else {
|
||||
btn.setText(R.string.ChangelogNext);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] colors = new int[2];
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
float pr = position == 0 ? positionOffset : 1f;
|
||||
colors[0] = ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.dialogBackground), ThemesRepo.getColor(R.attr.boostyColorTop), pr);
|
||||
colors[1] = ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.dialogBackground), ThemesRepo.getColor(R.attr.boostyColorBottom), pr);
|
||||
gd.setColors(colors);
|
||||
titleA.setAlpha(1f - pr);
|
||||
titleA.setTranslationX(-titleA.getWidth() * 0.25f * pr);
|
||||
titleB.setAlpha(pr);
|
||||
titleB.setTranslationX(titleB.getWidth() * 0.25f * (1f - pr));
|
||||
btn.setColor(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.colorAccent), ThemesRepo.getColor(R.attr.boostyColorTop), pr));
|
||||
}
|
||||
});
|
||||
ll.addView(pager, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (dm.heightPixels * 0.45f)));
|
||||
|
||||
btn.setText(R.string.ChangelogNext);
|
||||
btn.setOnClickListener(v -> {
|
||||
if (pager.getCurrentItem() != pager.getAdapter().getCount() - 1) {
|
||||
pager.setCurrentItem(pager.getCurrentItem() + 1);
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
ll.addView(btn, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(48)) {{
|
||||
leftMargin = topMargin = rightMargin = bottomMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
|
||||
ll.setFitsSystemWindows(true);
|
||||
setContentView(ll);
|
||||
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
setOnDismissListener(dialog -> SliceBeam.EVENT_BUS.unregisterListener(this));
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onDataUpdated(BeamServerDataUpdatedEvent e) {
|
||||
pager.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package ru.ytkab0bp.slicebeam.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.events.SlicingProgressEvent;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class SliceProgressBottomSheet extends BottomSheetDialog {
|
||||
private RecyclerView recyclerView;
|
||||
private LinearProgressIndicator indicator;
|
||||
private List<String> lines = new ArrayList<>();
|
||||
|
||||
public SliceProgressBottomSheet(@NonNull Context context) {
|
||||
super(context);
|
||||
setCancelable(false);
|
||||
|
||||
LinearLayout ll = new LinearLayout(context);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
ll.setBackgroundResource(R.drawable.bottom_sheet_rounded_background);
|
||||
ll.setBackgroundTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.dialogBackground)));
|
||||
ll.setPadding(0, ViewUtils.dp(12), 0, ViewUtils.dp(12));
|
||||
|
||||
TextView title = new TextView(context);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setText(R.string.SliceInProgress);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
title.setGravity(Gravity.CENTER);
|
||||
title.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
ll.addView(title);
|
||||
|
||||
indicator = new LinearProgressIndicator(context);
|
||||
indicator.setIndicatorColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
indicator.setTrackColor(ThemesRepo.getColor(R.attr.dividerColor));
|
||||
indicator.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1)) {{
|
||||
topMargin = ViewUtils.dp(8);
|
||||
bottomMargin = ViewUtils.dp(8);
|
||||
}});
|
||||
ll.addView(indicator);
|
||||
|
||||
recyclerView = new RecyclerView(context);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
|
||||
boolean portrait = dm.widthPixels < dm.heightPixels;
|
||||
recyclerView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (dm.heightPixels * (portrait ? 0.4f : 0.6f))));
|
||||
recyclerView.setAdapter(new RecyclerView.Adapter() {
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
TextView v = new TextView(parent.getContext());
|
||||
v.setPadding(ViewUtils.dp(12), ViewUtils.dp(4), ViewUtils.dp(12), ViewUtils.dp(4));
|
||||
v.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return new RecyclerView.ViewHolder(v) {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
TextView tv = (TextView) holder.itemView;
|
||||
tv.setText(lines.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return lines.size();
|
||||
}
|
||||
});
|
||||
ll.addView(recyclerView);
|
||||
|
||||
ll.setFitsSystemWindows(true);
|
||||
setContentView(ll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onProgressChanged(SlicingProgressEvent e) {
|
||||
if (!e.message.isEmpty()) {
|
||||
int size = lines.size();
|
||||
lines.add(Slic3rLocalization.getString(e.message) + "...");
|
||||
recyclerView.getAdapter().notifyItemInserted(size);
|
||||
recyclerView.smoothScrollToPosition(size);
|
||||
}
|
||||
indicator.setProgressCompat(e.progress, true);
|
||||
|
||||
if (e.progress == 100) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package ru.ytkab0bp.slicebeam.components;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.MirrorView;
|
||||
|
||||
public abstract class UnfoldMenu {
|
||||
protected BedFragment fragment;
|
||||
|
||||
private boolean isVisible;
|
||||
private SpringAnimation spring;
|
||||
private DynamicAnimation.OnAnimationUpdateListener updateListener;
|
||||
private FrameLayout containerLayout;
|
||||
private View innerView;
|
||||
|
||||
private Runnable onDismiss;
|
||||
|
||||
private View dimmView;
|
||||
private FrameLayout rootView;
|
||||
private float progress;
|
||||
|
||||
private float fromTranslationX;
|
||||
private float fromTranslationY;
|
||||
private float toTranslationX;
|
||||
private float toTranslationY;
|
||||
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return (int) ((portrait ? into.getHeight() : into.getWidth()) * 0.6f);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected void onCreate() {}
|
||||
|
||||
@CallSuper
|
||||
protected void onDestroy() {
|
||||
if (onDismiss != null) {
|
||||
onDismiss.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnDismiss(Runnable onDismiss) {
|
||||
this.onDismiss = onDismiss;
|
||||
}
|
||||
|
||||
protected abstract View onCreateView(Context ctx, boolean portrait);
|
||||
|
||||
public void show(View from, BedFragment fragment) {
|
||||
show(from, fragment, fragment.getOverlayLayout());
|
||||
}
|
||||
|
||||
public void show(View from, FrameLayout into) {
|
||||
show(from, null, into);
|
||||
}
|
||||
|
||||
private void show(View from, BedFragment fragment, FrameLayout into) {
|
||||
if (isVisible) return;
|
||||
this.fragment = fragment;
|
||||
this.isVisible = true;
|
||||
this.containerLayout = into;
|
||||
|
||||
boolean portrait = into.getWidth() < into.getHeight();
|
||||
|
||||
Context ctx = into.getContext();
|
||||
MirrorView mirror = new MirrorView(ctx);
|
||||
mirror.setMirroredView(from);
|
||||
mirror.setLayoutParams(new FrameLayout.LayoutParams(from.getWidth(), from.getHeight()));
|
||||
|
||||
int[] pos = new int[2];
|
||||
from.getLocationInWindow(pos);
|
||||
int[] intoPos = new int[2];
|
||||
into.getLocationInWindow(intoPos);
|
||||
intoPos[0] += into.getPaddingLeft();
|
||||
intoPos[1] += into.getPaddingTop();
|
||||
|
||||
int side = getRequestedSize(into, portrait) + ((View) into.getParent().getParent()).getPaddingBottom();
|
||||
fromTranslationX = pos[0] - intoPos[0];
|
||||
fromTranslationY = pos[1] - intoPos[1];
|
||||
toTranslationX = 0;
|
||||
toTranslationY = portrait ? into.getHeight() - side : 0;
|
||||
rootView = new FrameLayout(ctx) {
|
||||
{
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
private Path path = new Path();
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
canvas.save();
|
||||
path.rewind();
|
||||
float rad = ViewUtils.dp(16) * (1f - progress);
|
||||
path.addRoundRect(0, 0,
|
||||
ViewUtils.lerp(mirror.getWidth(), getWidth(), progress), ViewUtils.lerp(mirror.getHeight(), getHeight(), progress),
|
||||
rad, rad, Path.Direction.CW);
|
||||
canvas.clipPath(path);
|
||||
canvas.drawColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
super.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (isVisible) {
|
||||
onDestroy();
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
rootView.addView(mirror);
|
||||
rootView.addView(innerView = onCreateView(ctx, portrait));
|
||||
innerView.setAlpha(0f);
|
||||
|
||||
rootView.setTranslationX(fromTranslationX);
|
||||
rootView.setTranslationY(fromTranslationY);
|
||||
|
||||
onCreate();
|
||||
dimmView = new View(ctx);
|
||||
dimmView.setBackgroundColor(0x40000000);
|
||||
dimmView.setTranslationX(toTranslationX);
|
||||
dimmView.setTranslationY(toTranslationY);
|
||||
dimmView.setAlpha(0f);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(portrait ? ViewGroup.LayoutParams.MATCH_PARENT : side, portrait ? side : ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
into.addView(dimmView, params);
|
||||
|
||||
into.addView(rootView, params);
|
||||
|
||||
float invY = into.getHeight() - ViewUtils.dp(80 * 2) - toTranslationY;
|
||||
spring = new SpringAnimation(new FloatValueHolder(0))
|
||||
.setMinimumVisibleChange(1 / 256f)
|
||||
.setSpring(new SpringForce(1f)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener(updateListener = (animation, value, velocity) -> {
|
||||
this.progress = value;
|
||||
rootView.invalidate();
|
||||
|
||||
dimmView.setAlpha(value);
|
||||
|
||||
rootView.setTranslationX(ViewUtils.lerp(fromTranslationX, toTranslationX, value));
|
||||
rootView.setTranslationY(ViewUtils.lerp(fromTranslationY, toTranslationY, value));
|
||||
|
||||
float mirrorValue = Math.min(0.75f, value) / 0.75f;
|
||||
mirror.setAlpha((1f - mirrorValue) * mirror.getMirroredView().getAlpha());
|
||||
mirror.setScaleX(1f + mirrorValue);
|
||||
mirror.setScaleY(1f + mirrorValue);
|
||||
mirror.setTranslationX((rootView.getWidth() - mirror.getWidth()) / 2f * mirrorValue);
|
||||
mirror.setTranslationY((rootView.getHeight() - mirror.getHeight()) / 2f * mirrorValue);
|
||||
|
||||
innerView.setTranslationX((mirror.getWidth() - innerView.getWidth()) * (1f - value));
|
||||
innerView.setTranslationY((mirror.getHeight() - innerView.getHeight()) * (1f - value));
|
||||
innerView.setPivotX(innerView.getWidth() / 2f);
|
||||
innerView.setPivotY(innerView.getHeight() / 2f);
|
||||
innerView.setScaleX(0.5f + value * 0.5f);
|
||||
innerView.setScaleY(0.5f + value * 0.5f);
|
||||
innerView.setAlpha(value);
|
||||
|
||||
if (fragment != null) {
|
||||
if (!portrait) {
|
||||
float tX = rootView.getWidth() - ViewUtils.dp(80 * 2);
|
||||
fragment.getGlView().setTranslationX(tX / 2f * value);
|
||||
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) fragment.getSnackbarsLayout().getLayoutParams();
|
||||
marginParams.leftMargin = (int) (ViewUtils.dp(80 * 2) + tX * value);
|
||||
fragment.getSnackbarsLayout().requestLayout();
|
||||
dimmView.setTranslationX(rootView.getTranslationX() - ViewUtils.lerp(rootView.getWidth() - mirror.getWidth(), 0, progress));
|
||||
} else {
|
||||
fragment.getGlView().setTranslationY(-invY / 2 * value);
|
||||
fragment.getSnackbarsLayout().setTranslationY(-invY * value);
|
||||
dimmView.setTranslationY(rootView.getTranslationY());
|
||||
}
|
||||
fragment.getGlView().invalidate();
|
||||
}
|
||||
});
|
||||
spring.start();
|
||||
}
|
||||
|
||||
public void relayout() {
|
||||
FrameLayout into = containerLayout;
|
||||
boolean portrait = into.getWidth() < into.getHeight();
|
||||
int side = rootView.getHeight();
|
||||
toTranslationY = portrait ? into.getHeight() - side : 0;
|
||||
|
||||
updateListener.onAnimationUpdate(spring, 1f, 0f);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
dismiss(false);
|
||||
}
|
||||
|
||||
public void dismiss(boolean alphaOnly) {
|
||||
if (!isVisible) return;
|
||||
this.isVisible = false;
|
||||
|
||||
onDestroy();
|
||||
|
||||
if (alphaOnly) {
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(150);
|
||||
anim.setInterpolator(ViewUtils.CUBIC_INTERPOLATOR);
|
||||
anim.addUpdateListener(animation -> {
|
||||
float val = (float) animation.getAnimatedValue();
|
||||
rootView.setAlpha(1f - val);
|
||||
dimmView.setAlpha(1f - val);
|
||||
|
||||
rootView.setTranslationY(val * ViewUtils.dp(64));
|
||||
dimmView.setTranslationY(val * ViewUtils.dp(64));
|
||||
});
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
containerLayout.removeView(dimmView);
|
||||
containerLayout.removeView(rootView);
|
||||
}
|
||||
});
|
||||
anim.start();
|
||||
} else {
|
||||
spring.getSpring().setFinalPosition(0f);
|
||||
spring.addEndListener((animation, canceled, value, velocity) -> {
|
||||
containerLayout.removeView(dimmView);
|
||||
containerLayout.removeView(rootView);
|
||||
});
|
||||
spring.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package ru.ytkab0bp.slicebeam.components;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.events.NeedDismissCalibrationsMenu;
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
|
||||
public class WebViewMenu extends UnfoldMenu {
|
||||
private final Uri uri;
|
||||
private String javascript;
|
||||
private BedFragment fragment;
|
||||
|
||||
private FileOutputStream fileStream;
|
||||
private File cacheFile;
|
||||
|
||||
public WebViewMenu(Uri uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public WebViewMenu(Uri uri, String javascript) {
|
||||
this(uri);
|
||||
this.javascript = javascript;
|
||||
}
|
||||
|
||||
public WebViewMenu setFragment(BedFragment fragment) {
|
||||
this.fragment = fragment;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected View onCreateView(Context ctx, boolean portrait) {
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
LinearLayout toolbar = new LinearLayout(ctx);
|
||||
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||
toolbar.setOnClickListener(v -> dismiss());
|
||||
|
||||
ImageView icon = new ImageView(ctx);
|
||||
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||
|
||||
TextView title = new TextView(ctx);
|
||||
title.setText(R.string.MenuOrientationPositionBack);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
|
||||
WebView webView = new WebView(ctx) {
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
if (fileStream != null) {
|
||||
return true;
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
};
|
||||
webView.addJavascriptInterface(new Bridge(), "SliceBeam");
|
||||
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setUserAgentString(String.format(Locale.ROOT, "SliceBeam/%s-%d", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setDatabaseEnabled(true);
|
||||
settings.setGeolocationEnabled(false);
|
||||
|
||||
webView.loadUrl(uri.toString());
|
||||
webView.setAlpha(0f);
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
|
||||
if (javascript != null) {
|
||||
webView.evaluateJavascript(javascript, value -> ViewUtils.postOnMainThread(() -> webView.animate().alpha(1f).start()));
|
||||
} else {
|
||||
webView.animate().alpha(1f).start();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ll.addView(webView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
return ll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return portrait ? into.getHeight() : into.getWidth();
|
||||
}
|
||||
|
||||
private final class Bridge {
|
||||
|
||||
@JavascriptInterface
|
||||
public void beginDownload(String filename) {
|
||||
cacheFile = new File(SliceBeam.getModelCacheDir(), filename);
|
||||
try {
|
||||
fileStream = new FileOutputStream(cacheFile);
|
||||
} catch (Exception e) {
|
||||
Log.e("WebViewMenu", "Failed to begin download", e);
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void writeData(String data) {
|
||||
try {
|
||||
fileStream.write(Base64.decode(data, 0));
|
||||
} catch (Exception e) {
|
||||
Log.e("WebViewMenu", "Failed to write to stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void finishDownload() {
|
||||
try {
|
||||
fileStream.close();
|
||||
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
dismiss(true);
|
||||
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissCalibrationsMenu());
|
||||
ViewUtils.postOnMainThread(() -> fragment.loadGCode(cacheFile), 200);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e("WebViewMenu", "Failed to finish file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Bed3D;
|
||||
|
||||
public abstract class BedMenu {
|
||||
private View view;
|
||||
|
||||
public abstract View onCreateView(Context ctx, boolean portrait);
|
||||
|
||||
@CallSuper
|
||||
public void onViewCreated(View v) {
|
||||
view = v;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void onViewDestroyed() {
|
||||
view = null;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
public void onSetBed(BedFragment fragment) {}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolderView> {
|
||||
public final int titleRes;
|
||||
public final int iconRes;
|
||||
public boolean isSingleLine;
|
||||
|
||||
public boolean isEnabled = true;
|
||||
public boolean isChecked = false;
|
||||
public boolean isCheckable = false;
|
||||
public View.OnClickListener clickListener;
|
||||
public CompoundButton.OnCheckedChangeListener checkedChangeListener;
|
||||
|
||||
public BedMenuItem(int titleRes, int iconRes) {
|
||||
this.titleRes = titleRes;
|
||||
this.iconRes = iconRes;
|
||||
}
|
||||
|
||||
public BedMenuItem onClick(View.OnClickListener listener) {
|
||||
clickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedMenuItem setCheckable(CompoundButton.OnCheckedChangeListener checkedChangeListener, boolean checked) {
|
||||
this.checkedChangeListener = checkedChangeListener;
|
||||
isCheckable = true;
|
||||
isChecked = checked;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedMenuItem setEnabled(boolean enabled) {
|
||||
isEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedMenuItem setSingleLine(boolean singleLine) {
|
||||
isSingleLine = singleLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BedMenuItemHolderView onCreateView(Context ctx) {
|
||||
return new BedMenuItemHolderView(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(BedMenuItemHolderView view) {
|
||||
view.bind(this);
|
||||
}
|
||||
|
||||
public final static class BedMenuItemHolderView extends LinearLayout implements IThemeView {
|
||||
private ImageView icon;
|
||||
private TextView title;
|
||||
|
||||
private Paint accentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private Path path = new Path();
|
||||
private float checkedProgress;
|
||||
|
||||
public BedMenuItemHolderView(Context context) {
|
||||
super(context);
|
||||
setOrientation(VERTICAL);
|
||||
setGravity(Gravity.CENTER);
|
||||
|
||||
icon = new ImageView(context);
|
||||
addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(24), ViewUtils.dp(24)));
|
||||
|
||||
title = new TextView(context);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 11);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setGravity(Gravity.CENTER);
|
||||
title.setMaxLines(2);
|
||||
title.setEllipsize(TextUtils.TruncateAt.END);
|
||||
addView(title, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
topMargin = ViewUtils.dp(2);
|
||||
}});
|
||||
|
||||
setPadding(ViewUtils.dp(8), ViewUtils.dp(6), ViewUtils.dp(8), ViewUtils.dp(6));
|
||||
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) {{
|
||||
leftMargin = topMargin = bottomMargin = ViewUtils.dp(6);
|
||||
}});
|
||||
setWillNotDraw(false);
|
||||
onApplyTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
int rad = ViewUtils.dp(16);
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), rad, rad, bgPaint);
|
||||
|
||||
if (checkedProgress != 0f) {
|
||||
if (checkedProgress == 1f) {
|
||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), rad, rad, accentPaint);
|
||||
} else {
|
||||
path.rewind();
|
||||
path.addRoundRect(0, 0, getWidth(), getHeight(), rad, rad, Path.Direction.CW);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipPath(path);
|
||||
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, (float) (Math.sqrt(getWidth() * getWidth() + getHeight() * getHeight()) / 2f * checkedProgress), accentPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.getMode(heightMeasureSpec)), heightMeasureSpec);
|
||||
}
|
||||
|
||||
public void bind(BedMenuItem item) {
|
||||
title.setMaxLines(item.isSingleLine ? 1 : 2);
|
||||
title.setText(item.titleRes);
|
||||
icon.setImageResource(item.iconRes);
|
||||
checkedProgress = item.isCheckable && item.isChecked ? 1 : 0;
|
||||
title.setTextColor(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorPrimary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress));
|
||||
icon.setImageTintList(ColorStateList.valueOf(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorSecondary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress)));
|
||||
|
||||
if (item.checkedChangeListener != null) {
|
||||
setOnClickListener(v -> {
|
||||
item.isChecked = !item.isChecked;
|
||||
new SpringAnimation(new FloatValueHolder(item.isChecked ? 0 : 1))
|
||||
.setMinimumVisibleChange(1 / 256f)
|
||||
.setSpring(new SpringForce(item.isChecked ? 1 : 0)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener((animation, value, velocity) -> {
|
||||
checkedProgress = value;
|
||||
title.setTextColor(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorPrimary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress));
|
||||
icon.setImageTintList(ColorStateList.valueOf(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorSecondary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress)));
|
||||
|
||||
invalidate();
|
||||
})
|
||||
.start();
|
||||
|
||||
item.checkedChangeListener.onCheckedChanged(null, item.isChecked);
|
||||
});
|
||||
} else {
|
||||
setOnClickListener(item.clickListener);
|
||||
}
|
||||
setClickable(item.isEnabled);
|
||||
setAlpha(item.isEnabled ? 1f : 0.6f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||
bgPaint.setColor(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x10));
|
||||
accentPaint.setColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||
import ru.ytkab0bp.slicebeam.render.Camera;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Bed3D;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.Vec3d;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.GLView;
|
||||
|
||||
public class CameraMenu extends ListBedMenu {
|
||||
private boolean checkInvalidBed() {
|
||||
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
||||
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
||||
return Arrays.asList(
|
||||
new BedMenuItem(R.string.MenuCameraIsometric, R.drawable.camera_mode_0_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center.x - center.z * 2, center.y - center.z * 2, min.z + Math.sqrt(center.z * center.z * 8));
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new SpaceItem(portrait ? ViewUtils.dp(8) : 0, portrait ? 0 : ViewUtils.dp(8)),
|
||||
new BedMenuItem(R.string.MenuCameraTop, R.drawable.camera_mode_1_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center);
|
||||
toPosition.z = max.z + (max.z - min.z);
|
||||
toPosition.y -= 1f;
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuCameraBottom, R.drawable.camera_mode_2_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center);
|
||||
toPosition.z = min.z - (max.z - min.z);
|
||||
toPosition.y -= 1f;
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuCameraFront, R.drawable.camera_mode_3_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center);
|
||||
toPosition.y = min.y - (max.y - min.y);
|
||||
toPosition.z = 0;
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuCameraBack, R.drawable.camera_mode_4_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center);
|
||||
toPosition.y = max.y + (max.y - min.y);
|
||||
toPosition.z = 0;
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuCameraLeft, R.drawable.camera_mode_5_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center);
|
||||
toPosition.x = min.x - (max.x - min.x);
|
||||
toPosition.z = 0;
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuCameraRight, R.drawable.camera_mode_6_28).onClick(v -> {
|
||||
if (checkInvalidBed()) return;
|
||||
GLView glView = fragment.getGlView();
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
|
||||
Vec3d min = bed.getVolumeMin(), max = bed.getVolumeMax();
|
||||
Vec3d center = min.center(max);
|
||||
Vec3d toOrigin = new Vec3d(center).multiply(1, 1, 0);
|
||||
Vec3d toPosition = new Vec3d(center);
|
||||
toPosition.x = max.x + (max.x - min.x);
|
||||
toPosition.z = 0;
|
||||
animateTo(toOrigin, toPosition);
|
||||
}),
|
||||
new SpaceItem(portrait ? ViewUtils.dp(8) : 0, portrait ? 0 : ViewUtils.dp(8)),
|
||||
new BedMenuItem(R.string.MenuCameraEnableRotation, R.drawable.sync_outline_28).setCheckable((buttonView, isChecked) -> Prefs.setRotationEnabled(isChecked), Prefs.isRotationEnabled()),
|
||||
new BedMenuItem(R.string.MenuCameraOrtho, R.drawable.image_format_32).setCheckable((buttonView, isChecked) -> {
|
||||
Prefs.setOrthoProjectionEnabled(isChecked);
|
||||
fragment.getGlView().getRenderer().updateProjection();
|
||||
fragment.getGlView().requestRender();
|
||||
}, Prefs.isOrthoProjectionEnabled()));
|
||||
}
|
||||
|
||||
private void animateTo(Vec3d toOrigin, Vec3d toPosition) {
|
||||
animateTo(toOrigin, null, toPosition);
|
||||
}
|
||||
|
||||
private void animateTo(Vec3d toOrigin, Vec3d middlePoint, Vec3d toPosition) {
|
||||
GLView glView = fragment.getGlView();
|
||||
Camera camera = glView.getRenderer().getCamera();
|
||||
|
||||
Vec3d fromOrigin = new Vec3d(camera.origin);
|
||||
Vec3d fromPosition = new Vec3d(camera.position);
|
||||
if (middlePoint == null) {
|
||||
middlePoint = fromPosition.center(toPosition);
|
||||
}
|
||||
|
||||
float zoom = camera.getZoom();
|
||||
Vec3d finalMiddlePoint = middlePoint;
|
||||
new SpringAnimation(new FloatValueHolder(0))
|
||||
.setMinimumVisibleChange(1 / 1000f)
|
||||
.setSpring(new SpringForce(1f)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener((animation, value, velocity) -> {
|
||||
camera.setZoom(ViewUtils.lerp(zoom, 1f, value));
|
||||
camera.position.set(
|
||||
ViewUtils.lerpd(fromPosition.x, Math.abs(toPosition.x - toOrigin.x) <= 5 ? finalMiddlePoint.x : fromPosition.x + (toPosition.x - fromPosition.x) / 2, toPosition.x, value),
|
||||
ViewUtils.lerpd(fromPosition.y, Math.abs(toPosition.y - toOrigin.y) <= 5 ? finalMiddlePoint.y : fromPosition.y + (toPosition.y - fromPosition.y) / 2, toPosition.y, value),
|
||||
ViewUtils.lerpd(fromPosition.z, Math.abs(toPosition.z - toOrigin.z) <= 5 ? finalMiddlePoint.z : fromPosition.z + (toPosition.z - fromPosition.z) / 2, toPosition.z, value)
|
||||
);
|
||||
camera.origin.set(
|
||||
ViewUtils.lerpd(fromOrigin.x, toOrigin.x, value),
|
||||
ViewUtils.lerpd(fromOrigin.y, toOrigin.y, value),
|
||||
ViewUtils.lerpd(fromOrigin.z, toOrigin.z, value)
|
||||
);
|
||||
glView.getRenderer().updateProjection();
|
||||
glView.requestRender();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.MainActivity;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.WebViewMenu;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.events.NeedDismissCalibrationsMenu;
|
||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerAdapter;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Bed3D;
|
||||
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
||||
|
||||
public class FileMenu extends ListBedMenu {
|
||||
private final static List<String> K3D_SUPPORTED_LANGUAGES = Arrays.asList("en", "ru");
|
||||
|
||||
private String getK3DLanguage() {
|
||||
String lang = Locale.getDefault().getLanguage();
|
||||
return K3D_SUPPORTED_LANGUAGES.contains(lang) ? lang : "en";
|
||||
}
|
||||
|
||||
static String escapeStringForJs(String s) {
|
||||
if (s == null) return s;
|
||||
return s.replace("\\", "\\\\")
|
||||
.replace("\t", "\\t")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\b", "\\b")
|
||||
.replace("\f", "\\f")
|
||||
.replace("\r", "\\r")
|
||||
.replace("'", "\\'")
|
||||
.replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
private boolean hasSelection() {
|
||||
return fragment.getGlView().getRenderer().getModel() != null && fragment.getGlView().getRenderer().getSelectedObject() != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
||||
return Arrays.asList(
|
||||
new BedMenuItem(R.string.MenuFileOpen, R.drawable.folder_simple_plus_outline_28).onClick(v -> {
|
||||
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
||||
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragment.getContext() instanceof Activity) {
|
||||
Activity act = (Activity) fragment.getContext();
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
act.startActivityForResult(i, MainActivity.REQUEST_CODE_OPEN_FILE);
|
||||
}
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuFileDelete, R.drawable.delete_outline_android_28).setEnabled(hasSelection()).onClick(v -> {
|
||||
if (fragment.getGlView().getRenderer().getModel() == null) return;
|
||||
|
||||
if (fragment.getGlView().getRenderer().deleteObject(fragment.getGlView().getRenderer().getSelectedObject())) {
|
||||
fragment.getGlView().requestRender();
|
||||
fragment.updateModel();
|
||||
}
|
||||
}),
|
||||
new SpaceItem(portrait ? ViewUtils.dp(3) : 0, portrait ? 0 : ViewUtils.dp(3)),
|
||||
new BedMenuItem(R.string.MenuFileCalibrations, R.drawable.wrench_outline_28).setSingleLine(true).onClick(v -> {
|
||||
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
||||
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
fragment.showUnfoldMenu(new CalibrationsMenu(), v);
|
||||
}),
|
||||
new SpaceItem(portrait ? ViewUtils.dp(3) : 0, portrait ? 0 : ViewUtils.dp(3)),
|
||||
new BedMenuItem(R.string.MenuFileImportProfiles, R.drawable.folder_simple_arrow_up_outline_28).onClick(v -> {
|
||||
if (fragment.getContext() instanceof Activity) {
|
||||
Activity act = (Activity) fragment.getContext();
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
act.startActivityForResult(i, MainActivity.REQUEST_CODE_IMPORT_PROFILES);
|
||||
}
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuFileExportProfiles, R.drawable.folder_simple_arrow_right_outline_28).onClick(v -> {
|
||||
CharSequence[] prints = new CharSequence[SliceBeam.CONFIG.printConfigs.size()];
|
||||
boolean[] enabledPrints = new boolean[prints.length];
|
||||
for (int i = 0; i < prints.length; i++) {
|
||||
prints[i] = SliceBeam.CONFIG.printConfigs.get(i).getTitle();
|
||||
enabledPrints[i] = true;
|
||||
}
|
||||
|
||||
CharSequence[] filaments = new CharSequence[SliceBeam.CONFIG.filamentConfigs.size()];
|
||||
boolean[] enabledFilaments = new boolean[filaments.length];
|
||||
for (int i = 0; i < filaments.length; i++) {
|
||||
filaments[i] = SliceBeam.CONFIG.filamentConfigs.get(i).getTitle();
|
||||
enabledFilaments[i] = true;
|
||||
}
|
||||
|
||||
CharSequence[] printers = new CharSequence[SliceBeam.CONFIG.printerConfigs.size()];
|
||||
boolean[] enabledPrinters = new boolean[printers.length];
|
||||
for (int i = 0; i < printers.length; i++) {
|
||||
printers[i] = SliceBeam.CONFIG.printerConfigs.get(i).getTitle();
|
||||
enabledPrinters[i] = true;
|
||||
}
|
||||
|
||||
new BeamAlertDialogBuilder(v.getContext())
|
||||
.setTitle(R.string.MenuFileExportProfilesPrints)
|
||||
.setMultiChoiceItems(prints, enabledPrints, (dialog, which, isChecked) -> enabledPrints[which] = isChecked)
|
||||
.setPositiveButton(android.R.string.ok, (d1, w1) -> new BeamAlertDialogBuilder(v.getContext())
|
||||
.setTitle(R.string.MenuFileExportProfilesFilaments)
|
||||
.setMultiChoiceItems(filaments, enabledFilaments, (dialog, which, isChecked) -> enabledFilaments[which] = isChecked)
|
||||
.setPositiveButton(android.R.string.ok, (d2, w2) -> new BeamAlertDialogBuilder(v.getContext())
|
||||
.setTitle(R.string.MenuFileExportProfilesPrinters)
|
||||
.setMultiChoiceItems(printers, enabledPrinters, (dialog, which, isChecked) -> enabledPrinters[which] = isChecked)
|
||||
.setPositiveButton(android.R.string.ok, (d3, w3) -> {
|
||||
boolean hasEnabled = false;
|
||||
MainActivity.EXPORTING_PRINTS = new ArrayList<>();
|
||||
for (int i = 0; i < enabledPrints.length; i++) {
|
||||
if (enabledPrints[i]) {
|
||||
hasEnabled = true;
|
||||
MainActivity.EXPORTING_PRINTS.add(SliceBeam.CONFIG.printConfigs.get(i));
|
||||
}
|
||||
}
|
||||
MainActivity.EXPORTING_FILAMENTS = new ArrayList<>();
|
||||
for (int i = 0; i < enabledFilaments.length; i++) {
|
||||
if (enabledFilaments[i]) {
|
||||
hasEnabled = true;
|
||||
MainActivity.EXPORTING_FILAMENTS.add(SliceBeam.CONFIG.filamentConfigs.get(i));
|
||||
}
|
||||
}
|
||||
MainActivity.EXPORTING_PRINTERS = new ArrayList<>();
|
||||
for (int i = 0; i < enabledPrinters.length; i++) {
|
||||
if (enabledPrinters[i]) {
|
||||
hasEnabled = true;
|
||||
MainActivity.EXPORTING_PRINTERS.add(SliceBeam.CONFIG.printerConfigs.get(i));
|
||||
}
|
||||
}
|
||||
if (!hasEnabled) {
|
||||
new BeamAlertDialogBuilder(v.getContext())
|
||||
.setTitle(R.string.MenuFileExportProfiles)
|
||||
.setMessage(R.string.MenuFileExportProfilesNoProfiles)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragment.getContext() instanceof Activity) {
|
||||
Activity act = (Activity) fragment.getContext();
|
||||
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
i.setType("application/ini");
|
||||
i.putExtra(Intent.EXTRA_TITLE, "SliceBeam_config_bundle.ini");
|
||||
act.startActivityForResult(i, MainActivity.REQUEST_CODE_EXPORT_PROFILES);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onObjectsChanged(ObjectsListChangedEvent e) {
|
||||
((BedMenuItem) adapter.getItems().get(1)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(1);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onSelectionChanged(SelectedObjectChangedEvent e) {
|
||||
((BedMenuItem) adapter.getItems().get(1)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(1);
|
||||
}
|
||||
|
||||
public final class CalibrationsMenu extends UnfoldMenu {
|
||||
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return (int) (portrait ? into.getHeight() * 0.3f : into.getWidth() * 0.6f);
|
||||
}
|
||||
|
||||
private String loadJSLoader(String key) {
|
||||
try {
|
||||
InputStream in = SliceBeam.INSTANCE.getAssets().open("js_loader/" + key + ".js");
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[10240]; int c;
|
||||
while ((c = in.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, c);
|
||||
}
|
||||
bos.close();
|
||||
in.close();
|
||||
|
||||
ConfigObject cfg = SliceBeam.buildCurrentConfigObject();
|
||||
Bed3D bed = FileMenu.this.fragment.getGlView().getRenderer().getBed();
|
||||
double bedX = bed.getVolumeMax().x - bed.getVolumeMin().x;
|
||||
double bedY = bed.getVolumeMax().y - bed.getVolumeMin().y;
|
||||
|
||||
String str = new String(bos.toByteArray(), StandardCharsets.UTF_8);
|
||||
StringBuilder sb = new StringBuilder(str);
|
||||
Pattern placeholderPattern = Pattern.compile("\\$\\['(\\w+?)(\\[\\d+]|)']");
|
||||
Matcher m = placeholderPattern.matcher(str);
|
||||
int offset = 0;
|
||||
while (m.find()) {
|
||||
String pKey = m.group(1);
|
||||
String pIndex = m.group(2);
|
||||
int index = pIndex.isEmpty() ? -1 : Integer.parseInt(pIndex.substring(1, pIndex.length() - 1));
|
||||
|
||||
String v;
|
||||
boolean quote = false;
|
||||
switch (pKey) {
|
||||
case "bed_x":
|
||||
v = String.format(Locale.ROOT, "%.1f", bedX);
|
||||
quote = true;
|
||||
break;
|
||||
case "bed_y":
|
||||
v = String.format(Locale.ROOT, "%.1f", bedY);
|
||||
quote = true;
|
||||
break;
|
||||
case "color_accent":
|
||||
v = String.format(Locale.ROOT, "#%06X", ThemesRepo.getColor(android.R.attr.colorAccent) & 0xFFFFFF);
|
||||
break;
|
||||
case "window_background_dark":
|
||||
v = String.format(Locale.ROOT, "#%06X", BeamTheme.DARK.colors.get(android.R.attr.windowBackground) & 0xFFFFFF);
|
||||
break;
|
||||
case "window_background_light":
|
||||
v = String.format(Locale.ROOT, "#%06X", BeamTheme.LIGHT.colors.get(android.R.attr.windowBackground) & 0xFFFFFF);
|
||||
break;
|
||||
case "is_dark_theme":
|
||||
v = String.valueOf(ColorUtils.calculateLuminance(ThemesRepo.getColor(android.R.attr.windowBackground)) >= 0.9f);
|
||||
break;
|
||||
default:
|
||||
v = cfg.get(pKey);
|
||||
quote = true;
|
||||
break;
|
||||
}
|
||||
if (v != null && index != -1) {
|
||||
try {
|
||||
v = v.split(",")[index];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
v = "";
|
||||
}
|
||||
}
|
||||
String newVal = escapeStringForJs(v);
|
||||
if (quote) {
|
||||
newVal = "'" + newVal + "'";
|
||||
}
|
||||
sb = sb.replace(m.start() + offset, m.end() + offset, newVal);
|
||||
offset += newVal.length() - (m.end() - m.start());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(Context ctx, boolean portrait) {
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
RecyclerView rv = new FadeRecyclerView(ctx);
|
||||
SimpleRecyclerAdapter adapter = new SimpleRecyclerAdapter();
|
||||
adapter.setItems(Arrays.asList(
|
||||
new PreferenceItem().setIcon(R.drawable.menu_calibrate_la_28).setTitle(ctx.getString(R.string.MenuFileCalibrationsLA)).setSubtitle(ctx.getString(R.string.MenuFileCalibrationsLADescription)).setOnClickListener(v -> {
|
||||
if (ctx instanceof MainActivity) {
|
||||
((MainActivity) ctx).showUnfoldMenu(new WebViewMenu(Uri.parse("https://k3d.tech/calibrations/la/calibrator/").buildUpon().appendQueryParameter("lang", getK3DLanguage()).build(), loadJSLoader("k3d_la")).setFragment(fragment), v);
|
||||
}
|
||||
}),
|
||||
new PreferenceItem().setIcon(R.drawable.menu_calibrate_retract_28).setTitle(ctx.getString(R.string.MenuFileCalibrationsRetract)).setSubtitle(ctx.getString(R.string.MenuFileCalibrationsRetractDescription)).setOnClickListener(v -> {
|
||||
if (ctx instanceof MainActivity) {
|
||||
((MainActivity) ctx).showUnfoldMenu(new WebViewMenu(Uri.parse("https://k3d.tech/calibrations/retractions/calibrator/").buildUpon().appendQueryParameter("lang", getK3DLanguage()).build(), loadJSLoader("k3d_rct")).setFragment(fragment), v);
|
||||
}
|
||||
})
|
||||
));
|
||||
rv.setAdapter(adapter);
|
||||
ll.addView(rv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
|
||||
LinearLayout toolbar = new LinearLayout(ctx);
|
||||
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||
toolbar.setOnClickListener(v -> dismiss());
|
||||
|
||||
ImageView icon = new ImageView(ctx);
|
||||
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||
|
||||
TextView title = new TextView(ctx);
|
||||
title.setText(R.string.MenuOrientationPositionBack);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||
return ll;
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onDismiss(NeedDismissCalibrationsMenu e) {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerAdapter;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public abstract class ListBedMenu extends BedMenu {
|
||||
protected BedFragment fragment;
|
||||
protected RecyclerView recyclerView;
|
||||
protected SimpleRecyclerAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onSetBed(BedFragment fragment) {
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(Context ctx, boolean portrait) {
|
||||
recyclerView = new RecyclerView(ctx);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(ctx, portrait ? RecyclerView.HORIZONTAL : RecyclerView.VERTICAL, false));
|
||||
recyclerView.setItemAnimator(null);
|
||||
adapter = new SimpleRecyclerAdapter() {
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
RecyclerView.ViewHolder vh = super.onCreateViewHolder(parent, viewType);
|
||||
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
|
||||
if (!portrait && params != null) {
|
||||
params.rightMargin = ViewUtils.dp(6);
|
||||
params.bottomMargin = 0;
|
||||
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
return vh;
|
||||
}
|
||||
};
|
||||
adapter.setItems(onCreateItems(portrait));
|
||||
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
|
||||
if (parent.getChildViewHolder(view).getAdapterPosition() == adapter.getItemCount() - 1) {
|
||||
if (portrait) {
|
||||
outRect.right = ViewUtils.dp(6);
|
||||
} else {
|
||||
outRect.bottom = ViewUtils.dp(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
return recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View v) {
|
||||
super.onViewCreated(v);
|
||||
SliceBeam.EVENT_BUS.registerListener(ListBedMenu.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDestroyed() {
|
||||
super.onViewDestroyed();
|
||||
SliceBeam.EVENT_BUS.unregisterListener(ListBedMenu.this);
|
||||
}
|
||||
|
||||
protected abstract List<SimpleRecyclerItem> onCreateItems(boolean portrait);
|
||||
}
|
||||
@@ -0,0 +1,654 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Model;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.Vec3d;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.BeamButton;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
import ru.ytkab0bp.slicebeam.view.PositionScrollView;
|
||||
import ru.ytkab0bp.slicebeam.view.TextColorImageSpan;
|
||||
|
||||
public class OrientationMenu extends ListBedMenu {
|
||||
private boolean hasSelection() {
|
||||
return fragment.getGlView().getRenderer().getModel() != null && fragment.getGlView().getRenderer().getSelectedObject() != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
||||
return Arrays.asList(
|
||||
new BedMenuItem(R.string.MenuOrientationArrange, R.drawable.grid_layout_outline_28).onClick(v -> {
|
||||
fragment.getGlView().arrange();
|
||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuOrientationArrangeFinished));
|
||||
}).setEnabled(fragment.getGlView().getRenderer().getModel() != null),
|
||||
new SpaceItem(portrait ? ViewUtils.dp(8) : 0, portrait ? 0 : ViewUtils.dp(8)),
|
||||
new BedMenuItem(R.string.MenuOrientationPosition, R.drawable.menu_orientation_position_28).setEnabled(hasSelection()).onClick(v -> fragment.showUnfoldMenu(new PositionMenu(), v)),
|
||||
new BedMenuItem(R.string.MenuOrientationRotation, R.drawable.menu_orientation_rotation_28).setEnabled(hasSelection()).onClick(v -> fragment.showUnfoldMenu(new RotationMenu(), v))
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onObjectsChanged(ObjectsListChangedEvent e) {
|
||||
((BedMenuItem) adapter.getItems().get(0)).setEnabled(fragment.getGlView().getRenderer().getModel() != null);
|
||||
adapter.notifyItemChanged(0);
|
||||
|
||||
((BedMenuItem) adapter.getItems().get(2)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(2);
|
||||
((BedMenuItem) adapter.getItems().get(3)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(3);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onSelectionChanged(SelectedObjectChangedEvent e) {
|
||||
((BedMenuItem) adapter.getItems().get(2)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(2);
|
||||
((BedMenuItem) adapter.getItems().get(3)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(3);
|
||||
}
|
||||
|
||||
public final class PositionMenu extends UnfoldMenu {
|
||||
private PositionScrollView xTrack, yTrack, zTrack;
|
||||
private TextView xTitle, yTitle, zTitle;
|
||||
private Vec3d tempVec = new Vec3d();
|
||||
private int startedScrollObject;
|
||||
|
||||
private void translateVisual(Double x, Double y, Double z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
startedScrollObject = j;
|
||||
|
||||
if (x != null) {
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionXValue, x));
|
||||
}
|
||||
if (y != null) {
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionYValue, y));
|
||||
}
|
||||
if (z != null) {
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionZValue, z));
|
||||
}
|
||||
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
model.getTranslation(j, tempVec);
|
||||
|
||||
double dx = 0, dy = 0, dz = 0;
|
||||
if (x != null) dx = x - tempVec.x;
|
||||
if (y != null) dy = y - tempVec.y;
|
||||
if (z != null) dz = z - tempVec.z;
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionTranslation(dx, dy, dz);
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
private void translate(Double x, Double y, Double z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
startedScrollObject = -1;
|
||||
|
||||
fragment.getGlView().queueEvent(() -> {
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
model.getTranslation(j, tempVec);
|
||||
|
||||
double dx = 0, dy = 0, dz = 0;
|
||||
if (x != null) {
|
||||
dx = x - tempVec.x;
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionXValue, x));
|
||||
}
|
||||
if (y != null) {
|
||||
dy = y - tempVec.y;
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionYValue, y));
|
||||
}
|
||||
if (z != null) {
|
||||
dz = z - tempVec.z;
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionZValue, z));
|
||||
}
|
||||
|
||||
model.translate(j, dx, dy, dz);
|
||||
fragment.getGlView().getRenderer().setSelectionTranslation(0, 0, 0);
|
||||
fragment.getGlView().getRenderer().invalidateGlModel(j);
|
||||
fragment.getGlView().requestRender();
|
||||
});
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
private CharSequence formatTrackTitle(int res, double value) {
|
||||
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(SliceBeam.INSTANCE.getString(res, value));
|
||||
sb.append(" d");
|
||||
int size = ViewUtils.dp(14);
|
||||
Drawable dr = ContextCompat.getDrawable(SliceBeam.INSTANCE, R.drawable.edit_outline_28);
|
||||
dr.setTint(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
dr.setBounds(0, 0, size, size);
|
||||
sb.setSpan(new TextColorImageSpan(dr, 0), sb.length() - 1, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private void showManualEditor(int title, boolean x, boolean y, boolean z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
model.getTranslation(j, tempVec);
|
||||
|
||||
double current;
|
||||
if (x) {
|
||||
current = tempVec.x;
|
||||
} else if (y) {
|
||||
current = tempVec.y;
|
||||
} else {
|
||||
current = tempVec.z;
|
||||
}
|
||||
|
||||
Context ctx = getView().getContext();
|
||||
FrameLayout fl = new FrameLayout(ctx);
|
||||
EditText text = new EditText(ctx);
|
||||
text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
text.setText(String.format(Locale.ROOT, "%.2f", current));
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
fl.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(title)
|
||||
.setView(fl)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
double value;
|
||||
try {
|
||||
value = Double.parseDouble(text.getText().toString());
|
||||
} catch (NumberFormatException e) {
|
||||
value = current;
|
||||
}
|
||||
Double dx = null, dy = null, dz = null;
|
||||
if (x) xTrack.setCurrentPosition((dx = value).intValue());
|
||||
if (y) yTrack.setCurrentPosition((dy = value).intValue());
|
||||
if (z) zTrack.setCurrentPosition((dz = value).intValue());
|
||||
|
||||
translate(dx, dy, dz);
|
||||
})
|
||||
.show();
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
text.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(text, 0);
|
||||
text.setSelection(text.getText().length());
|
||||
}, 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(Context ctx, boolean portrait) {
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
ll.setPadding(0, ViewUtils.dp(12), 0, 0);
|
||||
|
||||
xTitle = new TextView(ctx);
|
||||
xTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
xTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
xTitle.setTextColor(ThemesRepo.getColor(R.attr.xTrackColor));
|
||||
xTitle.setGravity(Gravity.CENTER);
|
||||
xTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationPositionX, true, false, false));
|
||||
ll.addView(xTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
xTrack = new PositionScrollView(ctx);
|
||||
xTrack.setActiveColor(R.attr.xTrackColor);
|
||||
xTrack.setProgressListener(integer -> translateVisual(integer.doubleValue(), (double) yTrack.getCurrentPosition(), null));
|
||||
xTrack.setListener(integer -> translate(integer.doubleValue(), (double) yTrack.getCurrentPosition(), null));
|
||||
ll.addView(xTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
yTitle = new TextView(ctx);
|
||||
yTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
yTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
yTitle.setTextColor(ThemesRepo.getColor(R.attr.yTrackColor));
|
||||
yTitle.setGravity(Gravity.CENTER);
|
||||
yTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationPositionY, false, true, false));
|
||||
ll.addView(yTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
yTrack = new PositionScrollView(ctx);
|
||||
yTrack.setActiveColor(R.attr.yTrackColor);
|
||||
yTrack.setProgressListener(integer -> translateVisual((double) xTrack.getCurrentPosition(), integer.doubleValue(), null));
|
||||
yTrack.setListener(integer -> translate((double) xTrack.getCurrentPosition(), integer.doubleValue(), null));
|
||||
ll.addView(yTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
// TODO: Sinking parts are not supported yet, so no reason to show it here
|
||||
// zTitle = new TextView(ctx);
|
||||
// zTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
// zTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
// zTitle.setTextColor(ThemesRepo.getColor(R.attr.zTrackColor));
|
||||
// zTitle.setGravity(Gravity.CENTER);
|
||||
// zTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationPositionZ, false, true, false));
|
||||
// ll.addView(zTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
// bottomMargin = ViewUtils.dp(4);
|
||||
// }});
|
||||
//
|
||||
// zTrack = new PositionScrollView(ctx);
|
||||
// zTrack.setActiveColor(R.attr.zTrackColor);
|
||||
// zTrack.setProgressListener(integer -> translateVisual((double) xTrack.getCurrentPosition(), (double) yTrack.getCurrentPosition(), integer.doubleValue()));
|
||||
// zTrack.setListener(integer -> translate((double) xTrack.getCurrentPosition(), (double) yTrack.getCurrentPosition(), integer.doubleValue()));
|
||||
// ll.addView(zTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
ll.addView(new Space(ctx), new LinearLayout.LayoutParams(0, 0, 1f));
|
||||
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
LinearLayout toolbar = new LinearLayout(ctx);
|
||||
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||
toolbar.setOnClickListener(v -> dismiss());
|
||||
|
||||
ImageView icon = new ImageView(ctx);
|
||||
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||
|
||||
TextView title = new TextView(ctx);
|
||||
title.setText(R.string.MenuOrientationPositionBack);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||
|
||||
return ll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return portrait ? ViewUtils.dp(52) + ViewUtils.dp(80 + 24) * 2 + ViewUtils.dp(12) : (int) (into.getWidth() * 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
setSelectionValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
stopScroll();
|
||||
}
|
||||
|
||||
private void setSelectionValues() {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionTranslation(0, 0, 0);
|
||||
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
model.getTranslation(j, tempVec);
|
||||
|
||||
xTrack.setCurrentPosition((int) tempVec.x);
|
||||
yTrack.setCurrentPosition((int) tempVec.y);
|
||||
// zTrack.setCurrentPosition((int) tempVec.z);
|
||||
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionXValue, tempVec.x));
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionYValue, tempVec.y));
|
||||
// zTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionZValue, tempVec.z));
|
||||
}
|
||||
|
||||
private void stopScroll() {
|
||||
xTrack.stopScroll();
|
||||
yTrack.stopScroll();
|
||||
// zTrack.stopScroll();
|
||||
|
||||
if (startedScrollObject != -1) {
|
||||
fragment.getGlView().getRenderer().setSelectionTranslation(0, 0, 0);
|
||||
}
|
||||
startedScrollObject = -1;
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onSelectedObjectChanged(SelectedObjectChangedEvent e) {
|
||||
stopScroll();
|
||||
|
||||
if (fragment.getGlView().getRenderer().getSelectedObject() == -1) {
|
||||
dismiss();
|
||||
} else {
|
||||
setSelectionValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class RotationMenu extends UnfoldMenu {
|
||||
private PositionScrollView xTrack, yTrack, zTrack;
|
||||
private TextView xTitle, yTitle, zTitle;
|
||||
private Vec3d bbMin = new Vec3d(), bbMax = new Vec3d();
|
||||
private int startedScrollObject;
|
||||
|
||||
private void rotateVisual(Double x, Double y, Double z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
startedScrollObject = j;
|
||||
|
||||
if (x != null) {
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationXValue, x));
|
||||
}
|
||||
if (y != null) {
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationYValue, y));
|
||||
}
|
||||
if (z != null) {
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationZValue, z));
|
||||
}
|
||||
|
||||
double dx = 0, dy = 0, dz = 0;
|
||||
if (x != null) dx = x;
|
||||
if (y != null) dy = y;
|
||||
if (z != null) dz = z;
|
||||
|
||||
dx %= 360;
|
||||
dy %= 360;
|
||||
dz %= 360;
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionRotation(dx, dy, dz);
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
private void rotate(Double x, Double y, Double z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
startedScrollObject = -1;
|
||||
|
||||
fragment.getGlView().queueEvent(() -> {
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
|
||||
double dx = 0, dy = 0, dz = 0;
|
||||
if (x != null) {
|
||||
dx = x;
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationXValue, 0));
|
||||
xTrack.setCurrentPosition(0);
|
||||
}
|
||||
if (y != null) {
|
||||
dy = y;
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationYValue, 0));
|
||||
yTrack.setCurrentPosition(0);
|
||||
}
|
||||
if (z != null) {
|
||||
dz = z;
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationZValue, 0));
|
||||
zTrack.setCurrentPosition(0);
|
||||
}
|
||||
|
||||
dx %= 360;
|
||||
dy %= 360;
|
||||
dz %= 360;
|
||||
|
||||
model.rotate(j, Math.toRadians(dx), Math.toRadians(dy), Math.toRadians(dz));
|
||||
model.getBoundingBoxExact(j, bbMin, bbMax);
|
||||
model.translate(j, 0, 0, -bbMin.z);
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionRotation(0, 0, 0);
|
||||
fragment.getGlView().getRenderer().invalidateGlModel(j);
|
||||
fragment.getGlView().requestRender();
|
||||
});
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
private CharSequence formatTrackTitle(int res, double value) {
|
||||
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(SliceBeam.INSTANCE.getString(res, value));
|
||||
sb.append(" d");
|
||||
int size = ViewUtils.dp(14);
|
||||
Drawable dr = ContextCompat.getDrawable(SliceBeam.INSTANCE, R.drawable.edit_outline_28);
|
||||
dr.setTint(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
dr.setBounds(0, 0, size, size);
|
||||
sb.setSpan(new TextColorImageSpan(dr, 0), sb.length() - 1, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private void showManualEditor(int title, boolean x, boolean y, boolean z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
double current;
|
||||
if (x) {
|
||||
current = xTrack.getCurrentPosition();
|
||||
} else if (y) {
|
||||
current = yTrack.getCurrentPosition();
|
||||
} else {
|
||||
current = zTrack.getCurrentPosition();
|
||||
}
|
||||
|
||||
Context ctx = getView().getContext();
|
||||
FrameLayout fl = new FrameLayout(ctx);
|
||||
EditText text = new EditText(ctx);
|
||||
text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
text.setText(String.format(Locale.ROOT, "%.2f", current));
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
fl.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(title)
|
||||
.setView(fl)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
double value;
|
||||
try {
|
||||
value = Double.parseDouble(text.getText().toString());
|
||||
} catch (NumberFormatException e) {
|
||||
value = current;
|
||||
}
|
||||
Double dx = null, dy = null, dz = null;
|
||||
if (x) xTrack.setCurrentPosition((dx = value).intValue());
|
||||
if (y) yTrack.setCurrentPosition((dy = value).intValue());
|
||||
if (z) zTrack.setCurrentPosition((dz = value).intValue());
|
||||
|
||||
rotateVisual(dx, dy, dz);
|
||||
})
|
||||
.show();
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
text.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(text, 0);
|
||||
text.setSelection(text.getText().length());
|
||||
}, 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(Context ctx, boolean portrait) {
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
ll.setPadding(0, ViewUtils.dp(12), 0, 0);
|
||||
|
||||
xTitle = new TextView(ctx);
|
||||
xTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
xTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
xTitle.setTextColor(ThemesRepo.getColor(R.attr.xTrackColor));
|
||||
xTitle.setGravity(Gravity.CENTER);
|
||||
xTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationRotationX, true, false, false));
|
||||
ll.addView(xTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
xTrack = new PositionScrollView(ctx);
|
||||
xTrack.setActiveColor(R.attr.xTrackColor);
|
||||
xTrack.setProgressListener(integer -> rotateVisual(integer.doubleValue(), (double) yTrack.getCurrentPosition(), (double) zTrack.getCurrentPosition()));
|
||||
ll.addView(xTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
yTitle = new TextView(ctx);
|
||||
yTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
yTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
yTitle.setTextColor(ThemesRepo.getColor(R.attr.yTrackColor));
|
||||
yTitle.setGravity(Gravity.CENTER);
|
||||
yTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationRotationY, false, true, false));
|
||||
ll.addView(yTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
yTrack = new PositionScrollView(ctx);
|
||||
yTrack.setActiveColor(R.attr.yTrackColor);
|
||||
yTrack.setProgressListener(integer -> rotateVisual((double) xTrack.getCurrentPosition(), integer.doubleValue(), (double) zTrack.getCurrentPosition()));
|
||||
ll.addView(yTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
zTitle = new TextView(ctx);
|
||||
zTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
zTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
zTitle.setTextColor(ThemesRepo.getColor(R.attr.zTrackColor));
|
||||
zTitle.setGravity(Gravity.CENTER);
|
||||
zTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationRotationZ, false, false, true));
|
||||
ll.addView(zTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
zTrack = new PositionScrollView(ctx);
|
||||
zTrack.setActiveColor(R.attr.zTrackColor);
|
||||
zTrack.setProgressListener(integer -> rotateVisual((double) xTrack.getCurrentPosition(), (double) yTrack.getCurrentPosition(), integer.doubleValue()));
|
||||
ll.addView(zTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
ll.addView(new Space(ctx), new LinearLayout.LayoutParams(0, 0, 1f));
|
||||
|
||||
BeamButton btn = new BeamButton(ctx);
|
||||
btn.setText(R.string.MenuOrientationRotationApply);
|
||||
btn.setOnClickListener(v -> rotate((double) xTrack.getCurrentPosition(), (double) yTrack.getCurrentPosition(), (double) zTrack.getCurrentPosition()));
|
||||
if (portrait) {
|
||||
ll.addView(btn, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(48)) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||
topMargin = bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
}
|
||||
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
LinearLayout toolbar = new LinearLayout(ctx);
|
||||
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(portrait ? 12 : 4), 0);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||
toolbar.setOnClickListener(v -> dismiss());
|
||||
|
||||
ImageView icon = new ImageView(ctx);
|
||||
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||
|
||||
|
||||
TextView title = new TextView(ctx);
|
||||
title.setText(R.string.MenuOrientationPositionBack);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||
|
||||
if (!portrait) {
|
||||
toolbar.addView(btn, new LinearLayout.LayoutParams(0, ViewUtils.dp(42), 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
}
|
||||
|
||||
return ll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return portrait ? ViewUtils.dp(52) + ViewUtils.dp(56) + ViewUtils.dp(80 + 24) * 3 + ViewUtils.dp(12) : (int) (into.getWidth() * 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
setSelectionValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
stopScroll();
|
||||
}
|
||||
|
||||
private void setSelectionValues() {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionTranslation(0, 0, 0);
|
||||
|
||||
xTrack.setCurrentPosition(0);
|
||||
yTrack.setCurrentPosition(0);
|
||||
zTrack.setCurrentPosition(0);
|
||||
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationXValue, 0));
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationYValue, 0));
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuOrientationRotationZValue, 0));
|
||||
}
|
||||
|
||||
private void stopScroll() {
|
||||
xTrack.stopScroll();
|
||||
yTrack.stopScroll();
|
||||
zTrack.stopScroll();
|
||||
|
||||
if (startedScrollObject != -1) {
|
||||
fragment.getGlView().getRenderer().setSelectionRotation(0, 0, 0);
|
||||
}
|
||||
startedScrollObject = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
super.dismiss();
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionRotation(0, 0, 0);
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onSelectedObjectChanged(SelectedObjectChangedEvent e) {
|
||||
stopScroll();
|
||||
|
||||
if (fragment.getGlView().getRenderer().getSelectedObject() == -1) {
|
||||
dismiss();
|
||||
} else {
|
||||
setSelectionValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.loopj.android.http.AsyncHttpClient;
|
||||
import com.loopj.android.http.AsyncHttpResponseHandler;
|
||||
import com.loopj.android.http.RequestParams;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import cz.msebera.android.httpclient.Header;
|
||||
import cz.msebera.android.httpclient.entity.ContentType;
|
||||
import cz.msebera.android.httpclient.message.BasicHeader;
|
||||
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||
import ru.ytkab0bp.slicebeam.MainActivity;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.GCodeViewer;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
import ru.ytkab0bp.slicebeam.view.PositionScrollView;
|
||||
|
||||
public class SliceMenu extends ListBedMenu {
|
||||
private AsyncHttpClient client = new AsyncHttpClient();
|
||||
|
||||
{
|
||||
client.setLoggingEnabled(true);
|
||||
client.setMaxRetriesAndTimeout(0, 5000);
|
||||
}
|
||||
|
||||
private final static List<String> SUPPORTED_SEND = Collections.singletonList("octoprint");
|
||||
private int lastUid;
|
||||
|
||||
@Override
|
||||
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
||||
lastUid = SliceBeam.CONFIG_UID;
|
||||
List<SimpleRecyclerItem> items = new ArrayList<>(Arrays.asList(
|
||||
new BedMenuItem(R.string.MenuSliceInfo, R.drawable.square_stack_up_outline_28).onClick(v -> fragment.showUnfoldMenu(new PrintInfoMenu(), v)),
|
||||
new BedMenuItem(R.string.MenuSliceExportToFile, R.drawable.folder_simple_arrow_right_outline_28).onClick(v -> {
|
||||
if (fragment.getContext() instanceof Activity) {
|
||||
Activity act = (Activity) fragment.getContext();
|
||||
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
i.setType("application/x-gcode");
|
||||
i.putExtra(Intent.EXTRA_TITLE, fragment.getGlView().getRenderer().getGcodeResult().getRecommendedName());
|
||||
act.startActivityForResult(i, MainActivity.REQUEST_CODE_EXPORT_GCODE);
|
||||
}
|
||||
}),
|
||||
new BedMenuItem(R.string.MenuSliceShare, R.drawable.share_external_28).onClick(v -> {
|
||||
if (fragment.getContext() instanceof Activity) {
|
||||
File f = BedFragment.getTempGCodePath();
|
||||
|
||||
Activity act = (Activity) fragment.getContext();
|
||||
Intent i = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
i.setType("application/x-gcode");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// Simple trick for Samsung to display "1 element" instead of "temp.gcode"
|
||||
// It doesn't actually resolve name from provider and uses path-parsing instead, bruh.
|
||||
i.putParcelableArrayListExtra(Intent.EXTRA_STREAM, new ArrayList<>(Collections.singletonList(FileProvider.getUriForFile(act, BuildConfig.APPLICATION_ID + ".provider", f, BedFragment.getTempFileName()))));
|
||||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} else {
|
||||
i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
|
||||
}
|
||||
act.startActivity(Intent.createChooser(i, null));
|
||||
}
|
||||
})
|
||||
));
|
||||
ConfigObject obj = SliceBeam.CONFIG.findPrinter(SliceBeam.CONFIG.presets.get("printer"));
|
||||
assertTrue(obj != null);
|
||||
String type = obj.get("host_type");
|
||||
if (type == null) type = "octoprint";
|
||||
String host = obj.get("print_host");
|
||||
String apiKey = obj.get("printhost_apikey");
|
||||
if (SUPPORTED_SEND.contains(type) && !TextUtils.isEmpty(host)) {
|
||||
String finalType = type;
|
||||
items.add(new BedMenuItem(R.string.MenuSliceSendToPrinter, R.drawable.send_outline_28).onClick(v -> upload(finalType, host, apiKey, false)));
|
||||
items.add(new BedMenuItem(R.string.MenuSliceSendToPrinterAndPrint, R.drawable.send_28).onClick(v -> upload(finalType, host, apiKey, true)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private void upload(String type, String host, String apiKey, boolean print) {
|
||||
String name = fragment.getGlView().getRenderer().getGcodeResult().getRecommendedName();
|
||||
switch (type) {
|
||||
default:
|
||||
case "octoprint":
|
||||
if (!host.startsWith("http://")) {
|
||||
host = "http://" + host;
|
||||
}
|
||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuSliceSendToPrinterStarted));
|
||||
Header[] headers = TextUtils.isEmpty(apiKey) ? new Header[0] : new Header[] {new BasicHeader("X-Api-Key", apiKey)};
|
||||
RequestParams params = new RequestParams();
|
||||
try {
|
||||
params.put("file", new FileInputStream(BedFragment.getTempGCodePath()), name, ContentType.TEXT_PLAIN.getMimeType());
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
params.put("select", String.valueOf(print));
|
||||
params.put("print", String.valueOf(print));
|
||||
|
||||
client.post(SliceBeam.INSTANCE, host + "/api/files/local", headers, params, null, new AsyncHttpResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
|
||||
try {
|
||||
JSONObject obj = new JSONObject(new String(responseBody));
|
||||
if (!obj.has("action") && !obj.has("files")) {
|
||||
throw new JSONException(obj.toString());
|
||||
}
|
||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(print ? R.string.MenuSliceSendToPrinterPrintStarted : R.string.MenuSliceSendToPrinterOK));
|
||||
} catch (JSONException e) {
|
||||
onFailure(statusCode, headers, responseBody, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
|
||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(fragment.getContext())
|
||||
.setTitle(R.string.MenuSliceSendToPrinterFailed)
|
||||
.setMessage(error.toString())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show());
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View v) {
|
||||
super.onViewCreated(v);
|
||||
v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@NonNull View v) {
|
||||
if (lastUid != SliceBeam.CONFIG_UID) {
|
||||
adapter.setItems(onCreateItems(v.getWidth() < v.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull View v) {}
|
||||
});
|
||||
}
|
||||
|
||||
private final static class PrintInfoMenu extends UnfoldMenu {
|
||||
private PositionScrollView fromTrack, toTrack;
|
||||
private TextView title;
|
||||
|
||||
private GCodeViewer getViewer() {
|
||||
return fragment.getGlView().getRenderer().getViewer();
|
||||
}
|
||||
|
||||
private void applyView(int from, int to) {
|
||||
GCodeViewer viewer = getViewer();
|
||||
if (viewer == null) {
|
||||
return;
|
||||
}
|
||||
viewer.setLayersViewRange(from - 1, to - 1);
|
||||
fragment.getGlView().requestRender();
|
||||
|
||||
title.setText(fragment.getContext().getString(R.string.MenuSliceInfoLayers, from, to));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(Context ctx, boolean portrait) {
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
ll.addView(new Space(ctx), new LinearLayout.LayoutParams(0, 0, 1f));
|
||||
|
||||
title = new TextView(ctx);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||
title.setGravity(Gravity.CENTER);
|
||||
ll.addView(title, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
fromTrack = new PositionScrollView(ctx);
|
||||
fromTrack.setProgressListener(integer -> {
|
||||
if (getViewer() == null) return;
|
||||
toTrack.setMinMax(integer, (int) getViewer().getLayersCount());
|
||||
if (toTrack.getCurrentPosition() < integer) {
|
||||
toTrack.setCurrentPosition(integer);
|
||||
}
|
||||
applyView(integer, toTrack.getCurrentPosition());
|
||||
});
|
||||
fromTrack.setListener(integer -> applyView(integer, toTrack.getCurrentPosition()));
|
||||
ll.addView(fromTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
toTrack = new PositionScrollView(ctx);
|
||||
toTrack.setProgressListener(integer -> {
|
||||
// TODO: apply only visual?
|
||||
applyView(fromTrack.getCurrentPosition(), integer);
|
||||
});
|
||||
toTrack.setListener(integer -> applyView(fromTrack.getCurrentPosition(), integer));
|
||||
ll.addView(toTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
LinearLayout toolbar = new LinearLayout(ctx);
|
||||
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||
toolbar.setOnClickListener(v -> dismiss());
|
||||
|
||||
ImageView icon = new ImageView(ctx);
|
||||
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||
|
||||
TextView title = new TextView(ctx);
|
||||
title.setText(R.string.MenuOrientationPositionBack);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
|
||||
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||
return ll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return portrait ? ViewUtils.dp(80) * 2 + ViewUtils.dp(24) + ViewUtils.dp(52) + ViewUtils.dp(12) : super.getRequestedSize(into, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
GCodeViewer viewer = getViewer();
|
||||
long max = viewer.getLayersCount();
|
||||
fromTrack.setMinMax(1, (int) max);
|
||||
toTrack.setMinMax(1, (int) max);
|
||||
|
||||
Pair<Long, Long> range = viewer.getLayersViewRange();
|
||||
// TODO: Support long instead of int in PositionScrollView
|
||||
fromTrack.setCurrentPosition(Math.min(range.first.intValue() + 1, range.second.intValue() + 1));
|
||||
toTrack.setCurrentPosition(Math.max(range.first.intValue() + 1, range.second.intValue() + 1));
|
||||
|
||||
title.setText(fragment.getContext().getString(R.string.MenuSliceInfoLayers, fromTrack.getCurrentPosition(), toTrack.getCurrentPosition()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
fromTrack.stopScroll();
|
||||
toTrack.stopScroll();
|
||||
if (getViewer() != null) {
|
||||
applyView(1, (int) getViewer().getLayersCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
|
||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceSwitchItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Model;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.DoubleMatrix;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.Vec3d;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
import ru.ytkab0bp.slicebeam.view.PositionScrollView;
|
||||
import ru.ytkab0bp.slicebeam.view.TextColorImageSpan;
|
||||
|
||||
public class TransformMenu extends ListBedMenu {
|
||||
private double[] tempMatrix = new double[16];
|
||||
private double[] tempVecArr = new double[4];
|
||||
|
||||
private boolean hasSelection() {
|
||||
return fragment.getGlView().getRenderer().getModel() != null && fragment.getGlView().getRenderer().getSelectedObject() != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
||||
return Arrays.asList(
|
||||
new BedMenuItem(R.string.MenuTransformScale, R.drawable.arrow_up_right_corner_outline_24).setEnabled(hasSelection()).onClick(v -> fragment.showUnfoldMenu(new ScaleMenu(), v)),
|
||||
new BedMenuItem(R.string.MenuTransformMirror, R.drawable.menu_transform_cut_or_mirror_28).setEnabled(hasSelection()).onClick(v -> {
|
||||
Context ctx = fragment.getContext();
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.MenuTransformMirror)
|
||||
.setItems(new CharSequence[] {
|
||||
ctx.getString(R.string.MenuTransformMirrorX),
|
||||
ctx.getString(R.string.MenuTransformMirrorY),
|
||||
ctx.getString(R.string.MenuTransformMirrorZ)
|
||||
}, (dialog, which) -> {
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
Vec3d tempVec = new Vec3d();
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
model.getMirror(j, tempVec);
|
||||
|
||||
double dx = tempVec.x, dy = tempVec.y, dz = tempVec.z;
|
||||
|
||||
switch (which) {
|
||||
case 0:
|
||||
dx = -dx;
|
||||
break;
|
||||
case 1:
|
||||
dy = -dy;
|
||||
break;
|
||||
case 2:
|
||||
dz = -dz;
|
||||
break;
|
||||
}
|
||||
|
||||
model.getScale(j, tempVec);
|
||||
dx *= tempVec.x;
|
||||
dy *= tempVec.y;
|
||||
dz *= tempVec.z;
|
||||
|
||||
model.scale(j, dx, dy, dz);
|
||||
fragment.getGlView().getRenderer().invalidateGlModel(j);
|
||||
fragment.getGlView().requestRender();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onObjectsChanged(ObjectsListChangedEvent e) {
|
||||
((BedMenuItem) adapter.getItems().get(0)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(0);
|
||||
|
||||
((BedMenuItem) adapter.getItems().get(1)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(1);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onSelectionChanged(SelectedObjectChangedEvent e) {
|
||||
((BedMenuItem) adapter.getItems().get(0)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(0);
|
||||
|
||||
((BedMenuItem) adapter.getItems().get(1)).setEnabled(hasSelection());
|
||||
adapter.notifyItemChanged(1);
|
||||
}
|
||||
|
||||
public final class ScaleMenu extends UnfoldMenu {
|
||||
private PositionScrollView xTrack, yTrack, zTrack;
|
||||
private TextView xTitle, yTitle, zTitle;
|
||||
private Vec3d tempVec = new Vec3d(), tempVec2 = new Vec3d();
|
||||
private int startedScrollObject;
|
||||
private boolean isLinked;
|
||||
|
||||
public void setLinked(boolean linked) {
|
||||
if (isLinked == linked) return;
|
||||
isLinked = linked;
|
||||
|
||||
if (isLinked) {
|
||||
xTrack.addSynced(yTrack);
|
||||
xTrack.addSynced(zTrack);
|
||||
|
||||
yTrack.addSynced(xTrack);
|
||||
yTrack.addSynced(zTrack);
|
||||
|
||||
zTrack.addSynced(xTrack);
|
||||
zTrack.addSynced(yTrack);
|
||||
} else {
|
||||
xTrack.removeSynced(yTrack);
|
||||
xTrack.removeSynced(zTrack);
|
||||
|
||||
yTrack.removeSynced(xTrack);
|
||||
yTrack.removeSynced(zTrack);
|
||||
|
||||
zTrack.removeSynced(xTrack);
|
||||
zTrack.removeSynced(yTrack);
|
||||
}
|
||||
}
|
||||
|
||||
private void scaleVisual(Double x, Double y, Double z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
startedScrollObject = j;
|
||||
|
||||
if (x != null) {
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, x * 100));
|
||||
}
|
||||
if (y != null) {
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, y * 100));
|
||||
}
|
||||
if (z != null) {
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, z * 100));
|
||||
}
|
||||
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
|
||||
model.getRotation(j, tempVec);
|
||||
DoubleMatrix.setIdentityM(tempMatrix, 0);
|
||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.x), 1, 0, 0);
|
||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.y), 0, 1, 0);
|
||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.z), 0, 0, 1);
|
||||
|
||||
model.getScale(j, tempVec);
|
||||
tempVecArr[0] = tempVec.x;
|
||||
tempVecArr[1] = tempVec.y;
|
||||
tempVecArr[2] = tempVec.z;
|
||||
tempVecArr[3] = 1;
|
||||
DoubleMatrix.multiplyMV(tempVecArr, 0, tempMatrix, 0, tempVecArr, 0);
|
||||
double sx = Math.abs(tempVecArr[0] / tempVecArr[3]);
|
||||
double sy = Math.abs(tempVecArr[1] / tempVecArr[3]);
|
||||
double sz = Math.abs(tempVecArr[2] / tempVecArr[3]);
|
||||
|
||||
double dx = 1, dy = 1, dz = 1;
|
||||
if (x != null) dx = 1 / sx * x;
|
||||
if (y != null) dy = 1 / sy * y;
|
||||
if (z != null) dz = 1 / sz * z;
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionScale(dx, dy, dz);
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
private void scale(Double x, Double y, Double z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
startedScrollObject = -1;
|
||||
|
||||
fragment.getGlView().queueEvent(() -> {
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
|
||||
double dx = 1f, dy = 1f, dz = 1f;
|
||||
if (x != null) {
|
||||
dx = x;
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, x * 100));
|
||||
}
|
||||
if (y != null) {
|
||||
dy = y;
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, y * 100));
|
||||
}
|
||||
if (z != null) {
|
||||
dz = z;
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, z * 100));
|
||||
}
|
||||
|
||||
model.getRotation(j, tempVec);
|
||||
DoubleMatrix.setIdentityM(tempMatrix, 0);
|
||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.x), 1, 0, 0);
|
||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.y), 0, 1, 0);
|
||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.z), 0, 0, 1);
|
||||
tempVecArr[0] = dx;
|
||||
tempVecArr[1] = dy;
|
||||
tempVecArr[2] = dz;
|
||||
tempVecArr[3] = 1;
|
||||
DoubleMatrix.multiplyMV(tempVecArr, 0, tempMatrix, 0, tempVecArr, 0);
|
||||
dx = Math.abs(tempVecArr[0] / tempVecArr[3]);
|
||||
dy = Math.abs(tempVecArr[1] / tempVecArr[3]);
|
||||
dz = Math.abs(tempVecArr[2] / tempVecArr[3]);
|
||||
|
||||
model.getMirror(j, tempVec);
|
||||
dx *= tempVec.x;
|
||||
dy *= tempVec.y;
|
||||
dz *= tempVec.z;
|
||||
|
||||
model.getScale(j, tempVec);
|
||||
model.scale(j, dx, dy, dz);
|
||||
|
||||
model.getBoundingBoxExact(j, tempVec, tempVec2);
|
||||
model.translate(j, 0, 0, -tempVec.z);
|
||||
|
||||
fragment.getGlView().getRenderer().invalidateSelectionObject();
|
||||
fragment.getGlView().getRenderer().setSelectionScale(1, 1, 1);
|
||||
fragment.getGlView().getRenderer().invalidateGlModel(j);
|
||||
fragment.getGlView().requestRender();
|
||||
});
|
||||
fragment.getGlView().requestRender();
|
||||
}
|
||||
|
||||
private CharSequence formatTrackTitle(int res, double value) {
|
||||
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(SliceBeam.INSTANCE.getString(res, value));
|
||||
sb.append(" d");
|
||||
int size = ViewUtils.dp(14);
|
||||
Drawable dr = ContextCompat.getDrawable(SliceBeam.INSTANCE, R.drawable.edit_outline_28);
|
||||
dr.setTint(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
dr.setBounds(0, 0, size, size);
|
||||
sb.setSpan(new TextColorImageSpan(dr, 0), sb.length() - 1, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return sb;
|
||||
}
|
||||
|
||||
private void showManualEditor(int title, boolean x, boolean y, boolean z) {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
model.getScale(j, tempVec);
|
||||
|
||||
double current;
|
||||
if (x) {
|
||||
current = tempVec.x * 100;
|
||||
} else if (y) {
|
||||
current = tempVec.y * 100;
|
||||
} else {
|
||||
current = tempVec.z * 100;
|
||||
}
|
||||
|
||||
Context ctx = getView().getContext();
|
||||
FrameLayout fl = new FrameLayout(ctx);
|
||||
EditText text = new EditText(ctx);
|
||||
text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
text.setText(String.format(Locale.ROOT, "%.2f", current));
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
fl.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(title)
|
||||
.setView(fl)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
double value;
|
||||
try {
|
||||
value = Double.parseDouble(text.getText().toString()) / 100.0;
|
||||
} catch (NumberFormatException e) {
|
||||
value = current;
|
||||
}
|
||||
|
||||
double dx = tempVec.x, dy = tempVec.y, dz = tempVec.z;
|
||||
if (x || isLinked) xTrack.setCurrentPosition((int) ((dx = value) * 100));
|
||||
if (y || isLinked) yTrack.setCurrentPosition((int) ((dy = value) * 100));
|
||||
if (z || isLinked) zTrack.setCurrentPosition((int) ((dz = value) * 100));
|
||||
|
||||
scale(dx, dy, dz);
|
||||
})
|
||||
.show();
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
text.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(text, 0);
|
||||
text.setSelection(text.getText().length());
|
||||
}, 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(Context ctx, boolean portrait) {
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
ll.setPadding(0, ViewUtils.dp(12), 0, 0);
|
||||
|
||||
xTitle = new TextView(ctx);
|
||||
xTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
xTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
xTitle.setTextColor(ThemesRepo.getColor(R.attr.xTrackColor));
|
||||
xTitle.setGravity(Gravity.CENTER);
|
||||
xTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationPositionX, true, false, false));
|
||||
ll.addView(xTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
xTrack = new PositionScrollView(ctx);
|
||||
xTrack.setActiveColor(R.attr.xTrackColor);
|
||||
xTrack.setProgressListener(integer -> scaleVisual(integer.doubleValue() / 100.0, yTrack.getCurrentPosition() / 100.0, zTrack.getCurrentPosition() / 100.0));
|
||||
xTrack.setListener(integer -> scale(integer.doubleValue() / 100.0, yTrack.getCurrentPosition() / 100.0, zTrack.getCurrentPosition() / 100.0));
|
||||
xTrack.setMinMax(1, Integer.MAX_VALUE);
|
||||
ll.addView(xTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
yTitle = new TextView(ctx);
|
||||
yTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
yTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
yTitle.setTextColor(ThemesRepo.getColor(R.attr.yTrackColor));
|
||||
yTitle.setGravity(Gravity.CENTER);
|
||||
yTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationPositionY, false, true, false));
|
||||
ll.addView(yTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
yTrack = new PositionScrollView(ctx);
|
||||
yTrack.setActiveColor(R.attr.yTrackColor);
|
||||
yTrack.setProgressListener(integer -> scaleVisual(xTrack.getCurrentPosition() / 100.0, integer.doubleValue() / 100.0, zTrack.getCurrentPosition() / 100.0));
|
||||
yTrack.setListener(integer -> scale(xTrack.getCurrentPosition() / 100.0, integer.doubleValue() / 100.0, zTrack.getCurrentPosition() / 100.0));
|
||||
yTrack.setMinMax(1, Integer.MAX_VALUE);
|
||||
ll.addView(yTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
zTitle = new TextView(ctx);
|
||||
zTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
zTitle.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
zTitle.setTextColor(ThemesRepo.getColor(R.attr.zTrackColor));
|
||||
zTitle.setGravity(Gravity.CENTER);
|
||||
zTitle.setOnClickListener(v -> showManualEditor(R.string.MenuOrientationPositionZ, false, false, true));
|
||||
ll.addView(zTitle, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(20)) {{
|
||||
bottomMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
zTrack = new PositionScrollView(ctx);
|
||||
zTrack.setActiveColor(R.attr.zTrackColor);
|
||||
zTrack.setProgressListener(integer -> scaleVisual(xTrack.getCurrentPosition() / 100.0, yTrack.getCurrentPosition() / 100.0, integer.doubleValue() / 100.0));
|
||||
zTrack.setListener(integer -> scale(xTrack.getCurrentPosition() / 100.0, yTrack.getCurrentPosition() / 100.0, integer.doubleValue() / 100.0));
|
||||
zTrack.setMinMax(1, Integer.MAX_VALUE);
|
||||
ll.addView(zTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
|
||||
|
||||
ll.addView(new Space(ctx), new LinearLayout.LayoutParams(0, 0, 1f));
|
||||
|
||||
PreferenceSwitchItem.SwitchPreferenceHolderView holderView = new PreferenceSwitchItem.SwitchPreferenceHolderView(ctx);
|
||||
holderView.title.setText(R.string.MenuTransformScaleLink);
|
||||
holderView.title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
holderView.matSwitch.setChecked(Prefs.isScaleLinked());
|
||||
holderView.subtitle.setVisibility(View.GONE);
|
||||
holderView.setOnClickListener(v -> {
|
||||
boolean check = !Prefs.isScaleLinked();
|
||||
holderView.matSwitch.setChecked(check);
|
||||
Prefs.setScaleLinked(check);
|
||||
setLinked(check);
|
||||
});
|
||||
if (portrait) {
|
||||
ll.addView(holderView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(64)));
|
||||
}
|
||||
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
LinearLayout toolbar = new LinearLayout(ctx);
|
||||
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||
toolbar.setOnClickListener(v -> dismiss());
|
||||
|
||||
ImageView icon = new ImageView(ctx);
|
||||
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||
|
||||
TextView title = new TextView(ctx);
|
||||
title.setText(R.string.MenuOrientationPositionBack);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
if (!portrait) {
|
||||
title.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
((LinearLayout.LayoutParams) title.getLayoutParams()).weight = 0;
|
||||
|
||||
holderView.title.setMaxLines(1);
|
||||
toolbar.addView(holderView, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
}
|
||||
|
||||
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||
|
||||
setLinked(Prefs.isScaleLinked());
|
||||
|
||||
return ll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||
return portrait ? ViewUtils.dp(52) + ViewUtils.dp(64) + ViewUtils.dp(80 + 24) * 3 + ViewUtils.dp(12) : (int) (into.getWidth() * 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
setSelectionValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
stopScroll();
|
||||
}
|
||||
|
||||
private void setSelectionValues() {
|
||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||
if (j == -1) return;
|
||||
|
||||
fragment.getGlView().getRenderer().setSelectionScale(1, 1, 1);
|
||||
|
||||
Model model = fragment.getGlView().getRenderer().getModel();
|
||||
model.getScale(j, tempVec);
|
||||
|
||||
xTrack.setCurrentPosition((int) Math.round(tempVec.x * 100));
|
||||
yTrack.setCurrentPosition((int) Math.round(tempVec.y * 100));
|
||||
zTrack.setCurrentPosition((int) Math.round(tempVec.z * 100));
|
||||
|
||||
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, tempVec.x * 100));
|
||||
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, tempVec.y * 100));
|
||||
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, tempVec.z * 100));
|
||||
|
||||
xTrack.updateSyncDeltas();
|
||||
yTrack.updateSyncDeltas();
|
||||
zTrack.updateSyncDeltas();
|
||||
}
|
||||
|
||||
private void stopScroll() {
|
||||
xTrack.stopScroll();
|
||||
yTrack.stopScroll();
|
||||
zTrack.stopScroll();
|
||||
|
||||
if (startedScrollObject != -1) {
|
||||
fragment.getGlView().getRenderer().setSelectionScale(1, 1, 1);
|
||||
}
|
||||
startedScrollObject = -1;
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onSelectedObjectChanged(SelectedObjectChangedEvent e) {
|
||||
stopScroll();
|
||||
|
||||
if (fragment.getGlView().getRenderer().getSelectedObject() == -1) {
|
||||
dismiss();
|
||||
} else {
|
||||
setSelectionValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package ru.ytkab0bp.slicebeam.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.fragment.ProfileListFragment;
|
||||
|
||||
/** @noinspection CopyConstructorMissesField*/
|
||||
public class ConfigObject implements ProfileListFragment.ProfileListItem {
|
||||
public final static int PROFILE_LIST_PRINT = 0, PROFILE_LIST_FILAMENT = 1, PROFILE_LIST_PRINTER = 2;
|
||||
|
||||
private String title;
|
||||
public Map<String, String> values = new HashMap<>();
|
||||
|
||||
// Used only in setup
|
||||
public String thumbnailUrl;
|
||||
|
||||
// Type for isSelected()
|
||||
public int profileListType;
|
||||
|
||||
public ConfigObject() {
|
||||
title = null;
|
||||
}
|
||||
|
||||
public ConfigObject(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public ConfigObject(ConfigObject from) {
|
||||
this.title = from.title;
|
||||
this.values.putAll(from.values);
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
public void put(String key, String value) {
|
||||
values.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
switch (profileListType) {
|
||||
case PROFILE_LIST_PRINT:
|
||||
return getTitle().equals(SliceBeam.CONFIG.presets.get("print"));
|
||||
case PROFILE_LIST_FILAMENT:
|
||||
return getTitle().equals(SliceBeam.CONFIG.presets.get("filament"));
|
||||
case PROFILE_LIST_PRINTER:
|
||||
return getTitle().equals(SliceBeam.CONFIG.presets.get("printer"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("# generated by Slice Beam ").append(BuildConfig.VERSION_NAME).append("\n\n");
|
||||
for (Map.Entry<String, String> en : values.entrySet()) {
|
||||
sb.append(en.getKey()).append(" = ").append(en.getValue().replace("\n", "\\n")).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static ConfigObject createCustomPrinterProfile() {
|
||||
ConfigObject custom = new ConfigObject(SliceBeam.INSTANCE.getString(R.string.IntroCustomProfileName));
|
||||
custom.put("printer_technology", "FFF");
|
||||
custom.put("bed_shape", "0x0,200x0,200x200,0x200");
|
||||
custom.put("binary_gcode", "0");
|
||||
custom.put("gcode_flavor", "marlin");
|
||||
custom.put("max_print_height", "200");
|
||||
custom.put("min_layer_height", "0.15");
|
||||
custom.put("max_layer_height", "0.30");
|
||||
custom.put("layer_height", "0.2");
|
||||
custom.put("nozzle_diameter", "0.4");
|
||||
custom.put("z_offset", "0");
|
||||
custom.put("retract_length", "0.5");
|
||||
custom.put("retract_speed", "30");
|
||||
custom.put("deretract_speed", "30");
|
||||
custom.put("retract_before_travel", "2");
|
||||
|
||||
custom.put("machine_limits_usage", "time_estimate_only");
|
||||
custom.put("machine_max_acceleration_e", "5000");
|
||||
custom.put("machine_max_acceleration_extruding", "500");
|
||||
custom.put("machine_max_acceleration_retracting", "1000");
|
||||
custom.put("machine_max_acceleration_travel", "500");
|
||||
custom.put("machine_max_acceleration_x", "500");
|
||||
custom.put("machine_max_acceleration_y", "500");
|
||||
custom.put("machine_max_acceleration_z", "100");
|
||||
custom.put("machine_max_feedrate_e", "60");
|
||||
custom.put("machine_max_feedrate_x", "500");
|
||||
custom.put("machine_max_feedrate_y", "500");
|
||||
custom.put("machine_max_feedrate_z", "10");
|
||||
custom.put("machine_max_jerk_e", "5");
|
||||
custom.put("machine_max_jerk_x", "8");
|
||||
custom.put("machine_max_jerk_y", "8");
|
||||
custom.put("machine_max_jerk_z", "0.4");
|
||||
custom.put("machine_min_extruding_rate", "0");
|
||||
custom.put("machine_min_travel_rate", "0");
|
||||
|
||||
custom.put("start_gcode", "G90 ; use absolute coordinates\\nM83 ; extruder relative mode\\nM104 S{is_nil(idle_temperature[0]) ? 150 : idle_temperature[0]} ; set temporary nozzle temp to prevent oozing during homing\\nM140 S{first_layer_bed_temperature[0]} ; set final bed temp\\nG4 S30 ; allow partial nozzle warmup\\nG28 ; home all axis\\nG1 Z50 F240\\nG1 X2.0 Y10 F3000\\nM104 S{first_layer_temperature[0]} ; set final nozzle temp\\nM190 S{first_layer_bed_temperature[0]} ; wait for bed temp to stabilize\\nM109 S{first_layer_temperature[0]} ; wait for nozzle temp to stabilize\\nG1 Z0.28 F240\\nG92 E0\\nG1 X2.0 Y140 E10 F1500 ; prime the nozzle\\nG1 X2.3 Y140 F5000\\nG92 E0\\nG1 X2.3 Y10 E10 F1200 ; prime the nozzle\\nG92 E0");
|
||||
custom.put("end_gcode", "{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)} F600 ; Move print head up{endif}\\nG1 X5 Y{print_bed_max[1]*0.85} F{travel_speed*60} ; present print\\n{if max_layer_z < max_print_height-10}G1 Z{z_offset+min(max_layer_z+70, max_print_height-10)} F600 ; Move print head further up{endif}\\n{if max_layer_z < max_print_height*0.6}G1 Z{max_print_height*0.6} F600 ; Move print head further up{endif}\\nM140 S0 ; turn off heatbed\\nM104 S0 ; turn off temperature\\nM107 ; turn off fan\\nM84 X Y E ; disable motors");
|
||||
|
||||
return custom;
|
||||
}
|
||||
|
||||
public static ConfigObject createCustomFilamentProfile() {
|
||||
ConfigObject genericFilament = new ConfigObject(SliceBeam.INSTANCE.getString(R.string.IntroCustomProfileFilamentName));
|
||||
genericFilament.profileListType = ConfigObject.PROFILE_LIST_FILAMENT;
|
||||
genericFilament.put("first_layer_bed_temperature", "60");
|
||||
genericFilament.put("bed_temperature", "60");
|
||||
genericFilament.put("first_layer_temperature", "210");
|
||||
genericFilament.put("temperature", "210");
|
||||
genericFilament.put("filament_type", "PLA");
|
||||
genericFilament.put("slowdown_below_layer_time", "8");
|
||||
genericFilament.put("cooling", "1");
|
||||
genericFilament.put("fan_always_on", "1");
|
||||
genericFilament.put("fan_below_layer_time", "20");
|
||||
genericFilament.put("idle_temperature", "150");
|
||||
return genericFilament;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.ytkab0bp.slicebeam.events;
|
||||
|
||||
import ru.ytkab0bp.eventbus.Event;
|
||||
|
||||
@Event
|
||||
public class BeamServerDataUpdatedEvent {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.ytkab0bp.slicebeam.events;
|
||||
|
||||
import ru.ytkab0bp.eventbus.Event;
|
||||
|
||||
@Event
|
||||
public class NeedDismissCalibrationsMenu {}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.ytkab0bp.slicebeam.events;
|
||||
|
||||
import ru.ytkab0bp.eventbus.Event;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
|
||||
@Event
|
||||
public class NeedSnackbarEvent {
|
||||
public final CharSequence title;
|
||||
|
||||
public NeedSnackbarEvent(CharSequence title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public NeedSnackbarEvent(int title, Object... args) {
|
||||
this.title = SliceBeam.INSTANCE.getString(title, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.ytkab0bp.slicebeam.events;
|
||||
|
||||
import ru.ytkab0bp.eventbus.Event;
|
||||
|
||||
@Event
|
||||
public class ObjectsListChangedEvent {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.ytkab0bp.slicebeam.events;
|
||||
|
||||
import ru.ytkab0bp.eventbus.Event;
|
||||
|
||||
@Event
|
||||
public class SelectedObjectChangedEvent {}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.ytkab0bp.slicebeam.events;
|
||||
|
||||
public class SlicingProgressEvent {
|
||||
public final int progress;
|
||||
public final String message;
|
||||
|
||||
public SlicingProgressEvent(int progress, String message) {
|
||||
this.progress = progress;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package ru.ytkab0bp.slicebeam.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.google.android.material.navigation.NavigationBarView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.SliceProgressBottomSheet;
|
||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.bed_menu.BedMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.bed_menu.CameraMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.bed_menu.FileMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.bed_menu.OrientationMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.bed_menu.SliceMenu;
|
||||
import ru.ytkab0bp.slicebeam.components.bed_menu.TransformMenu;
|
||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||
import ru.ytkab0bp.slicebeam.events.SlicingProgressEvent;
|
||||
import ru.ytkab0bp.slicebeam.navigation.Fragment;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Bed3D;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.GCodeProcessorResult;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Model;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.Vec3d;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
import ru.ytkab0bp.slicebeam.view.GLView;
|
||||
import ru.ytkab0bp.slicebeam.view.ThemeBottomNavigationView;
|
||||
import ru.ytkab0bp.slicebeam.view.ThemeRailNavigationView;
|
||||
|
||||
public class BedFragment extends Fragment {
|
||||
private final static boolean DEBUG_VIEWER = false;
|
||||
private final static int MENU_SIZE_DP = 80;
|
||||
|
||||
private FrameLayout overlayLayout;
|
||||
private CoordinatorLayout snackbarsLayout;
|
||||
private GLView glView;
|
||||
private NavigationBarView navigationView;
|
||||
|
||||
private boolean isAnimatingMenu;
|
||||
private boolean isChangingByCode;
|
||||
private int currentMenuSlot;
|
||||
private FrameLayout menuView;
|
||||
private SparseArray<BedMenu> menuMap = new SparseArray<BedMenu>() {
|
||||
@Override
|
||||
public BedMenu get(int key) {
|
||||
BedMenu menu = super.get(key);
|
||||
if (menu == null) {
|
||||
switch (MenuCategory.values()[key]) {
|
||||
default:
|
||||
case FILE:
|
||||
menu = new FileMenu();
|
||||
break;
|
||||
case CAMERA:
|
||||
menu = new CameraMenu();
|
||||
break;
|
||||
case ORIENTATION:
|
||||
menu = new OrientationMenu();
|
||||
break;
|
||||
case TRANSFORM:
|
||||
menu = new TransformMenu();
|
||||
break;
|
||||
case SLICE_AND_EXPORT:
|
||||
menu = new SliceMenu();
|
||||
break;
|
||||
}
|
||||
|
||||
put(key, menu);
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
};
|
||||
|
||||
private View contentView;
|
||||
|
||||
private Model model;
|
||||
private GCodeProcessorResult gCodeResult;
|
||||
private UnfoldMenu currentUnfoldMenu;
|
||||
|
||||
private static String tempFileName;
|
||||
private static File tempExportingFile;
|
||||
|
||||
public static String getTempFileName() {
|
||||
return tempFileName;
|
||||
}
|
||||
|
||||
public static File getTempGCodePath() {
|
||||
return tempExportingFile != null ? tempExportingFile : new File(SliceBeam.INSTANCE.getCacheDir(), "temp.gcode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onNeedSnackbar(NeedSnackbarEvent e) {
|
||||
Snackbar.make(snackbarsLayout, e.title, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void showUnfoldMenu(UnfoldMenu menu, View from) {
|
||||
if (currentUnfoldMenu != null) return;
|
||||
|
||||
menu.setOnDismiss(()-> currentUnfoldMenu = null);
|
||||
currentUnfoldMenu = menu;
|
||||
menu.show(from, this);
|
||||
}
|
||||
|
||||
public void loadGCode(File f) {
|
||||
gCodeResult = new GCodeProcessorResult(f);
|
||||
ViewUtils.postOnMainThread(()-> {
|
||||
glView.queueEvent(()->{
|
||||
glView.getRenderer().setGCodeViewer(gCodeResult);
|
||||
glView.requestRender();
|
||||
});
|
||||
|
||||
tempFileName = gCodeResult.getRecommendedName();
|
||||
tempExportingFile = f;
|
||||
|
||||
isChangingByCode = true;
|
||||
navigationView.setSelectedItemId(MenuCategory.SLICE_AND_EXPORT.ordinal());
|
||||
isChangingByCode = false;
|
||||
|
||||
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
||||
boolean portrait = dm.widthPixels < dm.heightPixels;
|
||||
selectMenu(getContext(), portrait, MenuCategory.SLICE_AND_EXPORT.ordinal());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
if (currentUnfoldMenu != null) {
|
||||
currentUnfoldMenu.dismiss();
|
||||
return true;
|
||||
}
|
||||
if (currentMenuSlot != 0) {
|
||||
navigationView.setSelectedItemId(0);
|
||||
return true;
|
||||
}
|
||||
return super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
|
||||
for (int i = 0; i < menuMap.size(); i++) {
|
||||
menuMap.valueAt(i).onViewDestroyed();
|
||||
}
|
||||
|
||||
if (!(getContext() instanceof Activity && ((Activity) getContext()).isChangingConfigurations())) {
|
||||
if (model != null) {
|
||||
model.release();
|
||||
model = null;
|
||||
}
|
||||
if (gCodeResult != null) {
|
||||
gCodeResult.release();
|
||||
gCodeResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
glView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
glView.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(Context ctx) {
|
||||
glView = new GLView(ctx);
|
||||
glView.getRenderer().setModel(model);
|
||||
glView.getRenderer().setGCodeViewer(gCodeResult);
|
||||
overlayLayout = new FrameLayout(ctx) {
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
if (currentUnfoldMenu != null) {
|
||||
currentUnfoldMenu.relayout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
DisplayMetrics dm = ctx.getResources().getDisplayMetrics();
|
||||
boolean portrait = dm.widthPixels < dm.heightPixels;
|
||||
|
||||
ll.setOrientation(portrait ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
|
||||
navigationView = null;
|
||||
constructMenuView(ctx, portrait);
|
||||
|
||||
if (!portrait) {
|
||||
ll.addView(navigationView = new ThemeRailNavigationView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewUtils.dp(1), ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
ll.addView(menuView, new LinearLayout.LayoutParams(ViewUtils.dp(MENU_SIZE_DP), ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
ll.addView(glView, new LinearLayout.LayoutParams(portrait ? ViewGroup.LayoutParams.MATCH_PARENT : 0, portrait ? 0 : ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
|
||||
if (portrait) {
|
||||
ll.addView(menuView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(MENU_SIZE_DP)));
|
||||
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1)));
|
||||
ll.addView(navigationView = new ThemeBottomNavigationView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
navigationView.setLabelVisibilityMode(NavigationBarView.LABEL_VISIBILITY_LABELED);
|
||||
for (MenuCategory cat : MenuCategory.values()) {
|
||||
navigationView.getMenu().add(0, cat.ordinal(), 0, cat.titleRes).setIcon(cat.iconRes);
|
||||
}
|
||||
navigationView.setSelectedItemId(currentMenuSlot);
|
||||
navigationView.setOnItemSelectedListener(item -> {
|
||||
if (currentMenuSlot == item.getItemId() || isChangingByCode) return true;
|
||||
if (isAnimatingMenu) return false;
|
||||
if (item.getItemId() == MenuCategory.SLICE_AND_EXPORT.ordinal()) {
|
||||
if (glView.getRenderer().getModel() == null && !DEBUG_VIEWER) {
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.SliceFailed)
|
||||
.setMessage(R.string.SliceFailedNoModels)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
} else {
|
||||
tempExportingFile = null;
|
||||
File cfg = SliceBeam.getCurrentConfigFile();
|
||||
File gcode = getTempGCodePath();
|
||||
|
||||
if (!DEBUG_VIEWER) {
|
||||
new SliceProgressBottomSheet(ctx).show();
|
||||
}
|
||||
new Thread(()->{
|
||||
try {
|
||||
Process.setThreadPriority(-20);
|
||||
|
||||
try {
|
||||
SliceBeam.genCurrentConfig();
|
||||
} catch (Exception e) {
|
||||
Log.e("BedFragment", "Failed to write config", e);
|
||||
|
||||
ViewUtils.postOnMainThread(()->{
|
||||
SliceBeam.EVENT_BUS.fireEvent(new SlicingProgressEvent(100, ""));
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.SliceFailed)
|
||||
.setMessage(e.getMessage())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
if (!DEBUG_VIEWER) {
|
||||
gCodeResult = glView.getRenderer().getModel().slice(cfg.getAbsolutePath(), gcode.getAbsolutePath(), (progress, text) -> SliceBeam.EVENT_BUS.fireEvent(new SlicingProgressEvent(progress, text)));
|
||||
SliceBeam.EVENT_BUS.fireEvent(new SlicingProgressEvent(100, ""));
|
||||
} else {
|
||||
gCodeResult = new GCodeProcessorResult(gcode);
|
||||
}
|
||||
ViewUtils.postOnMainThread(()-> {
|
||||
glView.queueEvent(()->{
|
||||
glView.getRenderer().setGCodeViewer(gCodeResult);
|
||||
glView.requestRender();
|
||||
});
|
||||
|
||||
tempFileName = gCodeResult.getRecommendedName();
|
||||
tempExportingFile = null;
|
||||
|
||||
isChangingByCode = true;
|
||||
navigationView.setSelectedItemId(item.getItemId());
|
||||
isChangingByCode = false;
|
||||
selectMenu(ctx, portrait, item.getItemId());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e("BedFragment", "Slice failed", e);
|
||||
ViewUtils.postOnMainThread(()->{
|
||||
SliceBeam.EVENT_BUS.fireEvent(new SlicingProgressEvent(100, ""));
|
||||
new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.SliceFailed)
|
||||
.setMessage(e.getMessage())
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
glView.queueEvent(()->{
|
||||
if (gCodeResult != null) {
|
||||
gCodeResult.release();
|
||||
gCodeResult = null;
|
||||
}
|
||||
|
||||
glView.getRenderer().setGCodeViewer(null);
|
||||
glView.requestRender();
|
||||
});
|
||||
}
|
||||
|
||||
selectMenu(ctx, portrait, item.getItemId());
|
||||
return true;
|
||||
});
|
||||
|
||||
overlayLayout.addView(contentView = ll);
|
||||
overlayLayout.addView(snackbarsLayout = new CoordinatorLayout(ctx), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) {{
|
||||
if (portrait) {
|
||||
bottomMargin = ViewUtils.dp(80 * 2);
|
||||
} else {
|
||||
leftMargin = ViewUtils.dp(80 * 2);
|
||||
}
|
||||
}});
|
||||
return overlayLayout;
|
||||
}
|
||||
|
||||
public CoordinatorLayout getSnackbarsLayout() {
|
||||
return snackbarsLayout;
|
||||
}
|
||||
|
||||
public FrameLayout getOverlayLayout() {
|
||||
return overlayLayout;
|
||||
}
|
||||
|
||||
private void selectMenu(Context ctx, boolean portrait, int slot) {
|
||||
isAnimatingMenu = true;
|
||||
|
||||
BedMenu prevMenu = menuMap.get(currentMenuSlot);
|
||||
boolean forward = slot > currentMenuSlot;
|
||||
currentMenuSlot = slot;
|
||||
|
||||
BedMenu currentMenu = menuMap.get(currentMenuSlot);
|
||||
if (currentMenu.getView() == null) {
|
||||
currentMenu.onSetBed(this);
|
||||
currentMenu.onViewCreated(currentMenu.onCreateView(ctx, portrait));
|
||||
}
|
||||
View v = currentMenu.getView();
|
||||
if (v.getParent() != null) {
|
||||
menuView.removeView(v);
|
||||
}
|
||||
menuView.addView(v, 0, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
Runnable next = ()->{
|
||||
if (portrait) {
|
||||
v.setTranslationX(v.getWidth() * (forward ? 1 : -1));
|
||||
} else {
|
||||
v.setTranslationY(v.getHeight() * (forward ? 1 : -1));
|
||||
}
|
||||
v.setAlpha(0f);
|
||||
|
||||
View prevView = prevMenu.getView();
|
||||
|
||||
new SpringAnimation(new FloatValueHolder(0))
|
||||
.setMinimumVisibleChange(1 / 256f)
|
||||
.setSpring(new SpringForce(1f)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener((animation, value, velocity) -> {
|
||||
prevView.setAlpha(1f - value);
|
||||
v.setAlpha(value);
|
||||
|
||||
if (portrait) {
|
||||
prevView.setTranslationX(-v.getWidth() * value * 0.5f * (forward ? 1 : -1));
|
||||
v.setTranslationX(v.getWidth() * (1f - value) * 0.5f * (forward ? 1 : -1));
|
||||
} else {
|
||||
prevView.setTranslationY(-prevView.getHeight() * value * 0.5f * (forward ? 1 : -1));
|
||||
v.setTranslationY(v.getHeight() * (1f - value) * 0.5f * (forward ? 1 : -1));
|
||||
}
|
||||
})
|
||||
.addEndListener((animation, canceled, value, velocity) -> {
|
||||
menuView.removeView(prevMenu.getView());
|
||||
isAnimatingMenu = false;
|
||||
})
|
||||
.start();
|
||||
};
|
||||
|
||||
if (!v.isLaidOut()) {
|
||||
v.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
||||
@Override
|
||||
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
v.removeOnLayoutChangeListener(this);
|
||||
next.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next.run();
|
||||
}
|
||||
}
|
||||
|
||||
public GLView getGlView() {
|
||||
return glView;
|
||||
}
|
||||
|
||||
public void loadModel(File f) throws Slic3rRuntimeError {
|
||||
Model m = new Model(f);
|
||||
if (model != null) {
|
||||
glView.queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
if (bed == null) {
|
||||
ViewUtils.postOnMainThread(()-> glView.queueEvent(this));
|
||||
return;
|
||||
}
|
||||
Vec3d center = bed.getVolumeMin().center(bed.getVolumeMax());
|
||||
Vec3d objMin = new Vec3d(), objMax = new Vec3d();
|
||||
Vec3d objTranslate = new Vec3d();
|
||||
for (int i = 0; i < m.getObjectsCount(); i++) {
|
||||
m.getTranslation(i, objTranslate);
|
||||
m.getBoundingBoxExact(i, objMin, objMax);
|
||||
|
||||
m.translate(i, -objTranslate.x + center.x, -objTranslate.y + center.y, -objTranslate.z + (objMax.z - objMin.z) / 2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m.getObjectsCount(); i++) {
|
||||
model.addObject(m, i);
|
||||
}
|
||||
m.release();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
glView.getRenderer().setModel(model = m);
|
||||
glView.queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bed3D bed = glView.getRenderer().getBed();
|
||||
if (bed == null) {
|
||||
ViewUtils.postOnMainThread(()-> glView.queueEvent(this));
|
||||
return;
|
||||
}
|
||||
Vec3d center = bed.getVolumeMin().center(bed.getVolumeMax());
|
||||
Vec3d objMin = new Vec3d(), objMax = new Vec3d();
|
||||
Vec3d objTranslate = new Vec3d();
|
||||
for (int i = 0; i < m.getObjectsCount(); i++) {
|
||||
m.getTranslation(i, objTranslate);
|
||||
m.getBoundingBoxExact(i, objMin, objMax);
|
||||
|
||||
m.translate(i, -objTranslate.x + center.x, -objTranslate.y + center.y, -objTranslate.z + (objMax.z - objMin.z) / 2);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
glView.requestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
super.onApplyTheme();
|
||||
|
||||
menuView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
}
|
||||
|
||||
private void constructMenuView(Context ctx, boolean portrait) {
|
||||
menuView = new FrameLayout(ctx);
|
||||
BedMenu currentMenu = menuMap.get(currentMenuSlot);
|
||||
if (currentMenu.getView() == null) {
|
||||
currentMenu.onSetBed(this);
|
||||
currentMenu.onViewCreated(currentMenu.onCreateView(ctx, portrait));
|
||||
}
|
||||
menuView.addView(currentMenu.getView(), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
public void updateModel() {
|
||||
model = glView.getRenderer().getModel();
|
||||
}
|
||||
|
||||
public enum MenuCategory {
|
||||
FILE(R.string.MenuFile, R.drawable.folder_simple_outline_28),
|
||||
CAMERA(R.string.MenuCamera, R.drawable.camera_outline_28),
|
||||
ORIENTATION(R.string.MenuOrientation, R.drawable.menu_orientation_28),
|
||||
TRANSFORM(R.string.MenuTransform, R.drawable.menu_scale_28),
|
||||
// MODIFIERS(R.string.MenuModifiers, R.drawable.sliders_outline_28),
|
||||
SLICE_AND_EXPORT(R.string.MenuSlice, R.drawable.magic_wand_outline_28);
|
||||
|
||||
final int titleRes;
|
||||
final int iconRes;
|
||||
|
||||
MenuCategory(int titleRes, int iconRes) {
|
||||
this.titleRes = titleRes;
|
||||
this.iconRes = iconRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package ru.ytkab0bp.slicebeam.fragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rUtils;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class FilamentConfigFragment extends ProfileListFragment {
|
||||
private List<ProfileListItem> compatItems;
|
||||
private String lastPrinter;
|
||||
private String lastPrint;
|
||||
private int lastUid;
|
||||
private ConfigObject currentConfig;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
onResetConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ProfileListItem> getItems(boolean filter) {
|
||||
List<ConfigObject> list = SliceBeam.CONFIG.filamentConfigs;
|
||||
if (filter) {
|
||||
String printer = SliceBeam.CONFIG.presets.get("printer");
|
||||
String print = SliceBeam.CONFIG.presets.get("print");
|
||||
if (Objects.equals(lastPrinter, printer) && Objects.equals(lastPrint, print) && compatItems != null && lastUid == SliceBeam.CONFIG_UID) {
|
||||
return compatItems;
|
||||
}
|
||||
|
||||
List<ConfigObject> nList = new ArrayList<>(list.size());
|
||||
Slic3rUtils.ConfigChecker checker = new Slic3rUtils.ConfigChecker(SliceBeam.CONFIG.findPrinter(printer).serialize());
|
||||
Slic3rUtils.ConfigChecker printChecker = new Slic3rUtils.ConfigChecker(SliceBeam.CONFIG.findPrint(print).serialize());
|
||||
for (ConfigObject obj : list) {
|
||||
if (checker.checkCompatibility(obj.get("compatible_printers_condition")) && printChecker.checkCompatibility(obj.get("compatible_prints_condition"))) {
|
||||
nList.add(obj);
|
||||
}
|
||||
}
|
||||
printChecker.release();
|
||||
checker.release();
|
||||
lastPrinter = printer;
|
||||
lastPrint = print;
|
||||
lastUid = SliceBeam.CONFIG_UID;
|
||||
return compatItems = (List) nList;
|
||||
}
|
||||
return (List) list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<OptionElement> getConfigItems() {
|
||||
PrintConfigDef def = PrintConfigDef.getInstance();
|
||||
return Arrays.asList(
|
||||
new OptionElement(R.drawable.slot_filament_28, Slic3rLocalization.getString("Filament")),
|
||||
new OptionElement(new SubHeader("Filament")),
|
||||
new OptionElement(def.options.get("filament_colour")),
|
||||
new OptionElement(def.options.get("filament_diameter")),
|
||||
new OptionElement(def.options.get("extrusion_multiplier")),
|
||||
new OptionElement(def.options.get("filament_density")),
|
||||
new OptionElement(def.options.get("filament_cost")),
|
||||
new OptionElement(def.options.get("filament_spool_weight")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Temperature")),
|
||||
new OptionElement(def.options.get("idle_temperature")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Nozzle")),
|
||||
new OptionElement(def.options.get("first_layer_temperature")),
|
||||
new OptionElement(def.options.get("temperature")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Bed")),
|
||||
new OptionElement(def.options.get("first_layer_bed_temperature")),
|
||||
new OptionElement(def.options.get("bed_temperature")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Chamber")),
|
||||
new OptionElement(def.options.get("chamber_temperature")),
|
||||
new OptionElement(def.options.get("chamber_minimal_temperature")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.mode_fan_24, Slic3rLocalization.getString("Cooling")),
|
||||
new OptionElement(new SubHeader("Enable")),
|
||||
new OptionElement(def.options.get("fan_always_on")),
|
||||
new OptionElement(def.options.get("cooling")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Fan speed")),
|
||||
new OptionElement(def.options.get("min_fan_speed")),
|
||||
new OptionElement(def.options.get("max_fan_speed")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(def.options.get("bridge_fan_speed")),
|
||||
new OptionElement(def.options.get("disable_fan_first_layers")),
|
||||
new OptionElement(def.options.get("full_fan_speed_layer")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Dynamic fan speeds")),
|
||||
new OptionElement(def.options.get("enable_dynamic_fan_speeds")),
|
||||
new OptionElement(def.options.get("overhang_fan_speed_0")),
|
||||
new OptionElement(def.options.get("overhang_fan_speed_1")),
|
||||
new OptionElement(def.options.get("overhang_fan_speed_2")),
|
||||
new OptionElement(def.options.get("overhang_fan_speed_3")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Cooling thresholds")),
|
||||
new OptionElement(def.options.get("fan_below_layer_time")),
|
||||
new OptionElement(def.options.get("slowdown_below_layer_time")),
|
||||
new OptionElement(def.options.get("min_print_speed")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.settings_outline_28, Slic3rLocalization.getString("Advanced")),
|
||||
new OptionElement(new SubHeader("Filament properties")),
|
||||
new OptionElement(def.options.get("filament_type")),
|
||||
new OptionElement(def.options.get("filament_soluble")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Print speed override")),
|
||||
new OptionElement(def.options.get("filament_max_volumetric_speed")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(def.options.get("filament_infill_max_speed")),
|
||||
new OptionElement(def.options.get("filament_infill_max_crossing_speed")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Shrinkage compensation")),
|
||||
// TODO: Support x_size_compensation/y_size_compensation instead (I'm too lazy to write description for now)
|
||||
// new OptionElement(def.options.get("filament_shrinkage_compensation_x")),
|
||||
// new OptionElement(def.options.get("filament_shrinkage_compensation_y")),
|
||||
new OptionElement(def.options.get("filament_shrinkage_compensation_xy")),
|
||||
new OptionElement(def.options.get("filament_shrinkage_compensation_z")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Wipe tower parameters")),
|
||||
new OptionElement(def.options.get("filament_minimal_purge_on_wipe_tower")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Toolchange parameters with single extruder MM printers")),
|
||||
new OptionElement(def.options.get("filament_loading_speed_start")),
|
||||
new OptionElement(def.options.get("filament_loading_speed")),
|
||||
new OptionElement(def.options.get("filament_unloading_speed_start")),
|
||||
new OptionElement(def.options.get("filament_unloading_speed")),
|
||||
new OptionElement(def.options.get("filament_load_time")),
|
||||
new OptionElement(def.options.get("filament_unload_time")),
|
||||
new OptionElement(def.options.get("filament_toolchange_delay")),
|
||||
new OptionElement(def.options.get("filament_cooling_moves")),
|
||||
new OptionElement(def.options.get("filament_cooling_initial_speed")),
|
||||
new OptionElement(def.options.get("filament_cooling_final_speed")),
|
||||
new OptionElement(def.options.get("filament_stamping_loading_speed")),
|
||||
new OptionElement(def.options.get("filament_stamping_distance")),
|
||||
new OptionElement(def.options.get("filament_purge_multiplier")),
|
||||
new OptionElement(def.options.get("filament_ramming_parameters")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Toolchange parameters with multi extruder MM printers")),
|
||||
new OptionElement(def.options.get("filament_multitool_ramming")),
|
||||
new OptionElement(def.options.get("filament_multitool_ramming_volume")),
|
||||
new OptionElement(def.options.get("filament_multitool_ramming_flow")),
|
||||
|
||||
new OptionElement(R.drawable.settings_outline_28, Slic3rLocalization.getString("Filament Overrides")),
|
||||
new OptionElement(new SubHeader("Travel lift")),
|
||||
new OptionElement(def.options.get("filament_retract_lift")),
|
||||
new OptionElement(def.options.get("filament_travel_ramping_lift")),
|
||||
new OptionElement(def.options.get("filament_travel_max_lift")),
|
||||
new OptionElement(def.options.get("filament_travel_slope")),
|
||||
new OptionElement(def.options.get("filament_travel_lift_before_obstacle")),
|
||||
new OptionElement(def.options.get("filament_retract_lift_above")),
|
||||
new OptionElement(def.options.get("filament_retract_lift_below")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Retraction")),
|
||||
new OptionElement(def.options.get("filament_retract_length")),
|
||||
new OptionElement(def.options.get("filament_retract_speed")),
|
||||
new OptionElement(def.options.get("filament_deretract_speed")),
|
||||
new OptionElement(def.options.get("filament_retract_restart_extra")),
|
||||
new OptionElement(def.options.get("filament_retract_before_travel")),
|
||||
new OptionElement(def.options.get("filament_retract_layer_change")),
|
||||
new OptionElement(def.options.get("filament_wipe")),
|
||||
new OptionElement(def.options.get("filament_retract_before_wipe")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Retraction when tool is disabled")),
|
||||
new OptionElement(def.options.get("filament_retract_length_toolchange")),
|
||||
new OptionElement(def.options.get("filament_retract_restart_extra_toolchange")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.settings_outline_28, Slic3rLocalization.getString("Custom G-code")),
|
||||
new OptionElement(new SubHeader("Start G-code")),
|
||||
new OptionElement(def.options.get("start_filament_gcode")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("End G-code")),
|
||||
new OptionElement(def.options.get("end_filament_gcode")),
|
||||
|
||||
new OptionElement(R.drawable.note_pen_outline_96, Slic3rLocalization.getString("Notes")),
|
||||
new OptionElement(new SubHeader("Notes")),
|
||||
new OptionElement(def.options.get("filament_notes")),
|
||||
|
||||
new OptionElement(R.drawable.wrench_outline_28, Slic3rLocalization.getString("Dependencies")),
|
||||
new OptionElement(new SubHeader("Profile dependencies")),
|
||||
new OptionElement(def.options.get("compatible_printers_condition")),
|
||||
new OptionElement(def.options.get("compatible_prints")),
|
||||
new OptionElement(def.options.get("compatible_prints_condition"))
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cloneCurrentProfile() {
|
||||
ConfigObject obj = new ConfigObject(SliceBeam.INSTANCE.getString(R.string.SettingsProfileCopy, currentConfig.getTitle()));
|
||||
obj.values.putAll(currentConfig.values);
|
||||
currentConfig = new ConfigObject(obj);
|
||||
|
||||
SliceBeam.CONFIG.filamentConfigs.add(obj);
|
||||
SliceBeam.CONFIG.presets.put("filament", obj.getTitle());
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
|
||||
currentConfig = new ConfigObject(obj);
|
||||
dropdownView.setTitle(getCurrentConfig().getTitle());
|
||||
compatItems = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteCurrentProfile() {
|
||||
compatItems = null;
|
||||
SliceBeam.CONFIG.filamentConfigs.remove(SliceBeam.CONFIG.findFilament(currentConfig.getTitle()));
|
||||
selectItem(getItems(true).get(0));
|
||||
|
||||
dropdownView.setTitle(getCurrentConfig().getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyConfig(String title) {
|
||||
compatItems = null;
|
||||
ConfigObject obj = SliceBeam.CONFIG.findFilament(currentConfig.getTitle());
|
||||
obj.setTitle(title);
|
||||
obj.values.putAll(currentConfig.values);
|
||||
currentConfig.setTitle(title);
|
||||
|
||||
SliceBeam.CONFIG.presets.put("filament", title);
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
|
||||
dropdownView.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResetConfig() {
|
||||
currentConfig = new ConfigObject(SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigObject getCurrentConfig() {
|
||||
return currentConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitle() {
|
||||
return R.string.SlotFilamentConfigTooltip;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void selectItem(ProfileListItem item) {
|
||||
currentConfig = new ConfigObject((ConfigObject) item);
|
||||
SliceBeam.CONFIG.presets.put("filament", item.getTitle());
|
||||
SliceBeam.saveConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
package ru.ytkab0bp.slicebeam.fragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rUtils;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class PrintConfigFragment extends ProfileListFragment {
|
||||
private List<ProfileListItem> compatItems;
|
||||
private String lastPrinter;
|
||||
private int lastUid;
|
||||
|
||||
private ConfigObject currentConfig;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
onResetConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ProfileListItem> getItems(boolean filter) {
|
||||
List<ConfigObject> list = SliceBeam.CONFIG.printConfigs;
|
||||
if (filter) {
|
||||
String printer = SliceBeam.CONFIG.presets.get("printer");
|
||||
if (Objects.equals(lastPrinter, printer) && compatItems != null && lastUid == SliceBeam.CONFIG_UID) {
|
||||
return compatItems;
|
||||
}
|
||||
|
||||
List<ConfigObject> nList = new ArrayList<>(list.size());
|
||||
Slic3rUtils.ConfigChecker checker = new Slic3rUtils.ConfigChecker(SliceBeam.CONFIG.findPrinter(printer).serialize());
|
||||
for (ConfigObject obj : list) {
|
||||
if (checker.checkCompatibility(obj.get("compatible_printers_condition"))) {
|
||||
nList.add(obj);
|
||||
}
|
||||
}
|
||||
checker.release();
|
||||
lastPrinter = printer;
|
||||
lastUid = SliceBeam.CONFIG_UID;
|
||||
return compatItems = (List) nList;
|
||||
}
|
||||
return (List) list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<OptionElement> getConfigItems() {
|
||||
PrintConfigDef def = PrintConfigDef.getInstance();
|
||||
return Arrays.asList(
|
||||
new OptionElement(R.drawable.print_layers_28, "Layers and perimeters"),
|
||||
new OptionElement(new SubHeader("Layer height")),
|
||||
new OptionElement(def.options.get("layer_height")),
|
||||
new OptionElement(def.options.get("first_layer_height")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Vertical shells")),
|
||||
new OptionElement(def.options.get("perimeters")),
|
||||
new OptionElement(def.options.get("spiral_vase")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Horizontal shells")),
|
||||
new OptionElement(def.options.get("top_solid_layers")),
|
||||
new OptionElement(def.options.get("bottom_solid_layers")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Minimum shell thickness")),
|
||||
new OptionElement(def.options.get("top_solid_min_thickness")),
|
||||
new OptionElement(def.options.get("bottom_solid_min_thickness")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Quality (slower slicing)")),
|
||||
new OptionElement(def.options.get("extra_perimeters")),
|
||||
new OptionElement(def.options.get("extra_perimeters_on_overhangs")),
|
||||
new OptionElement(def.options.get("avoid_crossing_curled_overhangs")),
|
||||
new OptionElement(def.options.get("avoid_crossing_perimeters")),
|
||||
new OptionElement(def.options.get("avoid_crossing_perimeters_max_detour")),
|
||||
new OptionElement(def.options.get("thin_walls")),
|
||||
new OptionElement(def.options.get("thick_bridges")),
|
||||
new OptionElement(def.options.get("overhangs")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Advanced")),
|
||||
new OptionElement(def.options.get("seam_position")),
|
||||
new OptionElement(def.options.get("staggered_inner_seams")),
|
||||
new OptionElement(def.options.get("external_perimeters_first")),
|
||||
new OptionElement(def.options.get("gap_fill_enabled")),
|
||||
new OptionElement(def.options.get("perimeter_generator")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Fuzzy skin (experimental)")),
|
||||
new OptionElement(def.options.get("fuzzy_skin")),
|
||||
new OptionElement(def.options.get("fuzzy_skin_thickness")),
|
||||
new OptionElement(def.options.get("fuzzy_skin_point_dist")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Only one perimeter")),
|
||||
new OptionElement(def.options.get("top_one_perimeter_type")),
|
||||
new OptionElement(def.options.get("only_one_perimeter_first_layer")),
|
||||
|
||||
new OptionElement(R.drawable.print_infill_28, "Infill"),
|
||||
new OptionElement(new SubHeader("Infill")),
|
||||
new OptionElement(def.options.get("fill_density")),
|
||||
new OptionElement(def.options.get("fill_pattern")),
|
||||
new OptionElement(def.options.get("infill_anchor")),
|
||||
new OptionElement(def.options.get("infill_anchor_max")),
|
||||
new OptionElement(def.options.get("top_fill_pattern")),
|
||||
new OptionElement(def.options.get("bottom_fill_pattern")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Ironing")),
|
||||
new OptionElement(def.options.get("ironing")),
|
||||
new OptionElement(def.options.get("ironing_type")),
|
||||
new OptionElement(def.options.get("ironing_flowrate")),
|
||||
new OptionElement(def.options.get("ironing_spacing")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Reducing printing time")),
|
||||
new OptionElement(def.options.get("infill_every_layers")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Advanced")),
|
||||
new OptionElement(def.options.get("solid_infill_every_layers")),
|
||||
new OptionElement(def.options.get("fill_angle")),
|
||||
new OptionElement(def.options.get("solid_infill_below_area")),
|
||||
new OptionElement(def.options.get("bridge_angle")),
|
||||
new OptionElement(def.options.get("only_retract_when_crossing_perimeters")),
|
||||
new OptionElement(def.options.get("infill_first")),
|
||||
|
||||
new OptionElement(R.drawable.print_skirt_28, "Skirt and brim"),
|
||||
new OptionElement(new SubHeader("Skirt")),
|
||||
new OptionElement(def.options.get("skirts")),
|
||||
new OptionElement(def.options.get("skirt_distance")),
|
||||
new OptionElement(def.options.get("skirt_height")),
|
||||
new OptionElement(def.options.get("draft_shield")),
|
||||
new OptionElement(def.options.get("min_skirt_length")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Brim")),
|
||||
new OptionElement(def.options.get("brim_type")),
|
||||
new OptionElement(def.options.get("brim_width")),
|
||||
new OptionElement(def.options.get("brim_separation")),
|
||||
|
||||
new OptionElement(R.drawable.print_support_28, "Support material"),
|
||||
new OptionElement(new SubHeader("Support material")),
|
||||
new OptionElement(def.options.get("support_material")),
|
||||
new OptionElement(def.options.get("support_material_auto")),
|
||||
new OptionElement(def.options.get("support_material_threshold")),
|
||||
new OptionElement(def.options.get("support_material_enforce_layers")),
|
||||
new OptionElement(def.options.get("raft_first_layer_density")),
|
||||
new OptionElement(def.options.get("raft_first_layer_expansion")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Raft")),
|
||||
new OptionElement(def.options.get("raft_layers")),
|
||||
new OptionElement(def.options.get("raft_contact_distance")),
|
||||
new OptionElement(def.options.get("raft_expansion")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Options for support material and raft")),
|
||||
new OptionElement(def.options.get("support_material_style")),
|
||||
new OptionElement(def.options.get("support_material_contact_distance")),
|
||||
new OptionElement(def.options.get("support_material_bottom_contact_distance")),
|
||||
new OptionElement(def.options.get("support_material_pattern")),
|
||||
new OptionElement(def.options.get("support_material_with_sheath")),
|
||||
new OptionElement(def.options.get("support_material_spacing")),
|
||||
new OptionElement(def.options.get("support_material_angle")),
|
||||
new OptionElement(def.options.get("support_material_closing_radius")),
|
||||
new OptionElement(def.options.get("support_material_interface_layers")),
|
||||
new OptionElement(def.options.get("support_material_bottom_interface_layers")),
|
||||
new OptionElement(def.options.get("support_material_interface_pattern")),
|
||||
new OptionElement(def.options.get("support_material_interface_spacing")),
|
||||
new OptionElement(def.options.get("support_material_interface_contact_loops")),
|
||||
new OptionElement(def.options.get("support_material_buildplate_only")),
|
||||
new OptionElement(def.options.get("support_material_xy_spacing")),
|
||||
new OptionElement(def.options.get("dont_support_bridges")),
|
||||
new OptionElement(def.options.get("support_material_synchronize_layers")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Organic supports")),
|
||||
new OptionElement(def.options.get("support_tree_angle")),
|
||||
new OptionElement(def.options.get("support_tree_angle_slow")),
|
||||
new OptionElement(def.options.get("support_tree_branch_diameter")),
|
||||
new OptionElement(def.options.get("support_tree_branch_diameter_angle")),
|
||||
new OptionElement(def.options.get("support_tree_branch_diameter_double_wall")),
|
||||
new OptionElement(def.options.get("support_tree_tip_diameter")),
|
||||
new OptionElement(def.options.get("support_tree_branch_distance")),
|
||||
new OptionElement(def.options.get("support_tree_top_rate")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.clock_outline_28, "Speed"),
|
||||
new OptionElement(new SubHeader("Speed for print moves")),
|
||||
new OptionElement(def.options.get("perimeter_speed")),
|
||||
new OptionElement(def.options.get("small_perimeter_speed")),
|
||||
new OptionElement(def.options.get("external_perimeter_speed")),
|
||||
new OptionElement(def.options.get("infill_speed")),
|
||||
new OptionElement(def.options.get("solid_infill_speed")),
|
||||
new OptionElement(def.options.get("top_solid_infill_speed")),
|
||||
new OptionElement(def.options.get("support_material_speed")),
|
||||
new OptionElement(def.options.get("support_material_interface_speed")),
|
||||
new OptionElement(def.options.get("bridge_speed")),
|
||||
new OptionElement(def.options.get("gap_fill_speed")),
|
||||
new OptionElement(def.options.get("ironing_speed")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Dynamic overhang speed")),
|
||||
new OptionElement(def.options.get("overhang_speed_0")),
|
||||
new OptionElement(def.options.get("overhang_speed_1")),
|
||||
new OptionElement(def.options.get("overhang_speed_2")),
|
||||
new OptionElement(def.options.get("overhang_speed_3")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Speed for non-print moves")),
|
||||
new OptionElement(def.options.get("travel_speed")),
|
||||
new OptionElement(def.options.get("travel_speed_z")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Modifiers")),
|
||||
new OptionElement(def.options.get("first_layer_speed")),
|
||||
new OptionElement(def.options.get("first_layer_speed_over_raft")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Acceleration control (advanced)")),
|
||||
new OptionElement(def.options.get("external_perimeter_acceleration")),
|
||||
new OptionElement(def.options.get("top_solid_infill_acceleration")),
|
||||
new OptionElement(def.options.get("solid_infill_acceleration")),
|
||||
new OptionElement(def.options.get("infill_acceleration")),
|
||||
new OptionElement(def.options.get("bridge_acceleration")),
|
||||
new OptionElement(def.options.get("first_layer_acceleration")),
|
||||
new OptionElement(def.options.get("first_layer_acceleration_over_raft")),
|
||||
new OptionElement(def.options.get("wipe_tower_acceleration")),
|
||||
new OptionElement(def.options.get("travel_acceleration")),
|
||||
new OptionElement(def.options.get("default_acceleration")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Autospeed (advanced)")),
|
||||
new OptionElement(def.options.get("max_print_speed")),
|
||||
new OptionElement(def.options.get("max_volumetric_speed")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Pressure equalizer (experimental)")),
|
||||
new OptionElement(def.options.get("max_volumetric_extrusion_rate_slope_positive")),
|
||||
new OptionElement(def.options.get("max_volumetric_extrusion_rate_slope_negative")),
|
||||
|
||||
new OptionElement(R.drawable.hashtag_outline_28, "Multiple Extruders"),
|
||||
new OptionElement(new SubHeader("Extruders")),
|
||||
new OptionElement(def.options.get("perimeter_extruder")),
|
||||
new OptionElement(def.options.get("infill_extruder")),
|
||||
new OptionElement(def.options.get("solid_infill_extruder")),
|
||||
new OptionElement(def.options.get("support_material_extruder")),
|
||||
new OptionElement(def.options.get("support_material_interface_extruder")),
|
||||
new OptionElement(def.options.get("wipe_tower_extruder")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Ooze prevention")),
|
||||
new OptionElement(def.options.get("ooze_prevention")),
|
||||
new OptionElement(def.options.get("standby_temperature_delta")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Wipe tower")),
|
||||
new OptionElement(def.options.get("wipe_tower")),
|
||||
new OptionElement(def.options.get("wipe_tower_x")),
|
||||
new OptionElement(def.options.get("wipe_tower_y")),
|
||||
new OptionElement(def.options.get("wipe_tower_width")),
|
||||
new OptionElement(def.options.get("wipe_tower_rotation_angle")),
|
||||
new OptionElement(def.options.get("wipe_tower_brim_width")),
|
||||
new OptionElement(def.options.get("wipe_tower_bridging")),
|
||||
new OptionElement(def.options.get("wipe_tower_cone_angle")),
|
||||
new OptionElement(def.options.get("wipe_tower_extra_spacing")),
|
||||
new OptionElement(def.options.get("wipe_tower_extra_flow")),
|
||||
new OptionElement(def.options.get("wipe_tower_no_sparse_layers")),
|
||||
new OptionElement(def.options.get("single_extruder_multi_material_priming")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Advanced")),
|
||||
new OptionElement(def.options.get("interface_shells")),
|
||||
new OptionElement(def.options.get("mmu_segmented_region_max_width")),
|
||||
new OptionElement(def.options.get("mmu_segmented_region_interlocking_depth")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.settings_outline_28, "Advanced"),
|
||||
new OptionElement(new SubHeader("Extrusion width")),
|
||||
new OptionElement(def.options.get("extrusion_width")),
|
||||
new OptionElement(def.options.get("first_layer_extrusion_width")),
|
||||
new OptionElement(def.options.get("perimeter_extrusion_width")),
|
||||
new OptionElement(def.options.get("external_perimeter_extrusion_width")),
|
||||
new OptionElement(def.options.get("infill_extrusion_width")),
|
||||
new OptionElement(def.options.get("solid_infill_extrusion_width")),
|
||||
new OptionElement(def.options.get("top_infill_extrusion_width")),
|
||||
new OptionElement(def.options.get("support_material_extrusion_width")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Overlap")),
|
||||
new OptionElement(def.options.get("infill_overlap")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Flow")),
|
||||
new OptionElement(def.options.get("bridge_flow_ratio")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Slicing")),
|
||||
new OptionElement(def.options.get("slice_closing_radius")),
|
||||
new OptionElement(def.options.get("slicing_mode")),
|
||||
new OptionElement(def.options.get("resolution")),
|
||||
new OptionElement(def.options.get("gcode_resolution")),
|
||||
new OptionElement(def.options.get("arc_fitting")),
|
||||
// TODO: Support x_size_compensation/y_size_compensation instead (I'm too lazy to write description for now)
|
||||
// new OptionElement(def.options.get("x_size_compensation")),
|
||||
// new OptionElement(def.options.get("y_size_compensation")),
|
||||
new OptionElement(def.options.get("xy_size_compensation")),
|
||||
new OptionElement(def.options.get("elefant_foot_compensation")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Arachne perimeter generator")),
|
||||
new OptionElement(def.options.get("wall_transition_angle")),
|
||||
new OptionElement(def.options.get("wall_transition_filter_deviation")),
|
||||
new OptionElement(def.options.get("wall_transition_length")),
|
||||
new OptionElement(def.options.get("wall_distribution_count")),
|
||||
new OptionElement(def.options.get("min_bead_width")),
|
||||
new OptionElement(def.options.get("min_feature_size")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.door_arrow_right_outline_28, "Output options"),
|
||||
new OptionElement(new SubHeader("Sequential printing")),
|
||||
new OptionElement(def.options.get("complete_objects")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Extruder clearance")),
|
||||
new OptionElement(def.options.get("extruder_clearance_radius")),
|
||||
new OptionElement(def.options.get("extruder_clearance_height")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Output file")),
|
||||
new OptionElement(def.options.get("gcode_comments")),
|
||||
new OptionElement(def.options.get("gcode_label_objects")),
|
||||
new OptionElement(def.options.get("output_filename_format")),
|
||||
|
||||
new OptionElement(R.drawable.note_pen_outline_96, "Notes"),
|
||||
new OptionElement(new SubHeader("Notes")),
|
||||
new OptionElement(def.options.get("notes")),
|
||||
|
||||
new OptionElement(R.drawable.wrench_outline_28, "Dependencies"),
|
||||
new OptionElement(new SubHeader("Profile dependencies")),
|
||||
new OptionElement(def.options.get("compatible_printers")),
|
||||
new OptionElement(def.options.get("compatible_printers_condition"))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cloneCurrentProfile() {
|
||||
ConfigObject obj = new ConfigObject(SliceBeam.INSTANCE.getString(R.string.SettingsProfileCopy, currentConfig.getTitle()));
|
||||
obj.values.putAll(currentConfig.values);
|
||||
currentConfig = new ConfigObject(obj);
|
||||
|
||||
SliceBeam.CONFIG.printConfigs.add(obj);
|
||||
SliceBeam.CONFIG.presets.put("print", obj.getTitle());
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
|
||||
currentConfig = new ConfigObject(obj);
|
||||
dropdownView.setTitle(getCurrentConfig().getTitle());
|
||||
compatItems = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteCurrentProfile() {
|
||||
compatItems = null;
|
||||
SliceBeam.CONFIG.printConfigs.remove(SliceBeam.CONFIG.findPrint(currentConfig.getTitle()));
|
||||
selectItem(getItems(true).get(0));
|
||||
|
||||
dropdownView.setTitle(getCurrentConfig().getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyConfig(String title) {
|
||||
compatItems = null;
|
||||
ConfigObject obj = SliceBeam.CONFIG.findPrint(currentConfig.getTitle());
|
||||
obj.setTitle(title);
|
||||
obj.values.putAll(currentConfig.values);
|
||||
currentConfig.setTitle(title);
|
||||
|
||||
SliceBeam.CONFIG.presets.put("print", title);
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
|
||||
dropdownView.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResetConfig() {
|
||||
ConfigObject print = SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print"));
|
||||
if (print != null) {
|
||||
currentConfig = new ConfigObject(print);
|
||||
} else {
|
||||
currentConfig = new ConfigObject(SliceBeam.INSTANCE.getString(R.string.IntroCustomProfileName));
|
||||
SliceBeam.CONFIG.printConfigs.add(new ConfigObject(currentConfig));
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigObject getCurrentConfig() {
|
||||
return currentConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitle() {
|
||||
return R.string.SlotPrintConfigTooltip;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void selectItem(ProfileListItem item) {
|
||||
currentConfig = new ConfigObject((ConfigObject) item);
|
||||
SliceBeam.CONFIG.presets.put("print", item.getTitle());
|
||||
SliceBeam.saveConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package ru.ytkab0bp.slicebeam.fragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class PrinterConfigFragment extends ProfileListFragment {
|
||||
private ConfigObject currentConfig;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
onResetConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ProfileListItem> getItems(boolean filter) {
|
||||
return (List) SliceBeam.CONFIG.printerConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<OptionElement> getConfigItems() {
|
||||
PrintConfigDef def = PrintConfigDef.getInstance();
|
||||
ArrayList<OptionElement> list = new ArrayList<>(Arrays.asList(
|
||||
new OptionElement(R.drawable.printer_outline_28, "General"),
|
||||
new OptionElement(new SubHeader("Size and coordinates")),
|
||||
new OptionElement(def.options.get("bed_shape")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(def.options.get("max_print_height")),
|
||||
new OptionElement(def.options.get("z_offset")),
|
||||
|
||||
new OptionElement(new SubHeader("Capabilities")),
|
||||
// TODO: Extruders setting
|
||||
new OptionElement(def.options.get("single_extruder_multi_material")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Firmware")),
|
||||
new OptionElement(def.options.get("gcode_flavor")),
|
||||
// TODO: Thumbnails are not working *yet*
|
||||
// new OptionElement(def.options.get("thumbnails")),
|
||||
new OptionElement(def.options.get("silent_mode")),
|
||||
new OptionElement(def.options.get("remaining_times")),
|
||||
new OptionElement(def.options.get("binary_gcode")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Advanced")),
|
||||
new OptionElement(def.options.get("use_relative_e_distances")),
|
||||
new OptionElement(def.options.get("use_firmware_retraction")),
|
||||
new OptionElement(def.options.get("use_volumetric_e")),
|
||||
new OptionElement(def.options.get("variable_layer_height")),
|
||||
new OptionElement(def.options.get("prefer_clockwise_movements")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(R.drawable.settings_outline_28, "Custom G-code"),
|
||||
new OptionElement(new SubHeader("Start G-code")),
|
||||
new OptionElement(def.options.get("start_gcode")),
|
||||
new OptionElement(def.options.get("autoemit_temperature_commands")),
|
||||
|
||||
new OptionElement(new SubHeader("End G-code")),
|
||||
new OptionElement(def.options.get("end_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("Before layer change G-code")),
|
||||
new OptionElement(def.options.get("before_layer_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("After layer change G-code")),
|
||||
new OptionElement(def.options.get("layer_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("Tool change G-code")),
|
||||
new OptionElement(def.options.get("toolchange_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("Between objects G-code (for sequential printing)")),
|
||||
new OptionElement(def.options.get("between_objects_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("Color Change G-code")),
|
||||
new OptionElement(def.options.get("color_change_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("Pause Print G-code")),
|
||||
new OptionElement(def.options.get("pause_print_gcode")),
|
||||
|
||||
new OptionElement(new SubHeader("Template Custom G-code")),
|
||||
new OptionElement(def.options.get("template_custom_gcode")),
|
||||
|
||||
new OptionElement(R.drawable.note_pen_outline_96, "Machine limits"),
|
||||
new OptionElement(new SubHeader("General")),
|
||||
new OptionElement(def.options.get("machine_limits_usage")),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Maximum feedrates")),
|
||||
new OptionElement(def.options.get("machine_max_acceleration_x")),
|
||||
new OptionElement(def.options.get("machine_max_acceleration_y")),
|
||||
new OptionElement(def.options.get("machine_max_acceleration_z")),
|
||||
new OptionElement(def.options.get("machine_max_acceleration_e")),
|
||||
new OptionElement(def.options.get("machine_max_acceleration_extruding")),
|
||||
new OptionElement(def.options.get("machine_max_acceleration_retracting")),
|
||||
// TODO: m_supports_travel_acceleration? new OptionElement(def.options.get("machine_max_acceleration_travel")) <= repetier/reprap/marlin
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Jerk limits")),
|
||||
new OptionElement(def.options.get("machine_max_jerk_x")),
|
||||
new OptionElement(def.options.get("machine_max_jerk_y")),
|
||||
new OptionElement(def.options.get("machine_max_jerk_z")),
|
||||
new OptionElement(def.options.get("machine_max_jerk_e"))
|
||||
|
||||
// TODO: m_supports_min_feedrates? <= marlin/marlin legacy
|
||||
));
|
||||
|
||||
int count = currentConfig.get("nozzle_diameter") != null ? currentConfig.get("nozzle_diameter").replaceAll("[^.]+", "").length() : 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int j = count == 1 ? -1 : i;
|
||||
list.addAll(Arrays.asList(
|
||||
new OptionElement(R.drawable.hashtag_outline_28, String.format(Slic3rLocalization.getString("Extruder %d"), i + 1)),
|
||||
new OptionElement(new SubHeader("Size")),
|
||||
new OptionElement(def.options.get("nozzle_diameter"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Preview")),
|
||||
new OptionElement(def.options.get("extruder_colour"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Layer height limits")),
|
||||
new OptionElement(def.options.get("min_layer_height"), j),
|
||||
new OptionElement(def.options.get("max_layer_height"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Position (for multi-extruder printers)")),
|
||||
new OptionElement(def.options.get("extruder_offset"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Travel lift")),
|
||||
new OptionElement(def.options.get("retract_lift"), j),
|
||||
new OptionElement(def.options.get("travel_ramping_lift"), j),
|
||||
new OptionElement(def.options.get("travel_max_lift"), j),
|
||||
new OptionElement(def.options.get("travel_slope"), j),
|
||||
new OptionElement(def.options.get("travel_lift_before_obstacle"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Only lift")),
|
||||
new OptionElement(def.options.get("retract_lift_above"), j),
|
||||
new OptionElement(def.options.get("retract_lift_below"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Retraction")),
|
||||
new OptionElement(def.options.get("retract_length"), j),
|
||||
new OptionElement(def.options.get("retract_speed"), j),
|
||||
new OptionElement(def.options.get("deretract_speed"), j),
|
||||
new OptionElement(def.options.get("retract_restart_extra"), j),
|
||||
new OptionElement(def.options.get("retract_before_travel"), j),
|
||||
new OptionElement(def.options.get("retract_layer_change"), j),
|
||||
new OptionElement(def.options.get("wipe"), j),
|
||||
new OptionElement(def.options.get("retract_before_wipe"), j),
|
||||
new OptionElement(new SpaceItem(0, ViewUtils.dp(4))),
|
||||
|
||||
new OptionElement(new SubHeader("Retraction when tool is disabled (advanced settings for multi-extruder setups)")),
|
||||
new OptionElement(def.options.get("retract_length_toolchange"), j),
|
||||
new OptionElement(def.options.get("retract_restart_extra_toolchange"), j)
|
||||
));
|
||||
}
|
||||
list.addAll(Arrays.asList(
|
||||
new OptionElement(R.drawable.note_pen_outline_96, "Notes"),
|
||||
new OptionElement(new SubHeader("Notes")),
|
||||
new OptionElement(def.options.get("printer_notes")),
|
||||
|
||||
new OptionElement(R.drawable.power_socket_outline_28, "Physical Printer"),
|
||||
new OptionElement(new SubHeader("Print Host upload")),
|
||||
new OptionElement(def.options.get("host_type")),
|
||||
new OptionElement(def.options.get("print_host")),
|
||||
new OptionElement(def.options.get("printhost_apikey"))
|
||||
));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cloneCurrentProfile() {
|
||||
ConfigObject obj = new ConfigObject(SliceBeam.INSTANCE.getString(R.string.SettingsProfileCopy, currentConfig.getTitle()));
|
||||
obj.values.putAll(currentConfig.values);
|
||||
currentConfig = new ConfigObject(obj);
|
||||
|
||||
SliceBeam.CONFIG.printerConfigs.add(obj);
|
||||
SliceBeam.CONFIG.presets.put("printer", obj.getTitle());
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
|
||||
currentConfig = new ConfigObject(obj);
|
||||
dropdownView.setTitle(getCurrentConfig().getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteCurrentProfile() {
|
||||
SliceBeam.CONFIG.printerConfigs.remove(SliceBeam.CONFIG.findPrinter(currentConfig.getTitle()));
|
||||
selectItem(getItems(true).get(0));
|
||||
dropdownView.setTitle(getCurrentConfig().getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyConfig(String title) {
|
||||
ConfigObject obj = SliceBeam.CONFIG.findPrinter(currentConfig.getTitle());
|
||||
obj.setTitle(title);
|
||||
obj.values.putAll(currentConfig.values);
|
||||
currentConfig.setTitle(title);
|
||||
|
||||
SliceBeam.CONFIG.presets.put("printer", title);
|
||||
SliceBeam.saveConfig();
|
||||
SliceBeam.getCurrentConfigFile().delete();
|
||||
|
||||
dropdownView.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResetConfig() {
|
||||
currentConfig = new ConfigObject(SliceBeam.CONFIG.findPrinter(SliceBeam.CONFIG.presets.get("printer")));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigObject getCurrentConfig() {
|
||||
return currentConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitle() {
|
||||
return R.string.SlotPrinterConfigTooltip;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void selectItem(ProfileListItem item) {
|
||||
currentConfig = new ConfigObject((ConfigObject) item);
|
||||
SliceBeam.CONFIG.presets.put("printer", item.getTitle());
|
||||
|
||||
// TODO: Reset print/filament profiles, maybe physical profiles?
|
||||
SliceBeam.saveConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
package ru.ytkab0bp.slicebeam.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mrudultora.colorpicker.ColorPickerPopUp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamColorPickerPopUp;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.navigation.Fragment;
|
||||
import ru.ytkab0bp.slicebeam.recycler.CubicBezierItemAnimator;
|
||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceSwitchItem;
|
||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.ConfigOptionDef;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
|
||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.BeamButton;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
||||
import ru.ytkab0bp.slicebeam.view.ProfileDropdownView;
|
||||
|
||||
public abstract class ProfileListFragment extends Fragment {
|
||||
private final static Object ROTATION_PAYLOAD = new Object();
|
||||
|
||||
protected ProfileDropdownView dropdownView;
|
||||
protected FadeRecyclerView recyclerView;
|
||||
protected ImageView resetButton;
|
||||
protected BeamButton saveButton;
|
||||
|
||||
protected boolean changedConfig;
|
||||
|
||||
private List<OptionWrapper> currentList = Collections.emptyList();
|
||||
private SparseArray<List<OptionWrapper>> categoryElements = new SparseArray<>();
|
||||
private SparseBooleanArray unfolded = new SparseBooleanArray();
|
||||
|
||||
@Override
|
||||
public View onCreateView(Context ctx) {
|
||||
FrameLayout containerLayout = new FrameLayout(ctx);
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
dropdownView = new ProfileDropdownView(ctx);
|
||||
ProfileListItem selectedItem = getSelectedItem();
|
||||
dropdownView.setTitle(selectedItem != null ? selectedItem.getTitle() : null);
|
||||
dropdownView.setOnClickListener(v -> {
|
||||
List<ProfileListItem> items = getItems(true);
|
||||
String[] titles = new String[items.size()];
|
||||
int selected = -1;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ProfileListItem item = items.get(i);
|
||||
titles[i] = item.getTitle();
|
||||
if (item.isSelected()) {
|
||||
selected = i;
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new BeamAlertDialogBuilder(getContext())
|
||||
.setTitle(getTitle())
|
||||
.setSingleChoiceItems(titles, selected, (dialog, which) -> {
|
||||
dropdownView.setTitle(items.get(which).getTitle());
|
||||
selectItem(items.get(which));
|
||||
onUpdateConfigItems();
|
||||
dialog.dismiss();
|
||||
});
|
||||
if (items.size() > 1) {
|
||||
builder.setNegativeButton(R.string.SettingsDeleteProfile, (dialog, which) -> {
|
||||
deleteCurrentProfile();
|
||||
onUpdateConfigItems();
|
||||
});
|
||||
}
|
||||
builder.setPositiveButton(R.string.SettingsCloneProfile, (dialog, which) -> {
|
||||
cloneCurrentProfile();
|
||||
onUpdateConfigItems();
|
||||
});
|
||||
builder.show();
|
||||
});
|
||||
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
||||
boolean portrait = dm.widthPixels < dm.heightPixels;
|
||||
ll.addView(dropdownView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(48)) {{
|
||||
topMargin = portrait ? 0 : ViewUtils.dp(12);
|
||||
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||
bottomMargin = ViewUtils.dp(8);
|
||||
}});
|
||||
recyclerView = new FadeRecyclerView(ctx) {
|
||||
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
{
|
||||
onApplyTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c) {
|
||||
super.onDraw(c);
|
||||
|
||||
int startI = -1;
|
||||
boolean drawn = false;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View ch = getChildAt(i);
|
||||
int pos = getChildViewHolder(ch).getAdapterPosition();
|
||||
if (pos == -1 || ch.getAlpha() < 1) continue;
|
||||
|
||||
boolean top = currentList.get(pos).title != null;
|
||||
boolean bottom = pos == getAdapter().getItemCount() - 1 || currentList.get(pos + 1).title != null;
|
||||
|
||||
if (top && startI != -1) {
|
||||
c.drawRoundRect(0, getChildAt(startI).getTop() + getChildAt(startI).getTranslationY(), getWidth(), ch.getTop() + ch.getTranslationY() - ViewUtils.dp(8), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
||||
drawn = true;
|
||||
startI = -1;
|
||||
} else if (bottom) {
|
||||
if (!top && startI == -1) {
|
||||
c.drawRoundRect(0, -ViewUtils.dp(32), getWidth(), ch.getBottom() + ch.getTranslationY(), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
||||
drawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (top) {
|
||||
int color = ch.getTag() != null ? ThemesRepo.getColor((Integer) ch.getTag()) : 0;
|
||||
if (color != 0) {
|
||||
bgPaint.setColor(ColorUtils.setAlphaComponent(color, 0x22));
|
||||
} else {
|
||||
bgPaint.setColor(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x10));
|
||||
}
|
||||
startI = i;
|
||||
}
|
||||
|
||||
if (startI != -1 && bottom && pos == getAdapter().getItemCount() - 1) {
|
||||
View s = getChildAt(startI);
|
||||
c.drawRoundRect(0, s.getTop() + s.getTranslationY(), getWidth(), ch.getBottom() + ch.getTranslationY(), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
||||
drawn = true;
|
||||
startI = -1;
|
||||
}
|
||||
}
|
||||
if (startI != -1) {
|
||||
View ch = getChildAt(startI);
|
||||
View last = getChildAt(getChildCount() - 1);
|
||||
boolean bottom = getChildViewHolder(last).getAdapterPosition() == getAdapter().getItemCount() - 1;
|
||||
|
||||
c.drawRoundRect(0, ch.getTop() + ch.getTranslationY(), getWidth(), bottom ? ViewUtils.lerp(ch.getBottom(), last.getBottom(), last.getAlpha()) : getHeight() + ViewUtils.dp(32), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
||||
drawn = true;
|
||||
}
|
||||
|
||||
if (!drawn) {
|
||||
c.drawRoundRect(0, -ViewUtils.dp(32), getWidth(), getHeight() + ViewUtils.dp(32), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
||||
}
|
||||
|
||||
// TODO: Determine when user folds/unfolds category, animate only there
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
super.onApplyTheme();
|
||||
if (bgPaint != null) {
|
||||
bgPaint.setColor(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x10));
|
||||
}
|
||||
}
|
||||
};
|
||||
recyclerView.setItemAnimator(new CubicBezierItemAnimator());
|
||||
recyclerView.setAdapter(new RecyclerView.Adapter() {
|
||||
private final static int TYPE_TITLE = 0, TYPE_SIMPLE = 1;
|
||||
|
||||
private Map<Class<?>, Integer> viewType = new HashMap<>();
|
||||
private Map<Integer, SimpleRecyclerItem> viewCreator = new HashMap<>();
|
||||
private int lastType = TYPE_SIMPLE;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v;
|
||||
switch (viewType) {
|
||||
default: {
|
||||
v = viewCreator.get(viewType).onCreateView(ctx);
|
||||
break;
|
||||
}
|
||||
case TYPE_TITLE:
|
||||
v = new CategoryHolderView(ctx);
|
||||
break;
|
||||
}
|
||||
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return new RecyclerView.ViewHolder(v) {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) {
|
||||
if (payloads.contains(ROTATION_PAYLOAD)) {
|
||||
CategoryHolderView holderView = (CategoryHolderView) holder.itemView;
|
||||
OptionWrapper w = currentList.get(position);
|
||||
new SpringAnimation(holderView.dropdown, DynamicAnimation.ROTATION)
|
||||
.setSpring(new SpringForce(unfolded.get(w.categoryIndex) ? 180 : 0)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.start();
|
||||
return;
|
||||
}
|
||||
super.onBindViewHolder(holder, position, payloads);
|
||||
}
|
||||
|
||||
@SuppressLint("RecyclerView")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
|
||||
boolean top = position == 0 || currentList.get(position).title != null;
|
||||
params.topMargin = top ? ViewUtils.dp(8) : 0;
|
||||
params.bottomMargin = position == getItemCount() - 1 ? ViewUtils.dp(8) : 0;
|
||||
|
||||
int type = getItemViewType(position);
|
||||
switch (type) {
|
||||
default: {
|
||||
OptionElement el = currentList.get(position).optionEl;
|
||||
el.boundIndex = position;
|
||||
el.simpleItem.onBindView(holder.itemView);
|
||||
break;
|
||||
}
|
||||
case TYPE_TITLE: {
|
||||
OptionWrapper w = currentList.get(position);
|
||||
CategoryHolderView holderView = (CategoryHolderView) holder.itemView;
|
||||
holderView.icon.setImageResource(w.icon);
|
||||
holderView.title.setText(Slic3rLocalization.getString(w.title));
|
||||
holderView.dropdown.setRotation(unfolded.get(w.categoryIndex) ? 180 : 0);
|
||||
holderView.dropdown.setVisibility(w.onClick != null ? View.GONE : View.VISIBLE);
|
||||
holderView.setTag(w.color);
|
||||
holderView.icon.setTag(w.noTint ? true : null);
|
||||
holderView.onApplyTheme();
|
||||
|
||||
holderView.setOnClickListener(v -> {
|
||||
if (w.onClick != null) {
|
||||
w.onClick.run();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean unfold = !unfolded.get(w.categoryIndex, false);
|
||||
unfolded.put(w.categoryIndex, unfold);
|
||||
notifyItemChanged(holder.getAdapterPosition(), ROTATION_PAYLOAD);
|
||||
|
||||
int i = holder.getAdapterPosition() + 1;
|
||||
List<OptionWrapper> l = categoryElements.get(w.categoryIndex);
|
||||
if (l != null) {
|
||||
if (unfold) {
|
||||
currentList.addAll(i, l);
|
||||
notifyItemRangeInserted(i, l.size());
|
||||
recyclerView.invalidate();
|
||||
} else {
|
||||
currentList.removeAll(l);
|
||||
notifyItemRangeRemoved(i, l.size());
|
||||
recyclerView.invalidate();
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
OptionWrapper w = currentList.get(position);
|
||||
if (w.title != null) return TYPE_TITLE;
|
||||
|
||||
if (w.optionEl.simpleItem != null) {
|
||||
SimpleRecyclerItem it = w.optionEl.simpleItem;
|
||||
Integer t = viewType.get(it.getClass());
|
||||
if (t == null) {
|
||||
viewType.put(it.getClass(), t = lastType++);
|
||||
viewCreator.put(t, it);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return currentList.size();
|
||||
}
|
||||
});
|
||||
setConfigItems(getConfigItems());
|
||||
ll.addView(recyclerView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
LinearLayout bottomLL = new LinearLayout(ctx);
|
||||
bottomLL.setOrientation(LinearLayout.HORIZONTAL);
|
||||
bottomLL.setGravity(Gravity.CENTER_VERTICAL);
|
||||
saveButton = new BeamButton(ctx);
|
||||
saveButton.setText(R.string.SettingsSave);
|
||||
saveButton.setPadding(ViewUtils.dp(21), ViewUtils.dp(12), ViewUtils.dp(21), ViewUtils.dp(12));
|
||||
|
||||
saveButton.setOnClickListener(v -> {
|
||||
FrameLayout fl = new FrameLayout(ctx);
|
||||
EditText text = new EditText(ctx);
|
||||
text.setText(getCurrentConfig().getTitle());
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
fl.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
|
||||
AlertDialog dialog = new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.SettingsSaveTitle)
|
||||
// TODO: Draw settings delta
|
||||
.setView(fl)
|
||||
.setPositiveButton(android.R.string.ok, (d, which) -> {
|
||||
onApplyConfig(text.getText().toString());
|
||||
resetButton.animate().alpha(0.6f).start();
|
||||
resetButton.setClickable(false);
|
||||
onUpdateConfigItems();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
text.addTextChangedListener(new TextWatcher() {
|
||||
char[] chars = Slic3rConfigWrapper.BLACKLISTED_SYMBOLS.toCharArray();
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
String str = s.toString();
|
||||
boolean valid = true;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char ch = str.charAt(i);
|
||||
for (char aChar : chars) {
|
||||
if (ch == aChar) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!valid) break;
|
||||
}
|
||||
text.getBackground().setTintList(ColorStateList.valueOf(ThemesRepo.getColor(valid ? android.R.attr.textColorPrimary : R.attr.textColorNegative)));
|
||||
View btn = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
btn.setAlpha(valid ? 1f : 0.6f);
|
||||
btn.setClickable(valid);
|
||||
}
|
||||
});
|
||||
// I don't think we need keyboard here every time
|
||||
// ViewUtils.postOnMainThread(() -> {
|
||||
// text.requestFocus();
|
||||
// InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
// imm.showSoftInput(text, 0);
|
||||
// text.setSelection(text.getText().length());
|
||||
// }, 500);
|
||||
});
|
||||
bottomLL.addView(saveButton, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
|
||||
resetButton = new ImageView(ctx);
|
||||
resetButton.setImageResource(R.drawable.refresh_outline_28);
|
||||
resetButton.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorPrimary)));
|
||||
resetButton.setScaleX(-1f);
|
||||
resetButton.setAlpha(0.6f);
|
||||
resetButton.setOnClickListener(v -> new BeamAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.SettingsResetProfileTitle)
|
||||
.setMessage(R.string.SettingsResetProfileDescription)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
onResetConfig();
|
||||
resetButton.animate().alpha(0.6f).start();
|
||||
resetButton.setClickable(false);
|
||||
onUpdateConfigItems();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show());
|
||||
resetButton.setClickable(false);
|
||||
bottomLL.addView(resetButton, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
rightMargin = ViewUtils.dp(4);
|
||||
}});
|
||||
|
||||
ll.addView(bottomLL, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = topMargin = rightMargin = bottomMargin = ViewUtils.dp(12);
|
||||
bottomMargin += portrait ? 0 : ViewUtils.dp(6);
|
||||
}});
|
||||
|
||||
containerLayout.addView(ll);
|
||||
|
||||
return containerLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SliceBeam.EVENT_BUS.registerListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
protected void setConfigItems(List<OptionElement> items) {
|
||||
List<OptionWrapper> list = new ArrayList<>();
|
||||
int j = 0;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
OptionElement el = items.get(i);
|
||||
if (el == null) continue;
|
||||
OptionWrapper w = el.title != null ? new OptionWrapper(el.icon, el.title, el.onClick, el.color, el.noTint) : new OptionWrapper(el);
|
||||
if (el.title != null) {
|
||||
w.categoryIndex = j;
|
||||
categoryElements.put(j, new ArrayList<>());
|
||||
j++;
|
||||
list.add(w);
|
||||
} else {
|
||||
categoryElements.get(j - 1).add(w);
|
||||
}
|
||||
}
|
||||
currentList = list;
|
||||
recyclerView.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
ProfileListItem selectedItem = getSelectedItem();
|
||||
dropdownView.setTitle(selectedItem != null ? selectedItem.getTitle() : null);
|
||||
}
|
||||
|
||||
protected ProfileListItem getSelectedItem() {
|
||||
for (ProfileListItem item : getItems(false)) {
|
||||
if (item.isSelected()) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String opt(ConfigOptionDef def, int i) {
|
||||
String v = getCurrentConfig().get(def.key);
|
||||
if (i != -1) {
|
||||
try {
|
||||
v = v.split(",")[i];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
Log.w("ProfileListFragment", "Failed to parse mm option", e);
|
||||
}
|
||||
}
|
||||
return v != null ? v : (Objects.equals("host_type", def.key) ? "octoprint" : def.defaultValue);
|
||||
}
|
||||
|
||||
protected void updateConfigField(ConfigOptionDef def, int i, String value) {
|
||||
if (i != -1) {
|
||||
String[] vals = opt(def, -1).split(",");
|
||||
vals[i] = value;
|
||||
value = TextUtils.join(",", vals);
|
||||
}
|
||||
|
||||
if (!Objects.equals(opt(def, i), value)) {
|
||||
changedConfig = true;
|
||||
resetButton.animate().alpha(1).start();
|
||||
resetButton.setClickable(true);
|
||||
}
|
||||
getCurrentConfig().put(def.key, value);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
protected void onUpdateConfigItems() {
|
||||
recyclerView.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected abstract void cloneCurrentProfile();
|
||||
protected abstract void deleteCurrentProfile();
|
||||
protected abstract void onApplyConfig(String title);
|
||||
protected abstract void onResetConfig();
|
||||
protected abstract ConfigObject getCurrentConfig();
|
||||
protected abstract int getTitle();
|
||||
protected abstract void selectItem(ProfileListItem item);
|
||||
protected abstract List<ProfileListItem> getItems(boolean filter);
|
||||
protected abstract List<OptionElement> getConfigItems();
|
||||
|
||||
public interface ProfileListItem {
|
||||
String getTitle();
|
||||
boolean isSelected();
|
||||
}
|
||||
|
||||
public final class OptionElement {
|
||||
public int icon;
|
||||
public String title;
|
||||
public int color;
|
||||
public boolean noTint;
|
||||
|
||||
public SimpleRecyclerItem simpleItem;
|
||||
|
||||
private Runnable onClick;
|
||||
private int boundIndex;
|
||||
|
||||
public OptionElement(ConfigOptionDef def) {
|
||||
this(def, -1);
|
||||
}
|
||||
|
||||
public OptionElement(ConfigOptionDef def, int eIndex) {
|
||||
if (def.type != ConfigOptionDef.ConfigOptionType.BOOL && def.type != ConfigOptionDef.ConfigOptionType.BOOLS) {
|
||||
simpleItem = new PreferenceItem().setTitle(Slic3rLocalization.getString(def.getLabel())).setOnClickListener(v -> {
|
||||
if (def.guiType == ConfigOptionDef.GUIType.COLOR) {
|
||||
int defClr;
|
||||
try {
|
||||
defClr = (Color.parseColor(opt(def, eIndex)) & 0xFFFFFF) + 0xFF000000;
|
||||
} catch (Exception ignored) {
|
||||
defClr = Prefs.getAccentColor();
|
||||
}
|
||||
new BeamColorPickerPopUp(getContext())
|
||||
.setDialogTitle(Slic3rLocalization.getString(def.getFullLabel()))
|
||||
.setDefaultColor(defClr)
|
||||
.setShowAlpha(false)
|
||||
.setOnPickColorListener(new ColorPickerPopUp.OnPickColorListener() {
|
||||
@Override
|
||||
public void onColorPicked(int color) {
|
||||
int clr = color & 0xFFFFFF;
|
||||
updateConfigField(def, eIndex, String.format("#%06X", clr));
|
||||
recyclerView.getAdapter().notifyItemChanged(boundIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {}
|
||||
})
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
AtomicReference<AlertDialog> ref = new AtomicReference<>();
|
||||
AlertDialog.Builder builder = new BeamAlertDialogBuilder(getContext())
|
||||
.setTitle(Slic3rLocalization.getString(def.getFullLabel()));
|
||||
|
||||
if (def.type == ConfigOptionDef.ConfigOptionType.ENUM) {
|
||||
String[] labels;
|
||||
String[] values;
|
||||
if (Objects.equals("host_type", def.key)) {
|
||||
labels = new String[]{"OctoPrint"};
|
||||
values = new String[]{"octoprint"};
|
||||
} else {
|
||||
labels = new String[def.enumLabels.length];
|
||||
values = def.enumValues;
|
||||
for (int i = 0; i < def.enumLabels.length; i++) {
|
||||
labels[i] = Slic3rLocalization.getString(def.enumLabels[i]);
|
||||
}
|
||||
}
|
||||
builder.setSingleChoiceItems(labels, Arrays.asList(values).indexOf(opt(def, eIndex)), (dialog, which) -> {
|
||||
updateConfigField(def, eIndex, values[which]);
|
||||
// TODO: Update only value
|
||||
recyclerView.getAdapter().notifyItemChanged(boundIndex);
|
||||
dialog.dismiss();
|
||||
});
|
||||
} else {
|
||||
String msg = Slic3rLocalization.getString(def.tooltip);
|
||||
|
||||
Context ctx = getContext();
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
if (!TextUtils.isEmpty(msg)) {
|
||||
ScrollView scrollView = new ScrollView(ctx);
|
||||
TextView subtitle = new TextView(ctx);
|
||||
subtitle.setTextAppearance(ctx, com.google.android.material.R.style.MaterialAlertDialog_Material3_Body_Text);
|
||||
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
subtitle.setText(msg);
|
||||
subtitle.setPadding(ViewUtils.dp(24), ViewUtils.dp(12), ViewUtils.dp(24), ViewUtils.dp(12));
|
||||
scrollView.addView(subtitle, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
ll.addView(scrollView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
}
|
||||
|
||||
EditText text = new EditText(ctx);
|
||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
|
||||
if (def.type == ConfigOptionDef.ConfigOptionType.FLOAT) {
|
||||
text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
if (def.min < 0) {
|
||||
text.setInputType(text.getInputType() | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
} else if (def.type == ConfigOptionDef.ConfigOptionType.INT) {
|
||||
text.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
if (def.min < 0) {
|
||||
text.setInputType(text.getInputType() | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
} else {
|
||||
text.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
}
|
||||
if (def.multiline) {
|
||||
text.setInputType(text.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
text.setMaxLines(def.height);
|
||||
}
|
||||
|
||||
text.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
private boolean validateFloat(String msg) {
|
||||
if (msg.isEmpty()) return false;
|
||||
try {
|
||||
float v = Float.parseFloat(msg);
|
||||
return v >= def.min && v <= def.max;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
String msg = s.toString();
|
||||
boolean valid;
|
||||
|
||||
if (def.type == ConfigOptionDef.ConfigOptionType.FLOAT_OR_PERCENT) {
|
||||
valid = msg.endsWith("%") ? validateFloat(msg.substring(0, msg.length() - 1).trim()) : validateFloat(msg.trim());
|
||||
} else if (def.type == ConfigOptionDef.ConfigOptionType.PERCENT) {
|
||||
valid = msg.endsWith("%") && validateFloat(msg.substring(0, msg.length() - 1).trim());
|
||||
} else if (def.type == ConfigOptionDef.ConfigOptionType.FLOAT || def.type == ConfigOptionDef.ConfigOptionType.INT) {
|
||||
valid = validateFloat(msg.trim());
|
||||
} else if (def.type == ConfigOptionDef.ConfigOptionType.FLOATS || def.type == ConfigOptionDef.ConfigOptionType.INTS) {
|
||||
String[] vals = msg.split(",");
|
||||
valid = true;
|
||||
for (String val : vals) {
|
||||
if (!validateFloat(val.trim())) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
valid = true;
|
||||
}
|
||||
text.getBackground().setTintList(ColorStateList.valueOf(ThemesRepo.getColor(valid ? android.R.attr.textColorPrimary : R.attr.textColorNegative)));
|
||||
if (ref.get() != null) {
|
||||
View btn = ref.get().getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
btn.setAlpha(valid ? 1f : 0.6f);
|
||||
btn.setClickable(valid);
|
||||
}
|
||||
}
|
||||
});
|
||||
text.setText(opt(def, eIndex));
|
||||
ll.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||
}});
|
||||
|
||||
builder.setView(ll).setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
String str = text.getText().toString();
|
||||
updateConfigField(def, eIndex, str);
|
||||
// TODO: Update only value
|
||||
recyclerView.getAdapter().notifyItemChanged(boundIndex);
|
||||
}).setNegativeButton(android.R.string.cancel, null);
|
||||
ViewUtils.postOnMainThread(() -> {
|
||||
text.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(text, 0);
|
||||
text.setSelection(text.getText().length());
|
||||
}, 500);
|
||||
}
|
||||
|
||||
ref.set(builder.show());
|
||||
});
|
||||
|
||||
if (def.type == ConfigOptionDef.ConfigOptionType.STRING || def.type == ConfigOptionDef.ConfigOptionType.STRINGS) {
|
||||
((PreferenceItem) simpleItem).setSubtitleProvider(() -> opt(def, eIndex).trim());
|
||||
if (def.key.endsWith("_gcode")) {
|
||||
((PreferenceItem) simpleItem).setTitle(null);
|
||||
}
|
||||
} else {
|
||||
((PreferenceItem) simpleItem).setValueProvider(() -> def.type == ConfigOptionDef.ConfigOptionType.ENUM ? Slic3rLocalization.getString(def.enumLabels[Arrays.asList(def.enumValues).indexOf(opt(def, eIndex))]) : opt(def, eIndex));
|
||||
}
|
||||
}
|
||||
switch (def.type) {
|
||||
case BOOL:
|
||||
case BOOLS:
|
||||
simpleItem = new PreferenceSwitchItem().setTitle(Slic3rLocalization.getString(def.label))
|
||||
.setValueProvider(() -> "1".equals(opt(def, eIndex)))
|
||||
.setChangeListener((buttonView, isChecked) -> updateConfigField(def, eIndex, String.valueOf(isChecked ? 1 : 0)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public OptionElement(int icon, String title) {
|
||||
this.icon = icon;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public OptionElement(SimpleRecyclerItem item) {
|
||||
simpleItem = item;
|
||||
}
|
||||
|
||||
public OptionElement setOnClick(Runnable onClick) {
|
||||
this.onClick = onClick;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionElement setColor(int color, boolean noTint) {
|
||||
this.color = color;
|
||||
this.noTint = noTint;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class SubHeader extends SimpleRecyclerItem<SubHeader.SubHeaderHolderView> {
|
||||
public final String title;
|
||||
|
||||
public SubHeader(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubHeaderHolderView onCreateView(Context ctx) {
|
||||
return new SubHeaderHolderView(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(SubHeaderHolderView view) {
|
||||
view.bind(this);
|
||||
}
|
||||
|
||||
private final static class SubHeaderHolderView extends LinearLayout {
|
||||
TextView title;
|
||||
|
||||
SubHeaderHolderView(Context context) {
|
||||
super(context);
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
addView(new DividerView(context, R.attr.dividerContrastColor), new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
|
||||
title = new TextView(context);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||
title.setPadding(ViewUtils.dp(20), ViewUtils.dp(12), ViewUtils.dp(20), 0);
|
||||
addView(title);
|
||||
}
|
||||
|
||||
void bind(SubHeader h) {
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
title.setText(Slic3rLocalization.getString(h.title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final static class SubHint extends SimpleRecyclerItem<SubHint.SubHintHolderView> {
|
||||
public final PreferenceItem.ValueProvider provider;
|
||||
|
||||
public SubHint(PreferenceItem.ValueProvider title) {
|
||||
this.provider = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubHintHolderView onCreateView(Context ctx) {
|
||||
return new SubHintHolderView(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(SubHintHolderView view) {
|
||||
view.bind(this);
|
||||
}
|
||||
|
||||
private final static class SubHintHolderView extends LinearLayout {
|
||||
TextView title;
|
||||
|
||||
SubHintHolderView(Context context) {
|
||||
super(context);
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
addView(new DividerView(context, R.attr.dividerContrastColor), new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||
|
||||
title = new TextView(context);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
title.setPadding(ViewUtils.dp(20), ViewUtils.dp(6), ViewUtils.dp(20), ViewUtils.dp(12));
|
||||
addView(title);
|
||||
}
|
||||
|
||||
void bind(SubHint h) {
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
title.setText(h.provider.provide());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static class OptionWrapper extends SimpleRecyclerItem<View> {
|
||||
int icon;
|
||||
String title;
|
||||
Runnable onClick;
|
||||
int color;
|
||||
OptionElement optionEl;
|
||||
boolean noTint;
|
||||
|
||||
int categoryIndex;
|
||||
|
||||
OptionWrapper(int icon, String t, Runnable onClick, int color, boolean noTint) {
|
||||
this.icon = icon;
|
||||
this.title = t;
|
||||
this.onClick = onClick;
|
||||
this.color = color;
|
||||
this.noTint = noTint;
|
||||
}
|
||||
|
||||
OptionWrapper(OptionElement el) {
|
||||
optionEl = el;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(Context ctx) {
|
||||
FrameLayout v = new FrameLayout(ctx);
|
||||
v.setBackgroundColor(Color.GREEN);
|
||||
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(32)) {{
|
||||
bottomMargin = ViewUtils.dp(16);
|
||||
}});
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class CategoryHolderView extends LinearLayout implements IThemeView {
|
||||
private ImageView icon;
|
||||
private TextView title;
|
||||
private ImageView dropdown;
|
||||
|
||||
public CategoryHolderView(Context context) {
|
||||
super(context);
|
||||
|
||||
setOrientation(HORIZONTAL);
|
||||
setGravity(Gravity.CENTER_VERTICAL);
|
||||
setPadding(ViewUtils.dp(21), ViewUtils.dp(16), ViewUtils.dp(21), ViewUtils.dp(16));
|
||||
|
||||
icon = new ImageView(context);
|
||||
addView(icon, new LayoutParams(ViewUtils.dp(26), ViewUtils.dp(26)));
|
||||
|
||||
title = new TextView(context);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
addView(title, new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
leftMargin = ViewUtils.dp(12);
|
||||
}});
|
||||
|
||||
dropdown = new ImageView(context);
|
||||
dropdown.setImageResource(R.drawable.dropdown_24);
|
||||
addView(dropdown, new LayoutParams(ViewUtils.dp(24), ViewUtils.dp(24)));
|
||||
|
||||
onApplyTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
int color = getTag() != null ? ThemesRepo.getColor((Integer) getTag()) : 0;
|
||||
if (icon.getTag() != null) {
|
||||
icon.setImageTintList(null);
|
||||
} else {
|
||||
icon.setImageTintList(ColorStateList.valueOf(color != 0 ? color : ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||
}
|
||||
title.setTextColor(color != 0 ? color : ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
dropdown.setImageTintList(ColorStateList.valueOf(color != 0 ? color : ThemesRepo.getColor(android.R.attr.textColorPrimary)));
|
||||
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 32));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package ru.ytkab0bp.slicebeam.fragment;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.mrudultora.colorpicker.ColorPickerPopUp;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ru.ytkab0bp.eventbus.EventHandler;
|
||||
import ru.ytkab0bp.slicebeam.BeamServerData;
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.SetupActivity;
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||
import ru.ytkab0bp.slicebeam.components.BeamColorPickerPopUp;
|
||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
||||
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
|
||||
public class SettingsFragment extends ProfileListFragment {
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View v) {
|
||||
super.onViewCreated(v);
|
||||
dropdownView.setVisibility(View.GONE);
|
||||
((View) saveButton.getParent()).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<OptionElement> getConfigItems() {
|
||||
return Arrays.asList(
|
||||
new OptionElement(R.drawable.paint_roller_outline_28, getContext().getString(R.string.SettingsInterface)),
|
||||
new OptionElement(new PreferenceItem().setTitle(getContext().getString(R.string.SettingsInterfaceTheme)).setValueProvider(() -> getContext().getString(Prefs.getThemeMode().title)).setOnClickListener(v -> {
|
||||
String[] items = new String[Prefs.ThemeMode.values().length];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = getContext().getString(Prefs.ThemeMode.values()[i].title);
|
||||
}
|
||||
new BeamAlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.SettingsInterfaceTheme)
|
||||
.setSingleChoiceItems(items, Prefs.getThemeMode().ordinal(), (dialog, which) -> {
|
||||
boolean activity = getContext() instanceof Activity;
|
||||
if (activity) {
|
||||
Activity act = (Activity) getContext();
|
||||
ViewGroup decorView = (ViewGroup) act.getWindow().getDecorView();
|
||||
Bitmap bm = Bitmap.createBitmap(decorView.getWidth(), decorView.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas c = new Canvas(bm);
|
||||
decorView.draw(c);
|
||||
|
||||
ImageView overlay = new ImageView(act);
|
||||
overlay.setImageBitmap(bm);
|
||||
decorView.addView(overlay);
|
||||
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(1, 0).setDuration(250);
|
||||
anim.addUpdateListener(animation -> overlay.setAlpha((float) animation.getAnimatedValue()));
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
decorView.removeView(overlay);
|
||||
bm.recycle();
|
||||
}
|
||||
});
|
||||
anim.start();
|
||||
}
|
||||
Prefs.setThemeMode(which);
|
||||
if (activity) {
|
||||
ThemesRepo.invalidate((Activity) getContext());
|
||||
}
|
||||
dialog.dismiss();
|
||||
})
|
||||
.show();
|
||||
})),
|
||||
new OptionElement(new PreferenceItem().setTitle(getContext().getString(R.string.SettingsInterfaceColor)).setValueProvider(() -> "#" + String.format("%08X", Prefs.getAccentColor())).setOnClickListener(v -> {
|
||||
new BeamColorPickerPopUp(getContext())
|
||||
.setDialogTitle(getContext().getString(R.string.SettingsInterfaceColor))
|
||||
.setDefaultColor(Prefs.getAccentColor())
|
||||
.setShowAlpha(false)
|
||||
.setOnPickColorListener(new ColorPickerPopUp.OnPickColorListener() {
|
||||
@Override
|
||||
public void onColorPicked(int color) {
|
||||
Prefs.setAccentColor(color);
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
Prefs.setAccentColor(SetupActivity.AccentColors.DEFAULT.color);
|
||||
onChanged();
|
||||
}
|
||||
|
||||
void onChanged() {
|
||||
BeamTheme.LIGHT.colors.put(android.R.attr.colorAccent, Prefs.getAccentColor());
|
||||
BeamTheme.DARK.colors.put(android.R.attr.colorAccent, Prefs.getAccentColor());
|
||||
ThemesRepo.invalidate((Activity) getContext());
|
||||
recyclerView.getAdapter().notifyItemChanged(1);
|
||||
}
|
||||
})
|
||||
.setNegativeButtonText(getContext().getString(R.string.SettingsInterfaceColorReset))
|
||||
.show();
|
||||
})),
|
||||
new OptionElement(new PreferenceItem().setTitle(getContext().getString(R.string.SettingsInterfaceResolutionScale)).setSubtitle(getContext().getString(R.string.SettingsInterfaceResolutionScaleDescription)).setValueProvider(() -> (int) (Prefs.getRenderScale() * 100) + "%").setOnClickListener(v -> {
|
||||
float[] variants = {1, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f};
|
||||
String[] items = new String[variants.length];
|
||||
int j = 0;
|
||||
for (int i = 0; i < variants.length; i++) {
|
||||
items[i] = (int) (variants[i] * 100) + "%";
|
||||
if (variants[i] == Prefs.getRenderScale()) {
|
||||
j = i;
|
||||
}
|
||||
}
|
||||
|
||||
new BeamAlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.SettingsInterfaceResolutionScale)
|
||||
.setSingleChoiceItems(items, j, (dialog, which) -> {
|
||||
Prefs.setRenderScale(variants[which]);
|
||||
dialog.dismiss();
|
||||
// I'm too lazy to calculate real position for now
|
||||
recyclerView.getAdapter().notifyItemChanged(3);
|
||||
})
|
||||
.show();
|
||||
})),
|
||||
new OptionElement(R.drawable.info_outline_28, getContext().getString(R.string.SettingsAbout)).setOnClick(() -> {
|
||||
Activity act = (Activity) getContext();
|
||||
act.startActivity(new Intent(act, SetupActivity.class).putExtra(SetupActivity.EXTRA_ABOUT, true));
|
||||
}),
|
||||
new OptionElement(R.drawable.telegram, getContext().getString(R.string.SettingsTelegram)).setColor(R.attr.telegramColor, false).setOnClick(() -> {
|
||||
Activity act = (Activity) getContext();
|
||||
act.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/ytkab0bp_channel")));
|
||||
}),
|
||||
BeamServerData.isBoostyAvailable() ? new OptionElement(R.drawable.boosty, getContext().getString(R.string.SettingsBoosty)).setColor(R.attr.boostyColorTop, true).setOnClick(() -> {
|
||||
Activity act = (Activity) getContext();
|
||||
act.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp")));
|
||||
}) : null,
|
||||
new OptionElement(R.drawable.k3d_logo_new_14, getContext().getString(R.string.SettingsK3D)).setColor(R.attr.k3dColor, true).setOnClick(() -> {
|
||||
Activity act = (Activity) getContext();
|
||||
act.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/K_3_D")));
|
||||
}),
|
||||
new OptionElement(R.drawable.refresh_outline_28, getContext().getString(R.string.SettingsResetToDefault)).setColor(R.attr.textColorNegative, false).setOnClick(() -> {
|
||||
Context ctx = getContext();
|
||||
if (ctx instanceof Activity) {
|
||||
Activity act = (Activity) ctx;
|
||||
new BeamAlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.SettingsResetToDefaultTitle)
|
||||
.setMessage(R.string.SettingsResetToDefaultDescription)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
SliceBeam.getConfigFile().delete();
|
||||
SliceBeam.CONFIG = null;
|
||||
Prefs.getPrefs().edit().clear().apply();
|
||||
Prefs.setLastCommit();
|
||||
act.startActivity(new Intent(act, SetupActivity.class));
|
||||
act.finish();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null).
|
||||
show();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(runOnMainThread = true)
|
||||
public void onDataUpdated(BeamServerDataUpdatedEvent e) {
|
||||
setConfigItems(getConfigItems());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cloneCurrentProfile() {}
|
||||
|
||||
@Override
|
||||
protected void deleteCurrentProfile() {}
|
||||
|
||||
@Override
|
||||
protected void onApplyConfig(String title) {}
|
||||
|
||||
@Override
|
||||
protected void onResetConfig() {}
|
||||
|
||||
@Override
|
||||
protected ConfigObject getCurrentConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitle() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void selectItem(ProfileListItem item) {}
|
||||
|
||||
@Override
|
||||
protected List<ProfileListItem> getItems(boolean filter) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package ru.ytkab0bp.slicebeam.navigation;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||
import ru.ytkab0bp.slicebeam.fragment.FilamentConfigFragment;
|
||||
import ru.ytkab0bp.slicebeam.fragment.PrintConfigFragment;
|
||||
import ru.ytkab0bp.slicebeam.fragment.PrinterConfigFragment;
|
||||
import ru.ytkab0bp.slicebeam.fragment.SettingsFragment;
|
||||
|
||||
public abstract class DelegateSlotImpl extends NavigationDelegate {
|
||||
public int getSlotCount() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
public int getSlotIcon(int slot) {
|
||||
switch (slot) {
|
||||
default:
|
||||
case 0:
|
||||
return R.drawable.view_in_ar_24;
|
||||
case 1:
|
||||
return R.drawable.wrench_outline_28;
|
||||
case 2:
|
||||
return R.drawable.slot_filament_28;
|
||||
case 3:
|
||||
return R.drawable.printer_outline_28;
|
||||
case 4:
|
||||
return R.drawable.settings_outline_28;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean needDisplaySlotGear(int slot) {
|
||||
return slot != 0 && slot != 4;
|
||||
}
|
||||
|
||||
@StringRes
|
||||
public int getSlotTitle(int slot) {
|
||||
switch (slot) {
|
||||
default:
|
||||
case 0:
|
||||
return R.string.SlotBed;
|
||||
case 1:
|
||||
return R.string.SlotPrintConfig;
|
||||
case 2:
|
||||
return R.string.SlotFilamentConfig;
|
||||
case 3:
|
||||
return R.string.SlotPrinterConfig;
|
||||
case 4:
|
||||
return R.string.SlotAppSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
public int getSlotTooltip(int slot) {
|
||||
switch (slot) {
|
||||
default:
|
||||
return getSlotTitle(slot);
|
||||
case 1:
|
||||
return R.string.SlotPrintConfigTooltip;
|
||||
case 2:
|
||||
return R.string.SlotFilamentConfigTooltip;
|
||||
case 3:
|
||||
return R.string.SlotPrinterConfigTooltip;
|
||||
case 4:
|
||||
return R.string.SlotAppSettingsTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment newFragment(int slot) {
|
||||
switch (slot) {
|
||||
default:
|
||||
case 0:
|
||||
return new BedFragment();
|
||||
case 1:
|
||||
return new PrintConfigFragment();
|
||||
case 2:
|
||||
return new FilamentConfigFragment();
|
||||
case 3:
|
||||
return new PrinterConfigFragment();
|
||||
case 4:
|
||||
return new SettingsFragment();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ru.ytkab0bp.slicebeam.navigation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
|
||||
public abstract class Fragment {
|
||||
private View mView;
|
||||
private Context context;
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void onCreate() {}
|
||||
|
||||
@CallSuper
|
||||
public void onResume() {}
|
||||
|
||||
@CallSuper
|
||||
public void onPause() {}
|
||||
|
||||
@CallSuper
|
||||
public void onDestroy() {
|
||||
mView = null;
|
||||
}
|
||||
|
||||
public void onApplyTheme() {
|
||||
mView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
}
|
||||
|
||||
public abstract View onCreateView(Context ctx);
|
||||
|
||||
@CallSuper
|
||||
public void onViewCreated(View v) {
|
||||
mView = v;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public SlotChangeCallback onCheckDelayForSlotChange() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public interface SlotChangeCallback {
|
||||
boolean needDelay(Runnable callback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package ru.ytkab0bp.slicebeam.navigation;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.view.menu.MenuItemImpl;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.navigation.NavigationBarView;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.R;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.TextColorImageSpan;
|
||||
import ru.ytkab0bp.slicebeam.view.ThemeBottomNavigationView;
|
||||
import ru.ytkab0bp.slicebeam.view.ThemeRailNavigationView;
|
||||
|
||||
public class MobileNavigationDelegate extends DelegateSlotImpl {
|
||||
private boolean portrait;
|
||||
private FrameLayout root;
|
||||
private NavigationBarView navigationView;
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
super.onApplyTheme();
|
||||
ThemesRepo.invalidateView(navigationView);
|
||||
root.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public View onCreateView(Context ctx) {
|
||||
FrameLayout fl = new FrameLayout(ctx);
|
||||
LinearLayout ll = new LinearLayout(ctx);
|
||||
DisplayMetrics dm = ctx.getResources().getDisplayMetrics();
|
||||
portrait = dm.widthPixels < dm.heightPixels;
|
||||
ll.setOrientation(portrait ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
|
||||
|
||||
if (portrait) ll.addView(navigationView = new ThemeBottomNavigationView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
container = new FrameLayout(ctx);
|
||||
Fragment fr = getCurrentFragment();
|
||||
if (fr.getView() == null) {
|
||||
View v = fr.onCreateView(ctx);
|
||||
fr.onViewCreated(v);
|
||||
fr.onApplyTheme();
|
||||
|
||||
container.addView(v);
|
||||
} else {
|
||||
container.addView(fr.getView());
|
||||
}
|
||||
ll.addView(container, new LinearLayout.LayoutParams(portrait ? ViewGroup.LayoutParams.MATCH_PARENT : 0, portrait ? 0 : ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
|
||||
if (!portrait) {
|
||||
ll.addView(navigationView = new ThemeRailNavigationView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
navigationView.setLabelVisibilityMode(BottomNavigationView.LABEL_VISIBILITY_LABELED);
|
||||
Menu menu = navigationView.getMenu();
|
||||
for (int i = 0; i < getSlotCount(); i++) {
|
||||
MenuItemImpl item = (MenuItemImpl) menu.add(0, i, 0, getSlotTitle(i)).setIcon(getSlotIcon(i));
|
||||
if (needDisplaySlotGear(i)) {
|
||||
SpannableStringBuilder sb = SpannableStringBuilder.valueOf("d ");
|
||||
Drawable dr = ContextCompat.getDrawable(ctx, R.drawable.settings_outline_28);
|
||||
int size = ViewUtils.dp(13);
|
||||
dr.setBounds(0, 0, size, size);
|
||||
sb.setSpan(new ReplacementSpan() {
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
|
||||
return ViewUtils.dp(2f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {}
|
||||
}, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
sb.setSpan(new TextColorImageSpan(dr, ViewUtils.dp(1.5f)), 0, 1, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
sb.append(item.getTitle());
|
||||
item.setTitle(sb);
|
||||
}
|
||||
item.setTooltipText(ctx.getString(getSlotTooltip(i)));
|
||||
}
|
||||
navigationView.setSelectedItemId(currentSlot);
|
||||
NavigationBarView finalNavigationView = navigationView;
|
||||
navigationView.setOnItemSelectedListener(item -> {
|
||||
if (item.getItemId() == currentSlot) return true;
|
||||
switchSlot(item.getItemId(), () -> finalNavigationView.setSelectedItemId(item.getItemId()));
|
||||
return false;
|
||||
});
|
||||
|
||||
fl.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||
fl.addView(ll);
|
||||
return root = fl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLayout getOverlayView() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwitchingWithX() {
|
||||
return portrait;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package ru.ytkab0bp.slicebeam.navigation;
|
||||
|
||||
import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
|
||||
public abstract class NavigationDelegate {
|
||||
protected Context context;
|
||||
|
||||
protected SparseArray<Stack<Fragment>> fragmentStack = new SparseArray<>();
|
||||
protected int currentSlot = 0;
|
||||
protected FrameLayout container;
|
||||
|
||||
private SpringAnimation switchAnimation;
|
||||
|
||||
public final void setContext(Context ctx) {
|
||||
context = ctx;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void onCreate() {
|
||||
for (int i = 0; i < fragmentStack.size(); i++) {
|
||||
Stack<Fragment> fragments = fragmentStack.get(fragmentStack.keyAt(i));
|
||||
for (Fragment fr : fragments) {
|
||||
fr.setContext(context);
|
||||
fr.onCreate();
|
||||
}
|
||||
}
|
||||
|
||||
if (fragmentStack.get(currentSlot) == null) {
|
||||
Stack<Fragment> stack = new Stack<>();
|
||||
fragmentStack.put(currentSlot, stack);
|
||||
Fragment fr = newFragment(currentSlot);
|
||||
fr.setContext(context);
|
||||
fr.onCreate();
|
||||
stack.push(fr);
|
||||
}
|
||||
}
|
||||
|
||||
public void onApplyTheme() {
|
||||
for (int i = 0; i < fragmentStack.size(); i++) {
|
||||
Stack<Fragment> st = fragmentStack.valueAt(i);
|
||||
assertTrue(st != null);
|
||||
for (Fragment fr : st) {
|
||||
fr.onApplyTheme();
|
||||
if (fr.getView() != null) {
|
||||
ThemesRepo.invalidateView(fr.getView());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void onResume() {
|
||||
fragmentStack.get(currentSlot).peek().onResume();
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void onPause() {
|
||||
fragmentStack.get(currentSlot).peek().onPause();
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void onDestroy() {
|
||||
for (int i = 0; i < fragmentStack.size(); i++) {
|
||||
Stack<Fragment> fragments = fragmentStack.get(fragmentStack.keyAt(i));
|
||||
for (Fragment fr : fragments) {
|
||||
fr.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FrameLayout getContainerView() {
|
||||
return container;
|
||||
}
|
||||
|
||||
public abstract View onCreateView(Context ctx);
|
||||
public abstract Fragment newFragment(int slot);
|
||||
public abstract FrameLayout getOverlayView();
|
||||
|
||||
public boolean isSwitchingWithX() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void switchSlot(int slot, Runnable onInterfaceChange) {
|
||||
Stack<Fragment> stack = fragmentStack.get(slot);
|
||||
if (stack == null) {
|
||||
fragmentStack.put(slot, stack = new Stack<>());
|
||||
Fragment fr = newFragment(slot);
|
||||
fr.setContext(context);
|
||||
fr.onCreate();
|
||||
stack.push(fr);
|
||||
}
|
||||
int wasSlot = currentSlot;
|
||||
currentSlot = slot;
|
||||
|
||||
if (container.getChildCount() > 0) {
|
||||
if (switchAnimation != null) {
|
||||
switchAnimation.cancel();
|
||||
}
|
||||
|
||||
Fragment cur = fragmentStack.get(wasSlot).peek();
|
||||
cur.onPause();
|
||||
|
||||
Runnable next = () -> {
|
||||
onInterfaceChange.run();
|
||||
|
||||
Fragment fr = fragmentStack.get(slot).peek();
|
||||
View wasView = container.getChildAt(0);
|
||||
View newView;
|
||||
if (fr.getView() == null) {
|
||||
newView = fr.onCreateView(context);
|
||||
fr.onViewCreated(newView);
|
||||
fr.onApplyTheme();
|
||||
} else {
|
||||
newView = fr.getView();
|
||||
}
|
||||
container.addView(newView);
|
||||
|
||||
boolean forward = slot > wasSlot;
|
||||
switchAnimation = new SpringAnimation(new FloatValueHolder(0))
|
||||
.setMinimumVisibleChange(1 / 256f)
|
||||
.setSpring(new SpringForce(1f)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener((animation, value, velocity) -> {
|
||||
float fValue = value;
|
||||
if (!forward) {
|
||||
fValue = 1f - fValue;
|
||||
}
|
||||
|
||||
if (isSwitchingWithX()) {
|
||||
wasView.setTranslationX(fValue * -wasView.getWidth() * 0.75f);
|
||||
newView.setTranslationX((1f - value) * (forward ? 1 : -1) * newView.getWidth() * 0.75f);
|
||||
} else {
|
||||
wasView.setTranslationY(fValue * -wasView.getHeight() * 0.75f);
|
||||
newView.setTranslationY((1f - value) * (forward ? 1 : -1) * newView.getHeight() * 0.75f);
|
||||
}
|
||||
wasView.setAlpha(1f - value);
|
||||
newView.setAlpha(value);
|
||||
})
|
||||
.addEndListener((animation, canceled, value, velocity) -> {
|
||||
switchAnimation = null;
|
||||
container.removeView(wasView);
|
||||
fr.onResume();
|
||||
});
|
||||
switchAnimation.start();
|
||||
};
|
||||
|
||||
Fragment.SlotChangeCallback callback = cur.onCheckDelayForSlotChange();
|
||||
if (callback == null || !cur.onCheckDelayForSlotChange().needDelay(next)) {
|
||||
next.run();
|
||||
}
|
||||
} else {
|
||||
Fragment fr = fragmentStack.get(slot).peek();
|
||||
fr.setContext(context);
|
||||
fr.onCreate();
|
||||
View v = fr.onCreateView(context);
|
||||
fr.onViewCreated(v);
|
||||
fr.onApplyTheme();
|
||||
container.addView(v);
|
||||
fr.onResume();
|
||||
currentSlot = slot;
|
||||
|
||||
onInterfaceChange.run();
|
||||
}
|
||||
}
|
||||
|
||||
public Fragment getCurrentFragment() {
|
||||
return fragmentStack.get(currentSlot).peek();
|
||||
}
|
||||
|
||||
public boolean destroyCurrent() {
|
||||
if (fragmentStack.get(currentSlot).size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Fragment fr = fragmentStack.get(currentSlot).peek();
|
||||
fr.onPause();
|
||||
|
||||
Fragment prev = fragmentStack.get(currentSlot).get(fragmentStack.get(currentSlot).size() - 2);
|
||||
|
||||
View wasView = container.getChildAt(0);
|
||||
View newView = prev.getView();
|
||||
|
||||
switchAnimation = new SpringAnimation(new FloatValueHolder(0))
|
||||
.setMinimumVisibleChange(1 / 256f)
|
||||
.setSpring(new SpringForce(1f)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener((animation, value, velocity) -> {
|
||||
float fValue = 1f - value;
|
||||
|
||||
if (isSwitchingWithX()) {
|
||||
wasView.setTranslationX(-fValue * wasView.getWidth() * 0.75f);
|
||||
newView.setTranslationX((1f - fValue) * newView.getWidth() * 0.75f);
|
||||
} else {
|
||||
wasView.setTranslationY(-fValue * wasView.getHeight() * 0.75f);
|
||||
newView.setTranslationY((1f - fValue) * newView.getHeight() * 0.75f);
|
||||
}
|
||||
wasView.setAlpha(1f - value);
|
||||
newView.setAlpha(value);
|
||||
})
|
||||
.addEndListener((animation, canceled, value, velocity) -> {
|
||||
switchAnimation = null;
|
||||
container.removeView(wasView);
|
||||
prev.onResume();
|
||||
fr.onDestroy();
|
||||
});
|
||||
switchAnimation.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void pushFragment(Fragment fragment) {
|
||||
fragment.setContext(context);
|
||||
fragment.onCreate();
|
||||
|
||||
fragmentStack.get(currentSlot).peek().onPause();
|
||||
View wasView = container.getChildAt(0);
|
||||
View newView = fragment.onCreateView(context);
|
||||
fragment.onViewCreated(newView);
|
||||
fragment.onApplyTheme();
|
||||
fragmentStack.get(currentSlot).push(fragment);
|
||||
|
||||
switchAnimation = new SpringAnimation(new FloatValueHolder(0))
|
||||
.setMinimumVisibleChange(1 / 256f)
|
||||
.setSpring(new SpringForce(1f)
|
||||
.setStiffness(1000f)
|
||||
.setDampingRatio(1f))
|
||||
.addUpdateListener((animation, value, velocity) -> {
|
||||
if (isSwitchingWithX()) {
|
||||
wasView.setTranslationX(-value * wasView.getWidth() * 0.75f);
|
||||
newView.setTranslationX((1f - value) * newView.getWidth() * 0.75f);
|
||||
} else {
|
||||
wasView.setTranslationY(-value * wasView.getHeight() * 0.75f);
|
||||
newView.setTranslationY((1f - value) * newView.getHeight() * 0.75f);
|
||||
}
|
||||
wasView.setAlpha(1f - value);
|
||||
newView.setAlpha(value);
|
||||
})
|
||||
.addEndListener((animation, canceled, value, velocity) -> {
|
||||
switchAnimation = null;
|
||||
container.removeView(wasView);
|
||||
fragment.onResume();
|
||||
});
|
||||
switchAnimation.start();
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
Fragment fr = getCurrentFragment();
|
||||
if (fr != null && fr.onBackPressed()) return true;
|
||||
else if (fragmentStack.get(currentSlot).size() > 1) {
|
||||
return destroyCurrent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class BigHeaderItem extends SimpleRecyclerItem<TextView> {
|
||||
public String title;
|
||||
|
||||
public BigHeaderItem() {}
|
||||
|
||||
public BigHeaderItem(String t) {
|
||||
title = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextView onCreateView(Context ctx) {
|
||||
TextView tv = new TextView(ctx);
|
||||
tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
|
||||
tv.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
tv.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
tv.setPadding(ViewUtils.dp(21), ViewUtils.dp(12), ViewUtils.dp(21), ViewUtils.dp(12));
|
||||
tv.setGravity(Gravity.START);
|
||||
tv.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return tv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(TextView view) {
|
||||
view.setText(title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.view.View;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
/**
|
||||
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
|
||||
* animations on remove, add, and move events that happen to the items in
|
||||
* a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
|
||||
*
|
||||
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
|
||||
*/
|
||||
public class CubicBezierItemAnimator extends SimpleItemAnimator {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static TimeInterpolator sDefaultInterpolator = ViewUtils.CUBIC_INTERPOLATOR;
|
||||
|
||||
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
|
||||
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
|
||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
|
||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
|
||||
|
||||
ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>();
|
||||
ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
|
||||
ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
|
||||
|
||||
ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>();
|
||||
ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>();
|
||||
ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>();
|
||||
ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>();
|
||||
|
||||
private static class MoveInfo {
|
||||
public RecyclerView.ViewHolder holder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
this.holder = holder;
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChangeInfo {
|
||||
public RecyclerView.ViewHolder oldHolder, newHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
}
|
||||
|
||||
ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
this(oldHolder, newHolder);
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeInfo{"
|
||||
+ "oldHolder=" + oldHolder
|
||||
+ ", newHolder=" + newHolder
|
||||
+ ", fromX=" + fromX
|
||||
+ ", fromY=" + fromY
|
||||
+ ", toX=" + toX
|
||||
+ ", toY=" + toY
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
public CubicBezierItemAnimator() {
|
||||
int duration = 180;
|
||||
setAddDuration(duration);
|
||||
setRemoveDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPendingAnimations() {
|
||||
boolean removalsPending = !mPendingRemovals.isEmpty();
|
||||
boolean movesPending = !mPendingMoves.isEmpty();
|
||||
boolean changesPending = !mPendingChanges.isEmpty();
|
||||
boolean additionsPending = !mPendingAdditions.isEmpty();
|
||||
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
|
||||
// nothing to animate
|
||||
return;
|
||||
}
|
||||
// First, remove stuff
|
||||
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
|
||||
animateRemoveImpl(holder);
|
||||
}
|
||||
mPendingRemovals.clear();
|
||||
// Next, move stuff
|
||||
if (movesPending) {
|
||||
final ArrayList<MoveInfo> moves = new ArrayList<>();
|
||||
moves.addAll(mPendingMoves);
|
||||
mMovesList.add(moves);
|
||||
mPendingMoves.clear();
|
||||
Runnable mover = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (MoveInfo moveInfo : moves) {
|
||||
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
|
||||
moveInfo.toX, moveInfo.toY);
|
||||
}
|
||||
moves.clear();
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
View view = moves.get(0).holder.itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
|
||||
} else {
|
||||
mover.run();
|
||||
}
|
||||
}
|
||||
// Next, change stuff, to run in parallel with move animations
|
||||
if (changesPending) {
|
||||
final ArrayList<ChangeInfo> changes = new ArrayList<>();
|
||||
changes.addAll(mPendingChanges);
|
||||
mChangesList.add(changes);
|
||||
mPendingChanges.clear();
|
||||
Runnable changer = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (ChangeInfo change : changes) {
|
||||
animateChangeImpl(change);
|
||||
}
|
||||
changes.clear();
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
|
||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
|
||||
} else {
|
||||
changer.run();
|
||||
}
|
||||
}
|
||||
// Next, add stuff
|
||||
if (additionsPending) {
|
||||
final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
|
||||
additions.addAll(mPendingAdditions);
|
||||
mAdditionsList.add(additions);
|
||||
mPendingAdditions.clear();
|
||||
Runnable adder = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (RecyclerView.ViewHolder holder : additions) {
|
||||
animateAddImpl(holder);
|
||||
}
|
||||
additions.clear();
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
};
|
||||
if (removalsPending || movesPending || changesPending) {
|
||||
long removeDuration = removalsPending ? getRemoveDuration() : 0;
|
||||
long moveDuration = movesPending ? getMoveDuration() : 0;
|
||||
long changeDuration = changesPending ? getChangeDuration() : 0;
|
||||
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
|
||||
View view = additions.get(0).itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
|
||||
} else {
|
||||
adder.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
mPendingRemovals.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimator animation = view.animate();
|
||||
mRemoveAnimations.add(holder);
|
||||
animation.setDuration(getRemoveDuration()).alpha(0).setListener(
|
||||
new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchRemoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
animation.setListener(null);
|
||||
view.setAlpha(1);
|
||||
dispatchRemoveFinished(holder);
|
||||
mRemoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
holder.itemView.setAlpha(0);
|
||||
mPendingAdditions.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
void animateAddImpl(final RecyclerView.ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimator animation = view.animate();
|
||||
mAddAnimations.add(holder);
|
||||
animation.alpha(1).setDuration(getAddDuration())
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchAddStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
view.setAlpha(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
animation.setListener(null);
|
||||
dispatchAddFinished(holder);
|
||||
mAddAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
|
||||
int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
fromX += (int) holder.itemView.getTranslationX();
|
||||
fromY += (int) holder.itemView.getTranslationY();
|
||||
resetAnimation(holder);
|
||||
int deltaX = toX - fromX;
|
||||
int deltaY = toY - fromY;
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchMoveFinished(holder);
|
||||
return false;
|
||||
}
|
||||
if (deltaX != 0) {
|
||||
view.setTranslationX(-deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
view.setTranslationY(-deltaY);
|
||||
}
|
||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
final int deltaX = toX - fromX;
|
||||
final int deltaY = toY - fromY;
|
||||
if (deltaX != 0) {
|
||||
view.animate().translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
view.animate().translationY(0);
|
||||
}
|
||||
// TODO: make EndActions end listeners instead, since end actions aren't called when
|
||||
// vpas are canceled (and can't end them. why?)
|
||||
// need listener functionality in VPACompat for this. Ick.
|
||||
final ViewPropertyAnimator animation = view.animate();
|
||||
mMoveAnimations.add(holder);
|
||||
animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchMoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
if (deltaX != 0) {
|
||||
view.setTranslationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
view.setTranslationY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
animation.setListener(null);
|
||||
dispatchMoveFinished(holder);
|
||||
mMoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
if (oldHolder == newHolder) {
|
||||
// Don't know how to run change animations when the same view holder is re-used.
|
||||
// run a move animation to handle position changes.
|
||||
return animateMove(oldHolder, fromX, fromY, toX, toY);
|
||||
}
|
||||
final float prevTranslationX = oldHolder.itemView.getTranslationX();
|
||||
final float prevTranslationY = oldHolder.itemView.getTranslationY();
|
||||
final float prevAlpha = oldHolder.itemView.getAlpha();
|
||||
resetAnimation(oldHolder);
|
||||
int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
int deltaY = (int) (toY - fromY - prevTranslationY);
|
||||
// recover prev translation state after ending animation
|
||||
oldHolder.itemView.setTranslationX(prevTranslationX);
|
||||
oldHolder.itemView.setTranslationY(prevTranslationY);
|
||||
oldHolder.itemView.setAlpha(prevAlpha);
|
||||
if (newHolder != null) {
|
||||
// carry over translation values
|
||||
resetAnimation(newHolder);
|
||||
newHolder.itemView.setTranslationX(-deltaX);
|
||||
newHolder.itemView.setTranslationY(-deltaY);
|
||||
newHolder.itemView.setAlpha(0);
|
||||
}
|
||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
void animateChangeImpl(final ChangeInfo changeInfo) {
|
||||
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
|
||||
final View view = holder == null ? null : holder.itemView;
|
||||
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
|
||||
final View newView = newHolder != null ? newHolder.itemView : null;
|
||||
if (view != null) {
|
||||
final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
|
||||
getChangeDuration());
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
oldViewAnim.setListener(null);
|
||||
view.setAlpha(1);
|
||||
view.setTranslationX(0);
|
||||
view.setTranslationY(0);
|
||||
dispatchChangeFinished(changeInfo.oldHolder, true);
|
||||
mChangeAnimations.remove(changeInfo.oldHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (newView != null) {
|
||||
final ViewPropertyAnimator newViewAnimation = newView.animate();
|
||||
mChangeAnimations.add(changeInfo.newHolder);
|
||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
|
||||
.alpha(1).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchChangeStarting(changeInfo.newHolder, false);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
newViewAnimation.setListener(null);
|
||||
newView.setAlpha(1);
|
||||
newView.setTranslationX(0);
|
||||
newView.setTranslationY(0);
|
||||
dispatchChangeFinished(changeInfo.newHolder, false);
|
||||
mChangeAnimations.remove(changeInfo.newHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) {
|
||||
for (int i = infoList.size() - 1; i >= 0; i--) {
|
||||
ChangeInfo changeInfo = infoList.get(i);
|
||||
if (endChangeAnimationIfNecessary(changeInfo, item)) {
|
||||
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
|
||||
infoList.remove(changeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
|
||||
if (changeInfo.oldHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
|
||||
}
|
||||
if (changeInfo.newHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
|
||||
}
|
||||
}
|
||||
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
|
||||
boolean oldItem = false;
|
||||
if (changeInfo.newHolder == item) {
|
||||
changeInfo.newHolder = null;
|
||||
} else if (changeInfo.oldHolder == item) {
|
||||
changeInfo.oldHolder = null;
|
||||
oldItem = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
item.itemView.setAlpha(1);
|
||||
item.itemView.setTranslationX(0);
|
||||
item.itemView.setTranslationY(0);
|
||||
dispatchChangeFinished(item, oldItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
view.animate().cancel();
|
||||
// TODO if some other animations are chained to end, how do we cancel them as well?
|
||||
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
|
||||
MoveInfo moveInfo = mPendingMoves.get(i);
|
||||
if (moveInfo.holder == item) {
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(item);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
}
|
||||
endChangeAnimation(mPendingChanges, item);
|
||||
if (mPendingRemovals.remove(item)) {
|
||||
view.setAlpha(1);
|
||||
dispatchRemoveFinished(item);
|
||||
}
|
||||
if (mPendingAdditions.remove(item)) {
|
||||
view.setAlpha(1);
|
||||
dispatchAddFinished(item);
|
||||
}
|
||||
|
||||
for (int i = mChangesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
endChangeAnimation(changes, item);
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(i);
|
||||
}
|
||||
}
|
||||
for (int i = mMovesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
for (int j = moves.size() - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
if (moveInfo.holder == item) {
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(item);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
|
||||
if (additions.remove(item)) {
|
||||
view.setAlpha(1);
|
||||
dispatchAddFinished(item);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mRemoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mRemoveAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mAddAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mAddAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mChangeAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mChangeAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mMoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mMoveAnimations list");
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
private void resetAnimation(RecyclerView.ViewHolder holder) {
|
||||
holder.itemView.animate().setInterpolator(sDefaultInterpolator);
|
||||
endAnimation(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (!mPendingAdditions.isEmpty()
|
||||
|| !mPendingChanges.isEmpty()
|
||||
|| !mPendingMoves.isEmpty()
|
||||
|| !mPendingRemovals.isEmpty()
|
||||
|| !mMoveAnimations.isEmpty()
|
||||
|| !mRemoveAnimations.isEmpty()
|
||||
|| !mAddAnimations.isEmpty()
|
||||
|| !mChangeAnimations.isEmpty()
|
||||
|| !mMovesList.isEmpty()
|
||||
|| !mAdditionsList.isEmpty()
|
||||
|| !mChangesList.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of currently pending and running animations. If there are none
|
||||
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
|
||||
* listeners.
|
||||
*/
|
||||
void dispatchFinishedWhenDone() {
|
||||
if (!isRunning()) {
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
int count = mPendingMoves.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
MoveInfo item = mPendingMoves.get(i);
|
||||
View view = item.holder.itemView;
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(item.holder);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
count = mPendingRemovals.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
RecyclerView.ViewHolder item = mPendingRemovals.get(i);
|
||||
dispatchRemoveFinished(item);
|
||||
mPendingRemovals.remove(i);
|
||||
}
|
||||
count = mPendingAdditions.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
RecyclerView.ViewHolder item = mPendingAdditions.get(i);
|
||||
item.itemView.setAlpha(1);
|
||||
dispatchAddFinished(item);
|
||||
mPendingAdditions.remove(i);
|
||||
}
|
||||
count = mPendingChanges.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
endChangeAnimationIfNecessary(mPendingChanges.get(i));
|
||||
}
|
||||
mPendingChanges.clear();
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int listCount = mMovesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
count = moves.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
RecyclerView.ViewHolder item = moveInfo.holder;
|
||||
View view = item.itemView;
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(moveInfo.holder);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mAdditionsList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
|
||||
count = additions.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
RecyclerView.ViewHolder item = additions.get(j);
|
||||
View view = item.itemView;
|
||||
view.setAlpha(1);
|
||||
dispatchAddFinished(item);
|
||||
additions.remove(j);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mChangesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
count = changes.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
endChangeAnimationIfNecessary(changes.get(j));
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelAll(mRemoveAnimations);
|
||||
cancelAll(mMoveAnimations);
|
||||
cancelAll(mAddAnimations);
|
||||
cancelAll(mChangeAnimations);
|
||||
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
void cancelAll(List<RecyclerView.ViewHolder> viewHolders) {
|
||||
for (int i = viewHolders.size() - 1; i >= 0; i--) {
|
||||
viewHolders.get(i).itemView.animate().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
|
||||
* When this is the case:
|
||||
* <ul>
|
||||
* <li>If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both
|
||||
* ViewHolder arguments will be the same instance.
|
||||
* </li>
|
||||
* <li>
|
||||
* If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
|
||||
* then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and
|
||||
* run a move animation instead.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull List<Object> payloads) {
|
||||
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||
|
||||
public class DividerItem extends SimpleRecyclerItem<DividerView> {
|
||||
@Override
|
||||
public DividerView onCreateView(Context ctx) {
|
||||
DividerView v = new DividerView(ctx);
|
||||
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1)) {{
|
||||
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||
topMargin = bottomMargin = ViewUtils.dp(6);
|
||||
}});
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
|
||||
public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.PreferenceHolderView> {
|
||||
private Drawable mIcon;
|
||||
private CharSequence mTitle;
|
||||
private ValueProvider mSubtitle;
|
||||
private View.OnClickListener onClickListener;
|
||||
private int textColorRes;
|
||||
private boolean noTint;
|
||||
private ValueProvider valueProvider;
|
||||
|
||||
public PreferenceItem setTitle(CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setSubtitle(CharSequence subtitle) {
|
||||
mSubtitle = ()->subtitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setSubtitleProvider(ValueProvider mSubtitle) {
|
||||
this.mSubtitle = mSubtitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setValueProvider(ValueProvider valueProvider) {
|
||||
this.valueProvider = valueProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setValue(String text) {
|
||||
this.valueProvider = () -> text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setIcon(int iconRes) {
|
||||
mIcon = ContextCompat.getDrawable(SliceBeam.INSTANCE, iconRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setIcon(Drawable drawable) {
|
||||
mIcon = drawable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setNoTint(boolean noTint) {
|
||||
this.noTint = noTint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setTextColorRes(int textColorRes) {
|
||||
this.textColorRes = textColorRes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceItem setOnClickListener(View.OnClickListener onClickListener) {
|
||||
this.onClickListener = onClickListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceHolderView onCreateView(Context ctx) {
|
||||
return new PreferenceHolderView(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(PreferenceHolderView view) {
|
||||
view.bind(this);
|
||||
}
|
||||
|
||||
public final static class PreferenceHolderView extends LinearLayout implements IThemeView {
|
||||
private TextView title, subtitle;
|
||||
private ImageView icon;
|
||||
private TextView value;
|
||||
|
||||
public PreferenceHolderView(Context context) {
|
||||
super(context);
|
||||
|
||||
setOrientation(HORIZONTAL);
|
||||
setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
icon = new ImageView(context);
|
||||
icon.setLayoutParams(new LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
||||
setMarginStart(ViewUtils.dp(4));
|
||||
setMarginEnd(ViewUtils.dp(8));
|
||||
}});
|
||||
addView(icon);
|
||||
|
||||
LinearLayout innerLayout = new LinearLayout(context);
|
||||
innerLayout.setOrientation(VERTICAL);
|
||||
innerLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
title = new TextView(context);
|
||||
title.setEllipsize(TextUtils.TruncateAt.END);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
innerLayout.addView(title);
|
||||
|
||||
subtitle = new TextView(context);
|
||||
subtitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||
innerLayout.addView(subtitle);
|
||||
|
||||
addView(innerLayout, new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
setMarginStart(ViewUtils.dp(8));
|
||||
setMarginEnd(ViewUtils.dp(8));
|
||||
}});
|
||||
|
||||
value = new TextView(context);
|
||||
value.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||
value.setPadding(ViewUtils.dp(8), ViewUtils.dp(6), ViewUtils.dp(8), ViewUtils.dp(6));
|
||||
value.setVisibility(GONE);
|
||||
addView(value, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
int pad = ViewUtils.dp(12);
|
||||
setPadding(pad, pad, pad, pad);
|
||||
setMinimumHeight(ViewUtils.dp(56));
|
||||
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
onApplyTheme();
|
||||
}
|
||||
|
||||
void bind(PreferenceItem item) {
|
||||
title.setText(item.mTitle);
|
||||
title.setVisibility(TextUtils.isEmpty(item.mTitle) ? GONE : VISIBLE);
|
||||
|
||||
CharSequence sub = item.mSubtitle != null ? item.mSubtitle.provide() : null;
|
||||
subtitle.setText(sub);
|
||||
subtitle.setVisibility(TextUtils.isEmpty(sub) ? GONE : VISIBLE);
|
||||
|
||||
CharSequence v = item.valueProvider != null ? item.valueProvider.provide() : null;
|
||||
value.setText(v);
|
||||
value.setVisibility(TextUtils.isEmpty(v) ? GONE : VISIBLE);
|
||||
|
||||
if (item.mIcon != null) {
|
||||
icon.setVisibility(VISIBLE);
|
||||
icon.setImageDrawable(item.mIcon);
|
||||
} else {
|
||||
icon.setVisibility(GONE);
|
||||
}
|
||||
if (item.onClickListener != null) {
|
||||
setOnClickListener(item.onClickListener);
|
||||
} else {
|
||||
setClickable(false);
|
||||
}
|
||||
|
||||
if (item.textColorRes != 0) {
|
||||
title.setTextColor(ThemesRepo.getColor(item.textColorRes));
|
||||
}
|
||||
|
||||
if (item.textColorRes != 0 || item.mIcon != null) {
|
||||
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||
} else {
|
||||
title.setTypeface(Typeface.DEFAULT);
|
||||
}
|
||||
|
||||
if (item.noTint) {
|
||||
icon.setImageTintList(null);
|
||||
} else {
|
||||
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(item.textColorRes != 0 ? item.textColorRes : android.R.attr.textColorSecondary)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
value.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||
}
|
||||
}
|
||||
|
||||
public interface ValueProvider {
|
||||
CharSequence provide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||
import ru.ytkab0bp.slicebeam.view.BeamSwitch;
|
||||
|
||||
public class PreferenceSwitchItem extends SimpleRecyclerItem<PreferenceSwitchItem.SwitchPreferenceHolderView> {
|
||||
private Drawable mIcon;
|
||||
private CharSequence mTitle;
|
||||
private CharSequence mSubtitle;
|
||||
private String mKey;
|
||||
private boolean mDefaultValue;
|
||||
private CompoundButton.OnCheckedChangeListener mChangeListener;
|
||||
private ValueProvider valueProvider;
|
||||
|
||||
public PreferenceSwitchItem setValueProvider(ValueProvider valueProvider) {
|
||||
this.valueProvider = valueProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceSwitchItem setKeyAndDefaultValue(String key, boolean value) {
|
||||
mKey = key;
|
||||
mDefaultValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceSwitchItem setChangeListener(CompoundButton.OnCheckedChangeListener listener) {
|
||||
mChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceSwitchItem setTitle(CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceSwitchItem setSubtitle(CharSequence subtitle) {
|
||||
mSubtitle = subtitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceSwitchItem setIcon(int iconRes) {
|
||||
mIcon = ContextCompat.getDrawable(SliceBeam.INSTANCE, iconRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PreferenceSwitchItem setIcon(Drawable drawable) {
|
||||
mIcon = drawable;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SwitchPreferenceHolderView onCreateView(Context ctx) {
|
||||
return new SwitchPreferenceHolderView(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(SwitchPreferenceHolderView view) {
|
||||
view.bind(this);
|
||||
}
|
||||
|
||||
public final static class SwitchPreferenceHolderView extends LinearLayout implements IThemeView {
|
||||
public TextView title, subtitle;
|
||||
public ImageView icon;
|
||||
public BeamSwitch matSwitch;
|
||||
|
||||
public SwitchPreferenceHolderView(Context context) {
|
||||
super(context);
|
||||
|
||||
setOrientation(HORIZONTAL);
|
||||
setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
icon = new ImageView(context);
|
||||
icon.setLayoutParams(new LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
||||
setMarginEnd(ViewUtils.dp(16));
|
||||
gravity = Gravity.CENTER_VERTICAL;
|
||||
}});
|
||||
|
||||
LinearLayout innerLayout = new LinearLayout(context);
|
||||
innerLayout.setOrientation(VERTICAL);
|
||||
innerLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
title = new TextView(context);
|
||||
title.setEllipsize(TextUtils.TruncateAt.END);
|
||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
innerLayout.addView(title);
|
||||
|
||||
subtitle = new TextView(context);
|
||||
subtitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||
innerLayout.addView(subtitle);
|
||||
|
||||
addView(innerLayout, new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||
setMarginStart(ViewUtils.dp(8));
|
||||
setMarginEnd(ViewUtils.dp(8));
|
||||
gravity = Gravity.CENTER_VERTICAL;
|
||||
}});
|
||||
|
||||
addView(matSwitch = new BeamSwitch(context), new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewUtils.dp(32)));
|
||||
|
||||
int pad = ViewUtils.dp(12);
|
||||
setPadding(pad, pad, pad, pad);
|
||||
setMinimumHeight(ViewUtils.dp(52));
|
||||
onApplyTheme();
|
||||
}
|
||||
|
||||
void bind(PreferenceSwitchItem item) {
|
||||
title.setText(item.mTitle);
|
||||
subtitle.setText(item.mSubtitle);
|
||||
if (TextUtils.isEmpty(item.mSubtitle)) {
|
||||
subtitle.setVisibility(GONE);
|
||||
} else {
|
||||
subtitle.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (item.mIcon != null) {
|
||||
icon.setVisibility(VISIBLE);
|
||||
icon.setImageDrawable(item.mIcon);
|
||||
} else {
|
||||
icon.setVisibility(GONE);
|
||||
}
|
||||
if (item.mKey != null) {
|
||||
matSwitch.setChecked(Prefs.getPrefs().getBoolean(item.mKey, item.mDefaultValue));
|
||||
} else {
|
||||
matSwitch.setChecked(item.valueProvider.provide());
|
||||
}
|
||||
setOnClickListener(v -> {
|
||||
boolean check;
|
||||
if (item.mKey != null) {
|
||||
check = !Prefs.getPrefs().getBoolean(item.mKey, item.mDefaultValue);
|
||||
Prefs.getPrefs().edit().putBoolean(item.mKey, check).apply();
|
||||
matSwitch.setChecked(check);
|
||||
} else {
|
||||
matSwitch.setChecked(check = !matSwitch.isChecked());
|
||||
}
|
||||
|
||||
if (item.mChangeListener != null) {
|
||||
item.mChangeListener.onCheckedChanged(matSwitch, check);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyTheme() {
|
||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.colorAccent)));
|
||||
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||
}
|
||||
}
|
||||
|
||||
public interface ValueProvider {
|
||||
boolean provide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SimpleRecyclerAdapter extends RecyclerView.Adapter {
|
||||
private Map<Class<?>, Integer> viewType = new HashMap<>();
|
||||
private Map<Integer, SimpleRecyclerItem> viewCreator = new HashMap<>();
|
||||
private int lastType;
|
||||
|
||||
private List<SimpleRecyclerItem> items = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new RecyclerView.ViewHolder(viewCreator.get(viewType).onCreateView(parent.getContext())) {};
|
||||
}
|
||||
|
||||
/** @noinspection unchecked*/
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
items.get(position).onBindView(holder.itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return items.get(position).hashCode();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setItems(List<SimpleRecyclerItem> items) {
|
||||
this.items = items;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
Integer t = viewType.get(items.get(position).getClass());
|
||||
if (t == null) {
|
||||
viewType.put(items.get(position).getClass(), t = lastType++);
|
||||
viewCreator.put(t, items.get(position));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public List<SimpleRecyclerItem> getItems() {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
public abstract class SimpleRecyclerItem<V extends View> {
|
||||
public abstract V onCreateView(Context ctx);
|
||||
public void onBindView(V view) {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ru.ytkab0bp.slicebeam.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Space;
|
||||
|
||||
public class SpaceItem extends SimpleRecyclerItem<Space> {
|
||||
private int x, y;
|
||||
|
||||
public SpaceItem(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Space onCreateView(Context ctx) {
|
||||
return new Space(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(Space view) {
|
||||
view.setMinimumWidth(x);
|
||||
view.setMinimumHeight(y);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user