mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-24 01:12:41 +08:00
Compare commits
262 Commits
Author | SHA1 | Date | |
---|---|---|---|
97dbecaa2a | |||
e77e4cce07 | |||
dcf0bdce48 | |||
2a3df5de9d | |||
44ac4312e8 | |||
3494b043bc | |||
c552c4ac5b | |||
4f88d216d9 | |||
095ecd2aeb | |||
5a766682a3 | |||
3963674cb2 | |||
7b03554cd2 | |||
43442587c5 | |||
2f32e29c04 | |||
f946f33d96 | |||
7ac905e1f4 | |||
627a317308 | |||
48e98a1d33 | |||
d379d6b129 | |||
8e2e2abef4 | |||
7920c54761 | |||
fd50996cb2 | |||
5207b1a1c4 | |||
91d5fc284f | |||
4a1125cf1d | |||
2e96d09f67 | |||
8acc85061d | |||
41f0b0ed55 | |||
02eca61f40 | |||
2819ced303 | |||
755eae293e | |||
60580c8183 | |||
e9acd68ee4 | |||
7a4c7eb498 | |||
eb693eceb5 | |||
eedb7dd76f | |||
bc113e9093 | |||
dc449d4a1e | |||
668c8f7c3f | |||
5afebc7d07 | |||
35b0e3808a | |||
4b08cb55f5 | |||
a7f86227fb | |||
2077601464 | |||
6a7596c40b | |||
e4d38af4f5 | |||
70a1570441 | |||
9c077b3aa3 | |||
ca90b64378 | |||
f87b06989d | |||
6766a8cf4c | |||
5e761e2379 | |||
3783638c89 | |||
d038d13867 | |||
2efc3f6578 | |||
e175e9c438 | |||
a46bc11e42 | |||
b9b5d721c8 | |||
f4ba14cd13 | |||
b5b3e90b09 | |||
4263cef26a | |||
626e680510 | |||
b61ac481b9 | |||
ff684d4d4b | |||
7328610252 | |||
fd950e2aef | |||
2256828384 | |||
32684bc63e | |||
648ac941df | |||
0d4b4dc826 | |||
7c85969085 | |||
1fce3465c2 | |||
25747503cc | |||
76c578a9ea | |||
2da293ab21 | |||
88cbd0e970 | |||
a82abe2ec3 | |||
17ee92479c | |||
508ca27ec2 | |||
8949e3dc7d | |||
4280a071f6 | |||
48ed78ec36 | |||
3c964cfef9 | |||
184b037523 | |||
a49a918790 | |||
e3a58bf675 | |||
cc29dbda30 | |||
bc0ad5eab6 | |||
bdf86a7448 | |||
968546d43c | |||
680556d74e | |||
f490203b10 | |||
39d9585f1d | |||
2d414e544b | |||
513fcaa534 | |||
b41f7211e5 | |||
dd6cce1df1 | |||
ad54d2c76b | |||
867370ccee | |||
35eb78ca5d | |||
f1c3771c24 | |||
b012e2305c | |||
e309821743 | |||
56bedc9c6b | |||
59c5b13a05 | |||
b8b6cc1605 | |||
912b1b80ff | |||
6a9c64c2a1 | |||
54deecd312 | |||
403943a41f | |||
e7aa01ebc8 | |||
bf6d526284 | |||
c991cb4b22 | |||
3971e49ce1 | |||
6920ca1129 | |||
748e0cabcb | |||
b4b5f1ec93 | |||
5afaf85859 | |||
b65e417ecb | |||
23723a4ffd | |||
dab7ecd441 | |||
f1406d016f | |||
99e801b3bd | |||
20133e123c | |||
142a2a4750 | |||
629403a74d | |||
873d0f277d | |||
99719fafaf | |||
b550356f14 | |||
8c6202c194 | |||
f203ae37fc | |||
2006a9ea76 | |||
c9bc450d09 | |||
a1198f3a92 | |||
04248a89ce | |||
3639824df3 | |||
939861b5f0 | |||
ad5fc04a3b | |||
c39e097378 | |||
129a7e3765 | |||
643bb4519c | |||
b154cbf39d | |||
db91968519 | |||
5d58993b07 | |||
eea581f8d5 | |||
9bb3c77bae | |||
477a6859d7 | |||
f8f9671746 | |||
dc2759c599 | |||
653b4a2304 | |||
065ab033c9 | |||
11cbd24a6a | |||
fbf9859e0f | |||
2d7dfa53eb | |||
eff8d63c81 | |||
006cf0fc2c | |||
e5ca3530ff | |||
4de378907b | |||
bbdfb46a1e | |||
e6e2b3cd67 | |||
1d07046a74 | |||
de663f34b2 | |||
8d648fec43 | |||
835a81765e | |||
51ed936e30 | |||
ac16587cdc | |||
1e1eaa6c38 | |||
af17ae82c6 | |||
e23341c2b1 | |||
080bde4a63 | |||
1d739a1936 | |||
a927b5ed21 | |||
642c97812c | |||
e43d3579de | |||
6ea435deee | |||
94f749342d | |||
0769b7ef23 | |||
5086dcc82b | |||
56abd38e92 | |||
a7e6ae87df | |||
b5c584bb02 | |||
c8a3aecdf4 | |||
33c2378f41 | |||
38aafa7e5b | |||
4bb0811b2c | |||
4aefe1c5a3 | |||
c228d29707 | |||
56d1507aff | |||
72d31eaa64 | |||
4e8b84b67e | |||
5b94e31a12 | |||
692a37635e | |||
9cb1cea025 | |||
e13f198815 | |||
9a059c1056 | |||
ffb6cad8c2 | |||
d0a4863139 | |||
bb8837d58c | |||
a236b272c1 | |||
18de1eaf1c | |||
b1264c6912 | |||
9836566e55 | |||
d20461fa0e | |||
72ec34090d | |||
883a8705c3 | |||
6adaaf5500 | |||
5de771389e | |||
51cfbe524e | |||
217b93ef4f | |||
42156e1160 | |||
e7208d0c9d | |||
2f3b779199 | |||
916bdea59b | |||
d8688193d5 | |||
30b48b1f1f | |||
0fd382c1f6 | |||
fd20a1120b | |||
abcb548706 | |||
b056644385 | |||
71f72e8f36 | |||
1ab41f5a30 | |||
7dc58ea02c | |||
68eeee353e | |||
92fe1dc704 | |||
6e644b4f50 | |||
c47974115b | |||
535e88be9a | |||
e567c16221 | |||
d13af7548e | |||
5d750aec77 | |||
45b5ce0ef8 | |||
e3d1add090 | |||
a59bcc95e4 | |||
ac4414ca86 | |||
19263092fe | |||
6bafab785b | |||
62b1688d53 | |||
4d015cbe93 | |||
0da8f4faea | |||
b264151c46 | |||
3d2bc7cd4b | |||
85c26e6af7 | |||
b149efa234 | |||
72c222d59a | |||
153ad2268b | |||
1ba9b2eae1 | |||
be2da96cc0 | |||
a2405d69c5 | |||
b2a90c832f | |||
9a784fd467 | |||
d399b6acd1 | |||
0c3067973e | |||
411593590d | |||
a2677e2321 | |||
13c2d6b92d | |||
2f3bb80eeb | |||
e58cf45e07 | |||
7144b6a44c | |||
e8b17d3583 | |||
10ee2a837f | |||
5acc5a78d8 | |||
2ba6f27a27 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
*/mcs-unity*
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
687
LICENSE
687
LICENSE
@ -1,21 +1,674 @@
|
|||||||
MIT License
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (c) 2020 sinaioutlander
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Preamble
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The GNU General Public License is a free, copyleft license for
|
||||||
copies or substantial portions of the Software.
|
software and other kinds of works.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
The licenses for most software and other practical works are designed
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
share and change all versions of a program--to make sure it remains free
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
GNU General Public License for most of our software; it applies also to
|
||||||
SOFTWARE.
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
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 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. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
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 Affero 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 special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU 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 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 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 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 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
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 GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
|
115
README.md
115
README.md
@ -1,51 +1,112 @@
|
|||||||
# CppExplorer
|
<p align="center">
|
||||||
|
<img align="center" src="img/icon.png">
|
||||||
|
</p>
|
||||||
|
|
||||||
[]()
|
<p align="center">
|
||||||
|
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> and <b>Mono</b> Unity games, to aid with modding development.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="../../releases/latest">
|
||||||
|
<img src="https://img.shields.io/github/release/sinai-dev/Explorer.svg" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<img src="https://img.shields.io/github/downloads/sinai-dev/Explorer/total.svg" />
|
||||||
|
</p>
|
||||||
|
|
||||||
An in-game explorer and a suite of debugging tools for [IL2CPP](https://docs.unity3d.com/Manual/IL2CPP.html) Unity games, using [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader).
|
- [Releases](#releases)
|
||||||
|
- [Features](#features)
|
||||||
|
- [How to install](#how-to-install)
|
||||||
|
- [Mod Config](#mod-config)
|
||||||
|
- [Mouse Control](#mouse-control)
|
||||||
|
- [Building](#building)
|
||||||
|
- [Credits](#credits)
|
||||||
|
|
||||||
This was designed to be an IL2CPP-compatible equivalent to [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor).
|
## Releases
|
||||||
|
|
||||||
|
| Mod Loader | IL2CPP | Mono |
|
||||||
|
| ----------- | ------ | ---- |
|
||||||
|
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
|
||||||
|
| [BepInEx](https://github.com/BepInEx/BepInEx) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Mono.zip) |
|
||||||
|
|
||||||
|
<b>IL2CPP Issues:</b>
|
||||||
|
* Some methods may still fail with a `MissingMethodException`, please let me know if you experience this (with full debug log please).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Scene hierarchy explorer
|
|
||||||
* Search loaded assets with filters
|
<p align="center">
|
||||||
* Traverse and manipulate GameObjects
|
<a href="https://raw.githubusercontent.com/sinai-dev/UnityExplorer/master/img/preview.png">
|
||||||
* Generic Reflection inspector
|
<img src="img/preview.png" />
|
||||||
* REPL Console
|
</a>
|
||||||
* Inspect-under-mouse
|
</p>
|
||||||
|
|
||||||
|
* <b>Scene Explorer</b>: Simple menu to traverse the Transform heirarchy of the scene.
|
||||||
|
* <b>GameObject Inspector</b>: Various helpful tools to see and manipulate the GameObject, similar to what you can do in the Editor.
|
||||||
|
* <b>Reflection Inspector</b>: Inspect Properties and Fields. Can also set primitive values and evaluate primitive methods.
|
||||||
|
* <b>Search</b>: Search for UnityEngine.Objects with various filters, or use the helpers for static Instances and Classes.
|
||||||
|
* <b>C# Console</b>: Interactive console for evaluating C# methods on the fly, with some basic helpers.
|
||||||
|
* <b>Inspect-under-mouse</b>: Hover over an object with a collider and inspect it by clicking on it.
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
|
### BepInEx
|
||||||
|
|
||||||
1. Download <b>CppExplorer.zip</b> from [Releases](https://github.com/sinaioutlander/CppExplorer/releases).
|
0. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game.
|
||||||
2. Unzip the file into the `Mods` folder in your game's installation directory, created by MelonLoader.
|
1. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
|
||||||
3. Make sure it's not in a sub-folder, `CppExplorer.dll` and `mcs.dll` should be directly in the `Mods\` folder.
|
2. Take the `UnityExplorer.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
|
||||||
|
3. Take the `UnityExplorer\` folder (with `explorerui.bundle`) and put it in `[GameFolder]\Mods\`, so it looks like `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
|
||||||
|
4. In IL2CPP, it is highly recommended to get the base Unity libs for the game's Unity version and put them in the `BepInEx\unhollowed\base\` folder.
|
||||||
|
|
||||||
## How to use
|
### MelonLoader
|
||||||
|
|
||||||
* Press F7 to show or hide the menu.
|
0. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) for your game.
|
||||||
* Simply browse through the scene, search for objects, etc, it's pretty self-explanatory.
|
1. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
|
||||||
|
2. Take the contents of the release and put it in the `[GameFolder]\Mods\` folder. It should look like `[GameFolder]\Mods\UnityExplorer.dll` and `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
|
||||||
|
|
||||||
## Images
|
## Mod Config
|
||||||
|
|
||||||
Scene explorer, and inspection of a MonoBehaviour object:
|
You can access the settings via the "Options" page of the main menu, or directly from the config at `Mods\UnityExplorer\config.xml` (generated after first launch).
|
||||||
|
|
||||||
[](https://i.imgur.com/Yxizwcz.png)
|
`Main Menu Toggle` (KeyCode)
|
||||||
|
* Default: `F7`
|
||||||
|
* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes.
|
||||||
|
|
||||||
Search feature:
|
`Force Unlock Mouse` (bool)
|
||||||
|
* Default: `true`
|
||||||
|
* Forces the cursor to be unlocked and visible while the UnityExplorer menu is open, and prevents anything else taking control.
|
||||||
|
|
||||||
[](https://i.imgur.com/F9ZfMvz.png)
|
`Default Page Limit` (int)
|
||||||
|
* Default: `25`
|
||||||
|
* Sets the default items per page when viewing lists or search results.
|
||||||
|
* <b>Requires a restart to take effect</b>, apart from Reflection Inspector tabs.
|
||||||
|
|
||||||
|
`Default Output Path` (string)
|
||||||
|
* Default: `Mods\Explorer`
|
||||||
|
* Where output is generated to, by default (for Texture PNG saving, etc).
|
||||||
|
* Currently this is not actually used for anything, but it will be soon.
|
||||||
|
|
||||||
REPL console:
|
`Log Unity Debug` (bool)
|
||||||
|
* Default: `false`
|
||||||
|
* Listens for Unity `Debug.Log` messages and prints them to UnityExplorer's log.
|
||||||
|
|
||||||
[](https://i.imgur.com/14Dbtf8.png)
|
## Building
|
||||||
|
|
||||||
|
If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one IL2CPP and one Mono game, with BepInEx and MelonLoader installed for both.
|
||||||
|
|
||||||
|
1. Install MelonLoader or BepInEx for your game.
|
||||||
|
2. Open the `src\Explorer.csproj` file in a text editor.
|
||||||
|
3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader IL2CPP game.
|
||||||
|
4. Open the `src\Explorer.sln` project.
|
||||||
|
5. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it.
|
||||||
|
5. The DLLs are built to the `Release\` folder in the root of the repository.
|
||||||
|
6. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Written by Sinai.
|
Written by Sinai.
|
||||||
|
|
||||||
Thanks to:
|
### Licensing
|
||||||
* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the UI style.
|
|
||||||
* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor in `mcs.dll` since it was causing an exception with the Hook it attempted.
|
This project uses code from:
|
||||||
|
* (GPL) [ManlyMarco](https://github.com/ManlyMarco)'s [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for some aspects of the C# Console and Auto-Complete features. The snippets I used are indicated with a comment.
|
||||||
|
* (MIT) [denikson](https://github.com/denikson) (aka Horse)'s [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor since it was causing an exception with the Hook it attempted in IL2CPP.
|
||||||
|
* (Apache) [InGameCodeEditor](https://assetstore.unity.com/packages/tools/gui/ingame-code-editor-144254) was used as the base for the syntax highlighting for UnityExplorer's C# console, although it has been heavily rewritten and optimized. Used classes are in the `UnityExplorer.CSConsole.Lexer` namespace.
|
||||||
|
BIN
img/icon.png
Normal file
BIN
img/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
img/preview.png
Normal file
BIN
img/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 407 KiB |
BIN
img/social.png
Normal file
BIN
img/social.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
BIN
lib/0Harmony.dll
Normal file
BIN
lib/0Harmony.dll
Normal file
Binary file not shown.
BIN
lib/BepInEx.Core.dll
Normal file
BIN
lib/BepInEx.Core.dll
Normal file
Binary file not shown.
BIN
lib/BepInEx.IL2CPP.dll
Normal file
BIN
lib/BepInEx.IL2CPP.dll
Normal file
Binary file not shown.
BIN
lib/BepInEx.dll
Normal file
BIN
lib/BepInEx.dll
Normal file
Binary file not shown.
BIN
lib/MelonLoader.ModHandler.dll
Normal file
BIN
lib/MelonLoader.ModHandler.dll
Normal file
Binary file not shown.
BIN
lib/UnityEngine.UI.dll
Normal file
BIN
lib/UnityEngine.UI.dll
Normal file
Binary file not shown.
BIN
lib/UnityEngine.dll
Normal file
BIN
lib/UnityEngine.dll
Normal file
Binary file not shown.
BIN
lib/mcs.dll
BIN
lib/mcs.dll
Binary file not shown.
BIN
resources/explorerui.bundle
Normal file
BIN
resources/explorerui.bundle
Normal file
Binary file not shown.
314
src/CSConsole/AutoCompleter.cs
Normal file
314
src/CSConsole/AutoCompleter.cs
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole
|
||||||
|
{
|
||||||
|
public class AutoCompleter
|
||||||
|
{
|
||||||
|
public static AutoCompleter Instance;
|
||||||
|
|
||||||
|
public const int MAX_LABELS = 500;
|
||||||
|
private const int UPDATES_PER_BATCH = 100;
|
||||||
|
|
||||||
|
public static GameObject m_mainObj;
|
||||||
|
//private static RectTransform m_thisRect;
|
||||||
|
|
||||||
|
private static readonly List<GameObject> m_suggestionButtons = new List<GameObject>();
|
||||||
|
private static readonly List<Text> m_suggestionTexts = new List<Text>();
|
||||||
|
private static readonly List<Text> m_hiddenSuggestionTexts = new List<Text>();
|
||||||
|
|
||||||
|
private static bool m_suggestionsDirty;
|
||||||
|
private static Suggestion[] m_suggestions = new Suggestion[0];
|
||||||
|
private static int m_lastBatchIndex;
|
||||||
|
|
||||||
|
private static string m_prevInput = "NULL";
|
||||||
|
private static int m_lastCaretPos;
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
ConstructUI();
|
||||||
|
|
||||||
|
m_mainObj.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Update()
|
||||||
|
{
|
||||||
|
if (!m_mainObj)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CodeEditor.EnableAutocompletes)
|
||||||
|
{
|
||||||
|
if (m_mainObj.activeSelf)
|
||||||
|
{
|
||||||
|
m_mainObj.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshButtons();
|
||||||
|
|
||||||
|
UpdatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetSuggestions(Suggestion[] suggestions)
|
||||||
|
{
|
||||||
|
m_suggestions = suggestions;
|
||||||
|
|
||||||
|
m_suggestionsDirty = true;
|
||||||
|
m_lastBatchIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshButtons()
|
||||||
|
{
|
||||||
|
if (!m_suggestionsDirty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_suggestions.Length < 1)
|
||||||
|
{
|
||||||
|
if (m_mainObj.activeSelf)
|
||||||
|
{
|
||||||
|
m_mainObj?.SetActive(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_mainObj.activeSelf)
|
||||||
|
{
|
||||||
|
m_mainObj.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_suggestions.Length < 1 || m_lastBatchIndex >= MAX_LABELS)
|
||||||
|
{
|
||||||
|
m_suggestionsDirty = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = m_lastBatchIndex + UPDATES_PER_BATCH;
|
||||||
|
for (int i = m_lastBatchIndex; i < end && i < MAX_LABELS; i++)
|
||||||
|
{
|
||||||
|
if (i >= m_suggestions.Length)
|
||||||
|
{
|
||||||
|
if (m_suggestionButtons[i].activeSelf)
|
||||||
|
{
|
||||||
|
m_suggestionButtons[i].SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!m_suggestionButtons[i].activeSelf)
|
||||||
|
{
|
||||||
|
m_suggestionButtons[i].SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestion = m_suggestions[i];
|
||||||
|
var label = m_suggestionTexts[i];
|
||||||
|
var hiddenLabel = m_hiddenSuggestionTexts[i];
|
||||||
|
|
||||||
|
label.text = suggestion.Full;
|
||||||
|
hiddenLabel.text = suggestion.Addition;
|
||||||
|
|
||||||
|
label.color = suggestion.TextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastBatchIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastBatchIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdatePosition()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var editor = CSConsolePage.Instance.m_codeEditor;
|
||||||
|
|
||||||
|
if (!editor.InputField.isFocused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var textGen = editor.InputText.cachedTextGenerator;
|
||||||
|
int caretPos = editor.m_lastCaretPos;
|
||||||
|
|
||||||
|
if (caretPos == m_lastCaretPos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_lastCaretPos = caretPos;
|
||||||
|
|
||||||
|
if (caretPos >= 1)
|
||||||
|
caretPos--;
|
||||||
|
|
||||||
|
var pos = textGen.characters[caretPos].cursorPos;
|
||||||
|
|
||||||
|
pos = editor.InputField.transform.TransformPoint(pos);
|
||||||
|
|
||||||
|
m_mainObj.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
|
||||||
|
}
|
||||||
|
catch //(Exception e)
|
||||||
|
{
|
||||||
|
//ExplorerCore.Log(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly char[] splitChars = new[] { '{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?' };
|
||||||
|
|
||||||
|
public static void CheckAutocomplete()
|
||||||
|
{
|
||||||
|
var m_codeEditor = CSConsolePage.Instance.m_codeEditor;
|
||||||
|
string input = m_codeEditor.InputField.text;
|
||||||
|
int caretIndex = m_codeEditor.InputField.caretPosition;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int start = caretIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, caretIndex - 1) + 1;
|
||||||
|
input = input.Substring(start, caretIndex - start).Trim();
|
||||||
|
}
|
||||||
|
catch (ArgumentException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(input) && input != m_prevInput)
|
||||||
|
{
|
||||||
|
GetAutocompletes(input);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClearAutocompletes();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_prevInput = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearAutocompletes()
|
||||||
|
{
|
||||||
|
if (CodeEditor.AutoCompletes.Any())
|
||||||
|
{
|
||||||
|
CodeEditor.AutoCompletes.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetAutocompletes(string input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Credit ManylMarco
|
||||||
|
CodeEditor.AutoCompletes.Clear();
|
||||||
|
string[] completions = CSConsolePage.Instance.m_evaluator.GetCompletions(input, out string prefix);
|
||||||
|
if (completions != null)
|
||||||
|
{
|
||||||
|
if (prefix == null)
|
||||||
|
{
|
||||||
|
prefix = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeEditor.AutoCompletes.AddRange(completions
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x))
|
||||||
|
.Select(x => new Suggestion(x, prefix, Suggestion.Contexts.Other))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
string trimmed = input.Trim();
|
||||||
|
if (trimmed.StartsWith("using"))
|
||||||
|
{
|
||||||
|
trimmed = trimmed.Remove(0, 5).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Suggestion> namespaces = Suggestion.Namespaces
|
||||||
|
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
||||||
|
.Select(x => new Suggestion(
|
||||||
|
x.Substring(trimmed.Length),
|
||||||
|
x.Substring(0, trimmed.Length),
|
||||||
|
Suggestion.Contexts.Namespace));
|
||||||
|
|
||||||
|
CodeEditor.AutoCompletes.AddRange(namespaces);
|
||||||
|
|
||||||
|
IEnumerable<Suggestion> keywords = Suggestion.Keywords
|
||||||
|
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
||||||
|
.Select(x => new Suggestion(
|
||||||
|
x.Substring(trimmed.Length),
|
||||||
|
x.Substring(0, trimmed.Length),
|
||||||
|
Suggestion.Contexts.Keyword));
|
||||||
|
|
||||||
|
CodeEditor.AutoCompletes.AddRange(keywords);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ExplorerCore.Log("Autocomplete error:\r\n" + ex.ToString());
|
||||||
|
ClearAutocompletes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI Construction
|
||||||
|
|
||||||
|
private static void ConstructUI()
|
||||||
|
{
|
||||||
|
var parent = UIManager.CanvasRoot;
|
||||||
|
|
||||||
|
var obj = UIFactory.CreateScrollView(parent, out GameObject content, out _, new Color(0.1f, 0.1f, 0.1f, 0.95f));
|
||||||
|
|
||||||
|
m_mainObj = obj;
|
||||||
|
|
||||||
|
var mainRect = obj.GetComponent<RectTransform>();
|
||||||
|
//m_thisRect = mainRect;
|
||||||
|
mainRect.pivot = new Vector2(0f, 1f);
|
||||||
|
mainRect.anchorMin = new Vector2(0.45f, 0.45f);
|
||||||
|
mainRect.anchorMax = new Vector2(0.65f, 0.6f);
|
||||||
|
mainRect.offsetMin = Vector2.zero;
|
||||||
|
mainRect.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
var mainGroup = content.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childControlHeight = false;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = false;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_LABELS; i++)
|
||||||
|
{
|
||||||
|
var buttonObj = UIFactory.CreateButton(content);
|
||||||
|
Button btn = buttonObj.GetComponent<Button>();
|
||||||
|
ColorBlock btnColors = btn.colors;
|
||||||
|
btnColors.normalColor = new Color(0f, 0f, 0f, 0f);
|
||||||
|
btnColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||||
|
btn.colors = btnColors;
|
||||||
|
|
||||||
|
var nav = btn.navigation;
|
||||||
|
nav.mode = Navigation.Mode.Vertical;
|
||||||
|
btn.navigation = nav;
|
||||||
|
|
||||||
|
var btnLayout = buttonObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minHeight = 20;
|
||||||
|
|
||||||
|
var text = btn.GetComponentInChildren<Text>();
|
||||||
|
text.alignment = TextAnchor.MiddleLeft;
|
||||||
|
text.color = Color.white;
|
||||||
|
|
||||||
|
var hiddenChild = UIFactory.CreateUIObject("HiddenText", buttonObj);
|
||||||
|
hiddenChild.SetActive(false);
|
||||||
|
var hiddenText = hiddenChild.AddComponent<Text>();
|
||||||
|
m_hiddenSuggestionTexts.Add(hiddenText);
|
||||||
|
btn.onClick.AddListener(UseAutocompleteButton);
|
||||||
|
|
||||||
|
void UseAutocompleteButton()
|
||||||
|
{
|
||||||
|
CSConsolePage.Instance.m_codeEditor.UseAutocomplete(hiddenText.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_suggestionButtons.Add(buttonObj);
|
||||||
|
m_suggestionTexts.Add(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
308
src/CSConsole/CSharpLexer.cs
Normal file
308
src/CSConsole/CSharpLexer.cs
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.CSConsole.Lexer;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole
|
||||||
|
{
|
||||||
|
public struct LexerMatchInfo
|
||||||
|
{
|
||||||
|
public int startIndex;
|
||||||
|
public int endIndex;
|
||||||
|
public string htmlColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DelimiterType
|
||||||
|
{
|
||||||
|
Start,
|
||||||
|
End,
|
||||||
|
};
|
||||||
|
|
||||||
|
public class CSharpLexer
|
||||||
|
{
|
||||||
|
private string inputString;
|
||||||
|
private readonly Matcher[] matchers;
|
||||||
|
private readonly HashSet<char> startDelimiters;
|
||||||
|
private readonly HashSet<char> endDelimiters;
|
||||||
|
private int currentIndex;
|
||||||
|
private int currentLookaheadIndex;
|
||||||
|
|
||||||
|
public char Current { get; private set; }
|
||||||
|
public char Previous { get; private set; }
|
||||||
|
|
||||||
|
public bool EndOfStream => currentLookaheadIndex >= inputString.Length;
|
||||||
|
|
||||||
|
public static char indentOpen = '{';
|
||||||
|
public static char indentClose = '}';
|
||||||
|
private static StringBuilder indentBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
public static char[] delimiters = new[]
|
||||||
|
{
|
||||||
|
'[', ']', '(', ')', '{', '}', ';', ':', ',', '.'
|
||||||
|
};
|
||||||
|
public static CommentMatch commentMatcher = new CommentMatch();
|
||||||
|
public static SymbolMatch symbolMatcher = new SymbolMatch();
|
||||||
|
public static NumberMatch numberMatcher = new NumberMatch();
|
||||||
|
public static StringMatch stringMatcher = new StringMatch();
|
||||||
|
|
||||||
|
public static KeywordMatch validKeywordMatcher = new KeywordMatch
|
||||||
|
{
|
||||||
|
highlightColor = new Color(0.33f, 0.61f, 0.83f, 1.0f),
|
||||||
|
Keywords = new[] { "add", "as", "ascending", "await", "bool", "break", "by", "byte",
|
||||||
|
"case", "catch", "char", "checked", "const", "continue", "decimal", "default", "descending", "do", "dynamic",
|
||||||
|
"else", "equals", "false", "finally", "float", "for", "foreach", "from", "global", "goto", "group",
|
||||||
|
"if", "in", "int", "into", "is", "join", "let", "lock", "long", "new", "null", "object", "on", "orderby", "out",
|
||||||
|
"ref", "remove", "return", "sbyte", "select", "short", "sizeof", "stackalloc", "string",
|
||||||
|
"switch", "throw", "true", "try", "typeof", "uint", "ulong", "ushort", "var", "where", "while", "yield" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public static KeywordMatch invalidKeywordMatcher = new KeywordMatch()
|
||||||
|
{
|
||||||
|
highlightColor = new Color(0.95f, 0.10f, 0.10f, 1.0f),
|
||||||
|
Keywords = new[] { "abstract", "async", "base", "class", "delegate", "enum", "explicit", "extern", "fixed", "get",
|
||||||
|
"implicit", "interface", "internal", "namespace", "operator", "override", "params", "private", "protected", "public",
|
||||||
|
"using", "partial", "readonly", "sealed", "set", "static", "struct", "this", "unchecked", "unsafe", "value", "virtual", "volatile", "void" }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ~~~~~~~ ctor ~~~~~~~
|
||||||
|
|
||||||
|
public CSharpLexer()
|
||||||
|
{
|
||||||
|
startDelimiters = new HashSet<char>(delimiters);
|
||||||
|
endDelimiters = new HashSet<char>(delimiters);
|
||||||
|
|
||||||
|
this.matchers = new Matcher[]
|
||||||
|
{
|
||||||
|
commentMatcher,
|
||||||
|
symbolMatcher,
|
||||||
|
numberMatcher,
|
||||||
|
stringMatcher,
|
||||||
|
validKeywordMatcher,
|
||||||
|
invalidKeywordMatcher,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (Matcher lexer in matchers)
|
||||||
|
{
|
||||||
|
foreach (char c in lexer.StartChars)
|
||||||
|
{
|
||||||
|
if (!startDelimiters.Contains(c))
|
||||||
|
startDelimiters.Add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (char c in lexer.EndChars)
|
||||||
|
{
|
||||||
|
if (!endDelimiters.Contains(c))
|
||||||
|
endDelimiters.Add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~ Lex Matching ~~~~~~~
|
||||||
|
|
||||||
|
public IEnumerable<LexerMatchInfo> GetMatches(string input)
|
||||||
|
{
|
||||||
|
if (input == null || matchers == null || matchers.Length == 0)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputString = input;
|
||||||
|
Current = ' ';
|
||||||
|
Previous = ' ';
|
||||||
|
currentIndex = 0;
|
||||||
|
currentLookaheadIndex = 0;
|
||||||
|
|
||||||
|
while (!EndOfStream)
|
||||||
|
{
|
||||||
|
bool didMatchLexer = false;
|
||||||
|
|
||||||
|
ReadWhiteSpace();
|
||||||
|
|
||||||
|
foreach (Matcher matcher in matchers)
|
||||||
|
{
|
||||||
|
int startIndex = currentIndex;
|
||||||
|
|
||||||
|
bool isMatched = matcher.IsMatch(this);
|
||||||
|
|
||||||
|
if (isMatched)
|
||||||
|
{
|
||||||
|
int endIndex = currentIndex;
|
||||||
|
|
||||||
|
didMatchLexer = true;
|
||||||
|
|
||||||
|
yield return new LexerMatchInfo
|
||||||
|
{
|
||||||
|
startIndex = startIndex,
|
||||||
|
endIndex = endIndex,
|
||||||
|
htmlColor = matcher.HexColor,
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!didMatchLexer)
|
||||||
|
{
|
||||||
|
ReadNext();
|
||||||
|
Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~ Indent ~~~~~~~
|
||||||
|
|
||||||
|
public static string GetIndentForInput(string input, int indent, out int caretPosition)
|
||||||
|
{
|
||||||
|
indentBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
indent += 1;
|
||||||
|
|
||||||
|
bool stringState = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
if (input[i] == '"')
|
||||||
|
{
|
||||||
|
stringState = !stringState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input[i] == '\n')
|
||||||
|
{
|
||||||
|
indentBuilder.Append('\n');
|
||||||
|
for (int j = 0; j < indent; j++)
|
||||||
|
{
|
||||||
|
indentBuilder.Append("\t");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (input[i] == '\t')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!stringState && input[i] == indentOpen)
|
||||||
|
{
|
||||||
|
indentBuilder.Append(indentOpen);
|
||||||
|
indent++;
|
||||||
|
}
|
||||||
|
else if (!stringState && input[i] == indentClose)
|
||||||
|
{
|
||||||
|
indentBuilder.Append(indentClose);
|
||||||
|
indent--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indentBuilder.Append(input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string formattedSection = indentBuilder.ToString();
|
||||||
|
|
||||||
|
caretPosition = formattedSection.Length - 1;
|
||||||
|
|
||||||
|
for (int i = formattedSection.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (formattedSection[i] == '\n')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
caretPosition = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetIndentLevel(string inputString, int startIndex, int endIndex)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
|
||||||
|
for (int i = startIndex; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
if (inputString[i] == '\t')
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for end line or other characters
|
||||||
|
if (inputString[i] == '\n' || inputString[i] != ' ')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lexer reading
|
||||||
|
|
||||||
|
public char ReadNext()
|
||||||
|
{
|
||||||
|
if (EndOfStream)
|
||||||
|
{
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
Previous = Current;
|
||||||
|
|
||||||
|
Current = inputString[currentLookaheadIndex];
|
||||||
|
currentLookaheadIndex++;
|
||||||
|
|
||||||
|
return Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rollback(int amount = -1)
|
||||||
|
{
|
||||||
|
if (amount == -1)
|
||||||
|
{
|
||||||
|
currentLookaheadIndex = currentIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (currentLookaheadIndex > currentIndex)
|
||||||
|
{
|
||||||
|
currentLookaheadIndex -= amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int previousIndex = currentLookaheadIndex - 1;
|
||||||
|
|
||||||
|
if (previousIndex >= inputString.Length)
|
||||||
|
{
|
||||||
|
Previous = inputString[inputString.Length - 1];
|
||||||
|
}
|
||||||
|
else if (previousIndex >= 0)
|
||||||
|
{
|
||||||
|
Previous = inputString[previousIndex];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Previous = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Commit()
|
||||||
|
{
|
||||||
|
currentIndex = currentLookaheadIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSpecialSymbol(char character, DelimiterType position = DelimiterType.Start)
|
||||||
|
{
|
||||||
|
if (position == DelimiterType.Start)
|
||||||
|
{
|
||||||
|
return startDelimiters.Contains(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
return endDelimiters.Contains(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadWhiteSpace()
|
||||||
|
{
|
||||||
|
while (char.IsWhiteSpace(ReadNext()) == true)
|
||||||
|
{
|
||||||
|
Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
481
src/CSConsole/CodeEditor.cs
Normal file
481
src/CSConsole/CodeEditor.cs
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
using UnityExplorer.CSConsole.Lexer;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole
|
||||||
|
{
|
||||||
|
// Handles most of the UI side of the C# console, including syntax highlighting.
|
||||||
|
|
||||||
|
public class CodeEditor
|
||||||
|
{
|
||||||
|
public InputField InputField { get; internal set; }
|
||||||
|
public Text InputText { get; internal set; }
|
||||||
|
public int CurrentIndent { get; private set; }
|
||||||
|
|
||||||
|
public static bool EnableCtrlRShortcut { get; set; } = true;
|
||||||
|
public static bool EnableAutoIndent { get; set; } = true;
|
||||||
|
public static bool EnableAutocompletes { get; set; } = true;
|
||||||
|
public static List<Suggestion> AutoCompletes = new List<Suggestion>();
|
||||||
|
|
||||||
|
public string HighlightedText => inputHighlightText.text;
|
||||||
|
private Text inputHighlightText;
|
||||||
|
|
||||||
|
private readonly CSharpLexer highlightLexer;
|
||||||
|
private readonly StringBuilder sbHighlight;
|
||||||
|
|
||||||
|
internal int m_lastCaretPos;
|
||||||
|
internal int m_fixCaretPos;
|
||||||
|
internal bool m_fixwanted;
|
||||||
|
internal float m_lastSelectAlpha;
|
||||||
|
|
||||||
|
private static readonly KeyCode[] onFocusKeys =
|
||||||
|
{
|
||||||
|
KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow,
|
||||||
|
KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow
|
||||||
|
};
|
||||||
|
|
||||||
|
internal const string STARTUP_TEXT = @"Welcome to the UnityExplorer C# Console.
|
||||||
|
|
||||||
|
The following helper methods are available:
|
||||||
|
|
||||||
|
* <color=#add490>Log(""message"")</color> logs a message to the debug console
|
||||||
|
|
||||||
|
* <color=#add490>CurrentTarget()</color> returns the currently inspected target on the Home page
|
||||||
|
|
||||||
|
* <color=#add490>AllTargets()</color> returns an object[] array containing all inspected instances
|
||||||
|
|
||||||
|
* <color=#add490>Inspect(someObject)</color> to inspect an instance, eg. Inspect(Camera.main);
|
||||||
|
|
||||||
|
* <color=#add490>Inspect(typeof(SomeClass))</color> to inspect a Class with static reflection
|
||||||
|
|
||||||
|
* <color=#add490>AddUsing(""SomeNamespace"")</color> adds a using directive to the C# console
|
||||||
|
|
||||||
|
* <color=#add490>GetUsing()</color> logs the current using directives to the debug console
|
||||||
|
|
||||||
|
* <color=#add490>Reset()</color> resets all using directives and variables
|
||||||
|
";
|
||||||
|
|
||||||
|
public CodeEditor()
|
||||||
|
{
|
||||||
|
sbHighlight = new StringBuilder();
|
||||||
|
highlightLexer = new CSharpLexer();
|
||||||
|
|
||||||
|
ConstructUI();
|
||||||
|
|
||||||
|
InputField.onValueChanged.AddListener((string s) => { OnInputChanged(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (EnableCtrlRShortcut)
|
||||||
|
{
|
||||||
|
if ((InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
|
||||||
|
&& InputManager.GetKeyDown(KeyCode.R))
|
||||||
|
{
|
||||||
|
var text = InputField.text.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
CSConsolePage.Instance.Evaluate(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EnableAutoIndent && InputManager.GetKeyDown(KeyCode.Return))
|
||||||
|
AutoIndentCaret();
|
||||||
|
|
||||||
|
if (EnableAutocompletes && InputField.isFocused)
|
||||||
|
{
|
||||||
|
if (InputManager.GetMouseButton(0) || onFocusKeys.Any(it => InputManager.GetKeyDown(it)))
|
||||||
|
UpdateAutocompletes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fixCaretPos > 0)
|
||||||
|
{
|
||||||
|
if (!m_fixwanted)
|
||||||
|
{
|
||||||
|
EventSystem.current.SetSelectedGameObject(InputField.gameObject, null);
|
||||||
|
m_fixwanted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InputField.caretPosition = m_fixCaretPos;
|
||||||
|
InputField.selectionFocusPosition = m_fixCaretPos;
|
||||||
|
|
||||||
|
m_fixwanted = false;
|
||||||
|
m_fixCaretPos = -1;
|
||||||
|
|
||||||
|
var color = InputField.selectionColor;
|
||||||
|
color.a = m_lastSelectAlpha;
|
||||||
|
InputField.selectionColor = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (InputField.caretPosition > 0)
|
||||||
|
{
|
||||||
|
m_lastCaretPos = InputField.caretPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void UpdateAutocompletes()
|
||||||
|
{
|
||||||
|
AutoCompleter.CheckAutocomplete();
|
||||||
|
AutoCompleter.SetSuggestions(AutoCompletes.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UseAutocomplete(string suggestion)
|
||||||
|
{
|
||||||
|
string input = InputField.text;
|
||||||
|
input = input.Insert(m_lastCaretPos, suggestion);
|
||||||
|
InputField.text = input;
|
||||||
|
|
||||||
|
m_fixCaretPos = m_lastCaretPos += suggestion.Length;
|
||||||
|
|
||||||
|
var color = InputField.selectionColor;
|
||||||
|
m_lastSelectAlpha = color.a;
|
||||||
|
color.a = 0f;
|
||||||
|
InputField.selectionColor = color;
|
||||||
|
|
||||||
|
AutoCompleter.ClearAutocompletes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnInputChanged(string newInput, bool forceUpdate = false)
|
||||||
|
{
|
||||||
|
string newText = newInput;
|
||||||
|
|
||||||
|
UpdateIndent(newInput);
|
||||||
|
|
||||||
|
if (!forceUpdate && string.IsNullOrEmpty(newText))
|
||||||
|
inputHighlightText.text = string.Empty;
|
||||||
|
else
|
||||||
|
inputHighlightText.text = SyntaxHighlightContent(newText);
|
||||||
|
|
||||||
|
UpdateAutocompletes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIndent(string newText)
|
||||||
|
{
|
||||||
|
int caret = InputField.caretPosition;
|
||||||
|
|
||||||
|
int len = newText.Length;
|
||||||
|
if (caret < 0 || caret >= len)
|
||||||
|
{
|
||||||
|
while (caret >= 0 && caret >= len)
|
||||||
|
caret--;
|
||||||
|
|
||||||
|
if (caret < 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentIndent = 0;
|
||||||
|
|
||||||
|
bool stringState = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < caret && i < newText.Length; i++)
|
||||||
|
{
|
||||||
|
char character = newText[i];
|
||||||
|
|
||||||
|
if (character == '"')
|
||||||
|
stringState = !stringState;
|
||||||
|
else if (!stringState && character == CSharpLexer.indentOpen)
|
||||||
|
CurrentIndent++;
|
||||||
|
else if (!stringState && character == CSharpLexer.indentClose)
|
||||||
|
CurrentIndent--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentIndent < 0)
|
||||||
|
CurrentIndent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string CLOSE_COLOR_TAG = "</color>";
|
||||||
|
|
||||||
|
private string SyntaxHighlightContent(string inputText)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
sbHighlight.Length = 0;
|
||||||
|
|
||||||
|
foreach (LexerMatchInfo match in highlightLexer.GetMatches(inputText))
|
||||||
|
{
|
||||||
|
for (int i = offset; i < match.startIndex; i++)
|
||||||
|
{
|
||||||
|
sbHighlight.Append(inputText[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sbHighlight.Append($"{match.htmlColor}");
|
||||||
|
|
||||||
|
for (int i = match.startIndex; i < match.endIndex; i++)
|
||||||
|
{
|
||||||
|
sbHighlight.Append(inputText[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sbHighlight.Append(CLOSE_COLOR_TAG);
|
||||||
|
|
||||||
|
offset = match.endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = offset; i < inputText.Length; i++)
|
||||||
|
{
|
||||||
|
sbHighlight.Append(inputText[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputText = sbHighlight.ToString();
|
||||||
|
|
||||||
|
return inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoIndentCaret()
|
||||||
|
{
|
||||||
|
if (CurrentIndent > 0)
|
||||||
|
{
|
||||||
|
string indent = GetAutoIndentTab(CurrentIndent);
|
||||||
|
|
||||||
|
if (indent.Length > 0)
|
||||||
|
{
|
||||||
|
int caretPos = InputField.caretPosition;
|
||||||
|
|
||||||
|
string indentMinusOne = indent.Substring(0, indent.Length - 1);
|
||||||
|
|
||||||
|
// get last index of {
|
||||||
|
// chuck it on the next line if its not already
|
||||||
|
string text = InputField.text;
|
||||||
|
string sub = InputField.text.Substring(0, InputField.caretPosition);
|
||||||
|
int lastIndex = sub.LastIndexOf("{");
|
||||||
|
int offset = lastIndex - 1;
|
||||||
|
if (offset >= 0 && text[offset] != '\n' && text[offset] != '\t')
|
||||||
|
{
|
||||||
|
string open = "\n" + indentMinusOne;
|
||||||
|
|
||||||
|
InputField.text = text.Insert(offset + 1, open);
|
||||||
|
|
||||||
|
caretPos += open.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if should add auto-close }
|
||||||
|
int numOpen = InputField.text.Where(x => x == CSharpLexer.indentOpen).Count();
|
||||||
|
int numClose = InputField.text.Where(x => x == CSharpLexer.indentClose).Count();
|
||||||
|
|
||||||
|
if (numOpen > numClose)
|
||||||
|
{
|
||||||
|
// add auto-indent closing
|
||||||
|
indentMinusOne = $"\n{indentMinusOne}}}";
|
||||||
|
InputField.text = InputField.text.Insert(caretPos, indentMinusOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the actual auto indent now
|
||||||
|
InputField.text = InputField.text.Insert(caretPos, indent);
|
||||||
|
|
||||||
|
//InputField.stringPosition = caretPos + indent.Length;
|
||||||
|
InputField.caretPosition = caretPos + indent.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update line column and indent positions
|
||||||
|
UpdateIndent(InputField.text);
|
||||||
|
|
||||||
|
InputText.text = InputField.text;
|
||||||
|
//inputText.SetText(InputField.text, true);
|
||||||
|
InputText.Rebuild(CanvasUpdate.Prelayout);
|
||||||
|
InputField.ForceLabelUpdate();
|
||||||
|
InputField.Rebuild(CanvasUpdate.Prelayout);
|
||||||
|
|
||||||
|
OnInputChanged(InputText.text, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetAutoIndentTab(int amount)
|
||||||
|
{
|
||||||
|
string tab = string.Empty;
|
||||||
|
|
||||||
|
for (int i = 0; i < amount; i++)
|
||||||
|
{
|
||||||
|
tab += "\t";
|
||||||
|
}
|
||||||
|
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== UI CONSTRUCTION =========== //
|
||||||
|
|
||||||
|
public void ConstructUI()
|
||||||
|
{
|
||||||
|
CSConsolePage.Instance.Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport);
|
||||||
|
|
||||||
|
var mainLayout = CSConsolePage.Instance.Content.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.preferredHeight = 500;
|
||||||
|
mainLayout.flexibleHeight = 9000;
|
||||||
|
|
||||||
|
var mainGroup = CSConsolePage.Instance.Content.AddComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
#region TOP BAR
|
||||||
|
|
||||||
|
// Main group object
|
||||||
|
|
||||||
|
var topBarObj = UIFactory.CreateHorizontalGroup(CSConsolePage.Instance.Content);
|
||||||
|
LayoutElement topBarLayout = topBarObj.AddComponent<LayoutElement>();
|
||||||
|
topBarLayout.minHeight = 50;
|
||||||
|
topBarLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var topBarGroup = topBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
topBarGroup.padding.left = 30;
|
||||||
|
topBarGroup.padding.right = 30;
|
||||||
|
topBarGroup.padding.top = 8;
|
||||||
|
topBarGroup.padding.bottom = 8;
|
||||||
|
topBarGroup.spacing = 10;
|
||||||
|
topBarGroup.childForceExpandHeight = true;
|
||||||
|
topBarGroup.childForceExpandWidth = true;
|
||||||
|
topBarGroup.childControlWidth = true;
|
||||||
|
topBarGroup.childControlHeight = true;
|
||||||
|
topBarGroup.childAlignment = TextAnchor.LowerCenter;
|
||||||
|
|
||||||
|
var topBarLabel = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
|
||||||
|
var topBarLabelLayout = topBarLabel.AddComponent<LayoutElement>();
|
||||||
|
topBarLabelLayout.preferredWidth = 150;
|
||||||
|
topBarLabelLayout.flexibleWidth = 5000;
|
||||||
|
var topBarText = topBarLabel.GetComponent<Text>();
|
||||||
|
topBarText.text = "C# Console";
|
||||||
|
topBarText.fontSize = 20;
|
||||||
|
|
||||||
|
// Enable Ctrl+R toggle
|
||||||
|
|
||||||
|
var ctrlRToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle ctrlRToggle, out Text ctrlRToggleText);
|
||||||
|
ctrlRToggle.onValueChanged.AddListener(CtrlRToggleCallback);
|
||||||
|
void CtrlRToggleCallback(bool val)
|
||||||
|
{
|
||||||
|
EnableCtrlRShortcut = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlRToggleText.text = "Run on Ctrl+R";
|
||||||
|
ctrlRToggleText.alignment = TextAnchor.UpperLeft;
|
||||||
|
var ctrlRLayout = ctrlRToggleObj.AddComponent<LayoutElement>();
|
||||||
|
ctrlRLayout.minWidth = 140;
|
||||||
|
ctrlRLayout.flexibleWidth = 0;
|
||||||
|
ctrlRLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// Enable Suggestions toggle
|
||||||
|
|
||||||
|
var suggestToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle suggestToggle, out Text suggestToggleText);
|
||||||
|
suggestToggle.onValueChanged.AddListener(SuggestToggleCallback);
|
||||||
|
void SuggestToggleCallback(bool val)
|
||||||
|
{
|
||||||
|
EnableAutocompletes = val;
|
||||||
|
AutoCompleter.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestToggleText.text = "Suggestions";
|
||||||
|
suggestToggleText.alignment = TextAnchor.UpperLeft;
|
||||||
|
var suggestLayout = suggestToggleObj.AddComponent<LayoutElement>();
|
||||||
|
suggestLayout.minWidth = 120;
|
||||||
|
suggestLayout.flexibleWidth = 0;
|
||||||
|
suggestLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// Enable Auto-indent toggle
|
||||||
|
|
||||||
|
var autoIndentToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle autoIndentToggle, out Text autoIndentToggleText);
|
||||||
|
autoIndentToggle.onValueChanged.AddListener(OnIndentChanged);
|
||||||
|
void OnIndentChanged(bool val) => EnableAutoIndent = val;
|
||||||
|
|
||||||
|
autoIndentToggleText.text = "Auto-indent on Enter";
|
||||||
|
autoIndentToggleText.alignment = TextAnchor.UpperLeft;
|
||||||
|
|
||||||
|
var autoIndentLayout = autoIndentToggleObj.AddComponent<LayoutElement>();
|
||||||
|
autoIndentLayout.minWidth = 180;
|
||||||
|
autoIndentLayout.flexibleWidth = 0;
|
||||||
|
autoIndentLayout.minHeight = 25;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region CONSOLE INPUT
|
||||||
|
|
||||||
|
int fontSize = 16;
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateSrollInputField(CSConsolePage.Instance.Content, out InputFieldScroller consoleScroll, fontSize);
|
||||||
|
|
||||||
|
var inputField = consoleScroll.inputField;
|
||||||
|
|
||||||
|
var mainTextObj = inputField.textComponent.gameObject;
|
||||||
|
var mainTextInput = inputField.textComponent;
|
||||||
|
mainTextInput.supportRichText = false;
|
||||||
|
mainTextInput.color = new Color(1, 1, 1, 0.5f);
|
||||||
|
|
||||||
|
var placeHolderText = inputField.placeholder.GetComponent<Text>();
|
||||||
|
placeHolderText.text = STARTUP_TEXT;
|
||||||
|
placeHolderText.fontSize = fontSize;
|
||||||
|
|
||||||
|
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", mainTextObj.gameObject);
|
||||||
|
var highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
|
||||||
|
highlightTextRect.pivot = new Vector2(0, 1);
|
||||||
|
highlightTextRect.anchorMin = Vector2.zero;
|
||||||
|
highlightTextRect.anchorMax = Vector2.one;
|
||||||
|
highlightTextRect.offsetMin = new Vector2(20, 0);
|
||||||
|
highlightTextRect.offsetMax = new Vector2(14, 0);
|
||||||
|
|
||||||
|
var highlightTextInput = highlightTextObj.AddComponent<Text>();
|
||||||
|
highlightTextInput.supportRichText = true;
|
||||||
|
highlightTextInput.fontSize = fontSize;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region COMPILE BUTTON
|
||||||
|
|
||||||
|
var compileBtnObj = UIFactory.CreateButton(CSConsolePage.Instance.Content);
|
||||||
|
var compileBtnLayout = compileBtnObj.AddComponent<LayoutElement>();
|
||||||
|
compileBtnLayout.preferredWidth = 80;
|
||||||
|
compileBtnLayout.flexibleWidth = 0;
|
||||||
|
compileBtnLayout.minHeight = 45;
|
||||||
|
compileBtnLayout.flexibleHeight = 0;
|
||||||
|
var compileButton = compileBtnObj.GetComponent<Button>();
|
||||||
|
var compileBtnColors = compileButton.colors;
|
||||||
|
compileBtnColors.normalColor = new Color(14f / 255f, 80f / 255f, 14f / 255f);
|
||||||
|
compileButton.colors = compileBtnColors;
|
||||||
|
var btnText = compileBtnObj.GetComponentInChildren<Text>();
|
||||||
|
btnText.text = "Run";
|
||||||
|
btnText.fontSize = 18;
|
||||||
|
btnText.color = Color.white;
|
||||||
|
|
||||||
|
// Set compile button callback now that we have the Input Field reference
|
||||||
|
compileButton.onClick.AddListener(CompileCallback);
|
||||||
|
void CompileCallback()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(inputField.text))
|
||||||
|
{
|
||||||
|
CSConsolePage.Instance.Evaluate(inputField.text.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
//mainTextInput.supportRichText = false;
|
||||||
|
|
||||||
|
mainTextInput.font = UIManager.ConsoleFont;
|
||||||
|
placeHolderText.font = UIManager.ConsoleFont;
|
||||||
|
highlightTextInput.font = UIManager.ConsoleFont;
|
||||||
|
|
||||||
|
// reset this after formatting finalized
|
||||||
|
highlightTextRect.anchorMin = Vector2.zero;
|
||||||
|
highlightTextRect.anchorMax = Vector2.one;
|
||||||
|
highlightTextRect.offsetMin = Vector2.zero;
|
||||||
|
highlightTextRect.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
// assign references
|
||||||
|
|
||||||
|
this.InputField = inputField;
|
||||||
|
|
||||||
|
this.InputText = mainTextInput;
|
||||||
|
this.inputHighlightText = highlightTextInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/CSConsole/Lexer/CommentMatch.cs
Normal file
46
src/CSConsole/Lexer/CommentMatch.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole.Lexer
|
||||||
|
{
|
||||||
|
public class CommentMatch : Matcher
|
||||||
|
{
|
||||||
|
public string lineCommentStart = @"//";
|
||||||
|
public string blockCommentStart = @"/*";
|
||||||
|
public string blockCommentEnd = @"*/";
|
||||||
|
|
||||||
|
public override Color HighlightColor => new Color(0.34f, 0.65f, 0.29f, 1.0f);
|
||||||
|
public override IEnumerable<char> StartChars => new char[] { lineCommentStart[0], blockCommentStart[0] };
|
||||||
|
public override IEnumerable<char> EndChars => new char[] { blockCommentEnd[0] };
|
||||||
|
public override bool IsImplicitMatch(CSharpLexer lexer) => IsMatch(lexer, lineCommentStart) || IsMatch(lexer, blockCommentStart);
|
||||||
|
|
||||||
|
private bool IsMatch(CSharpLexer lexer, string commentType)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(commentType))
|
||||||
|
{
|
||||||
|
lexer.Rollback();
|
||||||
|
|
||||||
|
bool match = true;
|
||||||
|
for (int i = 0; i < commentType.Length; i++)
|
||||||
|
{
|
||||||
|
if (commentType[i] != lexer.ReadNext())
|
||||||
|
{
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
{
|
||||||
|
// Read until end of line or file
|
||||||
|
while (!IsEndLineOrEndFile(lexer, lexer.ReadNext())) { }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsEndLineOrEndFile(CSharpLexer lexer, char character) => lexer.EndOfStream || character == '\n' || character == '\r';
|
||||||
|
}
|
||||||
|
}
|
97
src/CSConsole/Lexer/KeywordMatch.cs
Normal file
97
src/CSConsole/Lexer/KeywordMatch.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole.Lexer
|
||||||
|
{
|
||||||
|
// I use two different KeywordMatch instances (valid and invalid).
|
||||||
|
// This class just contains common implementations.
|
||||||
|
public class KeywordMatch : Matcher
|
||||||
|
{
|
||||||
|
public string[] Keywords;
|
||||||
|
|
||||||
|
public override Color HighlightColor => highlightColor;
|
||||||
|
public Color highlightColor;
|
||||||
|
|
||||||
|
private readonly HashSet<string> shortlist = new HashSet<string>();
|
||||||
|
private readonly Stack<string> removeList = new Stack<string>();
|
||||||
|
|
||||||
|
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||||
|
{
|
||||||
|
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||||
|
!lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
shortlist.Clear();
|
||||||
|
|
||||||
|
int currentIndex = 0;
|
||||||
|
char currentChar = lexer.ReadNext();
|
||||||
|
|
||||||
|
for (int i = 0; i < Keywords.Length; i++)
|
||||||
|
{
|
||||||
|
if (Keywords[i][0] == currentChar)
|
||||||
|
{
|
||||||
|
shortlist.Add(Keywords[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortlist.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (lexer.EndOfStream)
|
||||||
|
{
|
||||||
|
RemoveLongStrings(currentIndex + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChar = lexer.ReadNext();
|
||||||
|
currentIndex++;
|
||||||
|
|
||||||
|
if (char.IsWhiteSpace(currentChar) ||
|
||||||
|
lexer.IsSpecialSymbol(currentChar, DelimiterType.Start))
|
||||||
|
{
|
||||||
|
RemoveLongStrings(currentIndex);
|
||||||
|
lexer.Rollback(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string keyword in shortlist)
|
||||||
|
{
|
||||||
|
if (currentIndex >= keyword.Length || keyword[currentIndex] != currentChar)
|
||||||
|
{
|
||||||
|
removeList.Push(keyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (removeList.Count > 0)
|
||||||
|
{
|
||||||
|
shortlist.Remove(removeList.Pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (shortlist.Count > 0);
|
||||||
|
|
||||||
|
return shortlist.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveLongStrings(int length)
|
||||||
|
{
|
||||||
|
foreach (string keyword in shortlist)
|
||||||
|
{
|
||||||
|
if (keyword.Length > length)
|
||||||
|
{
|
||||||
|
removeList.Push(keyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (removeList.Count > 0)
|
||||||
|
{
|
||||||
|
shortlist.Remove(removeList.Pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/CSConsole/Lexer/Matcher.cs
Normal file
32
src/CSConsole/Lexer/Matcher.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole.Lexer
|
||||||
|
{
|
||||||
|
public abstract class Matcher
|
||||||
|
{
|
||||||
|
public abstract Color HighlightColor { get; }
|
||||||
|
|
||||||
|
public string HexColor => htmlColor ?? (htmlColor = "<color=#" + HighlightColor.ToHex() + ">");
|
||||||
|
private string htmlColor;
|
||||||
|
|
||||||
|
public virtual IEnumerable<char> StartChars => Enumerable.Empty<char>();
|
||||||
|
public virtual IEnumerable<char> EndChars => Enumerable.Empty<char>();
|
||||||
|
|
||||||
|
public abstract bool IsImplicitMatch(CSharpLexer lexer);
|
||||||
|
|
||||||
|
public bool IsMatch(CSharpLexer lexer)
|
||||||
|
{
|
||||||
|
if (IsImplicitMatch(lexer))
|
||||||
|
{
|
||||||
|
lexer.Commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lexer.Rollback();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
src/CSConsole/Lexer/NumberMatch.cs
Normal file
39
src/CSConsole/Lexer/NumberMatch.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole.Lexer
|
||||||
|
{
|
||||||
|
public class NumberMatch : Matcher
|
||||||
|
{
|
||||||
|
public override Color HighlightColor => new Color(0.58f, 0.33f, 0.33f, 1.0f);
|
||||||
|
|
||||||
|
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||||
|
{
|
||||||
|
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||||
|
!lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matchedNumber = false;
|
||||||
|
|
||||||
|
while (!lexer.EndOfStream)
|
||||||
|
{
|
||||||
|
if (IsNumberOrDecimalPoint(lexer.ReadNext()))
|
||||||
|
{
|
||||||
|
matchedNumber = true;
|
||||||
|
lexer.Commit();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lexer.Rollback();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNumberOrDecimalPoint(char character) => char.IsNumber(character) || character == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/CSConsole/Lexer/StringMatch.cs
Normal file
26
src/CSConsole/Lexer/StringMatch.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole.Lexer
|
||||||
|
{
|
||||||
|
public class StringMatch : Matcher
|
||||||
|
{
|
||||||
|
public override Color HighlightColor => new Color(0.79f, 0.52f, 0.32f, 1.0f);
|
||||||
|
|
||||||
|
public override IEnumerable<char> StartChars => new[] { '"' };
|
||||||
|
public override IEnumerable<char> EndChars => new[] { '"' };
|
||||||
|
|
||||||
|
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||||
|
{
|
||||||
|
if (lexer.ReadNext() == '"')
|
||||||
|
{
|
||||||
|
while (!IsClosingQuoteOrEndFile(lexer, lexer.ReadNext())) { }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsClosingQuoteOrEndFile(CSharpLexer lexer, char character) => lexer.EndOfStream || character == '"';
|
||||||
|
}
|
||||||
|
}
|
106
src/CSConsole/Lexer/SymbolMatch.cs
Normal file
106
src/CSConsole/Lexer/SymbolMatch.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole.Lexer
|
||||||
|
{
|
||||||
|
public class SymbolMatch : Matcher
|
||||||
|
{
|
||||||
|
public override Color HighlightColor => new Color(0.58f, 0.47f, 0.37f, 1.0f);
|
||||||
|
|
||||||
|
private readonly string[] symbols = new[]
|
||||||
|
{
|
||||||
|
"[", "]", "(", ")", ".", "?", ":", "+", "-", "*", "/", "%", "&", "|", "^", "~", "=", "<", ">",
|
||||||
|
"++", "--", "&&", "||", "<<", ">>", "==", "!=", "<=", ">=", "+=", "-=", "*=", "/=", "%=", "&=",
|
||||||
|
"|=", "^=", "<<=", ">>=", "->", "??", "=>",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly List<string> shortlist = new List<string>();
|
||||||
|
private static readonly Stack<string> removeList = new Stack<string>();
|
||||||
|
|
||||||
|
public override IEnumerable<char> StartChars => symbols.Select(s => s[0]);
|
||||||
|
public override IEnumerable<char> EndChars => symbols.Select(s => s[0]);
|
||||||
|
|
||||||
|
public override bool IsImplicitMatch(CSharpLexer lexer)
|
||||||
|
{
|
||||||
|
if (lexer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||||
|
!char.IsLetter(lexer.Previous) &&
|
||||||
|
!char.IsDigit(lexer.Previous) &&
|
||||||
|
!lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
shortlist.Clear();
|
||||||
|
|
||||||
|
int currentIndex = 0;
|
||||||
|
char currentChar = lexer.ReadNext();
|
||||||
|
|
||||||
|
for (int i = symbols.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (symbols[i][0] == currentChar)
|
||||||
|
shortlist.Add(symbols[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortlist.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (lexer.EndOfStream)
|
||||||
|
{
|
||||||
|
RemoveLongStrings(currentIndex + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChar = lexer.ReadNext();
|
||||||
|
currentIndex++;
|
||||||
|
|
||||||
|
if (char.IsWhiteSpace(currentChar) ||
|
||||||
|
char.IsLetter(currentChar) ||
|
||||||
|
char.IsDigit(currentChar) ||
|
||||||
|
lexer.IsSpecialSymbol(currentChar, DelimiterType.Start))
|
||||||
|
{
|
||||||
|
RemoveLongStrings(currentIndex);
|
||||||
|
lexer.Rollback(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string symbol in shortlist)
|
||||||
|
{
|
||||||
|
if (currentIndex >= symbol.Length || symbol[currentIndex] != currentChar)
|
||||||
|
{
|
||||||
|
removeList.Push(symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (removeList.Count > 0)
|
||||||
|
{
|
||||||
|
shortlist.Remove(removeList.Pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (shortlist.Count > 0);
|
||||||
|
|
||||||
|
return shortlist.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveLongStrings(int length)
|
||||||
|
{
|
||||||
|
foreach (string keyword in shortlist)
|
||||||
|
{
|
||||||
|
if (keyword.Length > length)
|
||||||
|
{
|
||||||
|
removeList.Push(keyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (removeList.Count > 0)
|
||||||
|
{
|
||||||
|
shortlist.Remove(removeList.Pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
src/CSConsole/ScriptEvaluator.cs
Normal file
76
src/CSConsole/ScriptEvaluator.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Mono.CSharp;
|
||||||
|
|
||||||
|
// Thanks to ManlyMarco for this
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole
|
||||||
|
{
|
||||||
|
public class ScriptEvaluator : Evaluator, IDisposable
|
||||||
|
{
|
||||||
|
private static readonly HashSet<string> StdLib = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||||
|
{
|
||||||
|
"mscorlib", "System.Core", "System", "System.Xml"
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly TextWriter tw;
|
||||||
|
|
||||||
|
public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
|
||||||
|
{
|
||||||
|
this.tw = tw;
|
||||||
|
|
||||||
|
ImportAppdomainAssemblies(ReferenceAssembly);
|
||||||
|
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
||||||
|
tw.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
||||||
|
{
|
||||||
|
string name = args.LoadedAssembly.GetName().Name;
|
||||||
|
if (StdLib.Contains(name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReferenceAssembly(args.LoadedAssembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompilerContext BuildContext(TextWriter tw)
|
||||||
|
{
|
||||||
|
var reporter = new StreamReportPrinter(tw);
|
||||||
|
|
||||||
|
var settings = new CompilerSettings
|
||||||
|
{
|
||||||
|
Version = LanguageVersion.Experimental,
|
||||||
|
GenerateDebugInfo = false,
|
||||||
|
StdLib = true,
|
||||||
|
Target = Target.Library,
|
||||||
|
WarningLevel = 0,
|
||||||
|
EnhancedWarnings = false
|
||||||
|
};
|
||||||
|
|
||||||
|
return new CompilerContext(settings, reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
||||||
|
{
|
||||||
|
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
string name = assembly.GetName().Name;
|
||||||
|
if (StdLib.Contains(name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
import(assembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
src/CSConsole/ScriptInteraction.cs
Normal file
57
src/CSConsole/ScriptInteraction.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using Mono.CSharp;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using UnityExplorer.Inspectors;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole
|
||||||
|
{
|
||||||
|
public class ScriptInteraction : InteractiveBase
|
||||||
|
{
|
||||||
|
public static void Log(object message)
|
||||||
|
{
|
||||||
|
ExplorerCore.Log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddUsing(string directive)
|
||||||
|
{
|
||||||
|
CSConsolePage.Instance.AddUsing(directive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetUsing()
|
||||||
|
{
|
||||||
|
ExplorerCore.Log(CSConsolePage.Instance.m_evaluator.GetUsing());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Reset()
|
||||||
|
{
|
||||||
|
CSConsolePage.Instance.ResetConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object CurrentTarget()
|
||||||
|
{
|
||||||
|
return InspectorManager.Instance?.m_activeInspector?.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[] AllTargets()
|
||||||
|
{
|
||||||
|
int count = InspectorManager.Instance?.m_currentInspectors.Count ?? 0;
|
||||||
|
object[] ret = new object[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ret[i] = InspectorManager.Instance?.m_currentInspectors[i].Target;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Inspect(object obj)
|
||||||
|
{
|
||||||
|
InspectorManager.Instance.Inspect(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Inspect(Type type)
|
||||||
|
{
|
||||||
|
InspectorManager.Instance.Inspect(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
src/CSConsole/Suggestion.cs
Normal file
69
src/CSConsole/Suggestion.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.CSConsole
|
||||||
|
{
|
||||||
|
public struct Suggestion
|
||||||
|
{
|
||||||
|
public enum Contexts
|
||||||
|
{
|
||||||
|
Namespace,
|
||||||
|
Keyword,
|
||||||
|
Other
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~ Instance ~~~~
|
||||||
|
|
||||||
|
public readonly string Prefix;
|
||||||
|
public readonly string Addition;
|
||||||
|
public readonly Contexts Context;
|
||||||
|
|
||||||
|
public string Full => Prefix + Addition;
|
||||||
|
|
||||||
|
public Color TextColor => GetTextColor();
|
||||||
|
|
||||||
|
public Suggestion(string addition, string prefix, Contexts type)
|
||||||
|
{
|
||||||
|
Addition = addition;
|
||||||
|
Prefix = prefix;
|
||||||
|
Context = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color GetTextColor()
|
||||||
|
{
|
||||||
|
switch (Context)
|
||||||
|
{
|
||||||
|
case Contexts.Namespace: return Color.grey;
|
||||||
|
case Contexts.Keyword: return keywordColor;
|
||||||
|
default: return Color.white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~ Static ~~~~
|
||||||
|
|
||||||
|
public static HashSet<string> Namespaces => m_namspaces ?? GetNamespaces();
|
||||||
|
private static HashSet<string> m_namspaces;
|
||||||
|
|
||||||
|
public static HashSet<string> Keywords => m_keywords ?? (m_keywords = new HashSet<string>(CSharpLexer.validKeywordMatcher.Keywords));
|
||||||
|
private static HashSet<string> m_keywords;
|
||||||
|
|
||||||
|
private static readonly Color keywordColor = new Color(80f / 255f, 150f / 255f, 215f / 255f);
|
||||||
|
|
||||||
|
private static HashSet<string> GetNamespaces()
|
||||||
|
{
|
||||||
|
HashSet<string> set = new HashSet<string>(
|
||||||
|
AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(GetTypes)
|
||||||
|
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
|
||||||
|
.Select(x => x.Namespace));
|
||||||
|
|
||||||
|
return m_namspaces = set;
|
||||||
|
|
||||||
|
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
src/Config/ModConfig.cs
Normal file
68
src/Config/ModConfig.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Config
|
||||||
|
{
|
||||||
|
public class ModConfig
|
||||||
|
{
|
||||||
|
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig));
|
||||||
|
|
||||||
|
//[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
|
||||||
|
[XmlIgnore] private const string SETTINGS_PATH = ExplorerCore.EXPLORER_FOLDER + @"\config.xml";
|
||||||
|
|
||||||
|
[XmlIgnore] public static ModConfig Instance;
|
||||||
|
|
||||||
|
// Actual configs
|
||||||
|
public KeyCode Main_Menu_Toggle = KeyCode.F7;
|
||||||
|
public bool Force_Unlock_Mouse = true;
|
||||||
|
public int Default_Page_Limit = 25;
|
||||||
|
public string Default_Output_Path = ExplorerCore.EXPLORER_FOLDER;
|
||||||
|
public bool Log_Unity_Debug = false;
|
||||||
|
public bool Save_Logs_To_Disk = true;
|
||||||
|
|
||||||
|
public static event Action OnConfigChanged;
|
||||||
|
|
||||||
|
internal static void InvokeConfigChanged()
|
||||||
|
{
|
||||||
|
OnConfigChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnLoad()
|
||||||
|
{
|
||||||
|
if (LoadSettings())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Instance = new ModConfig();
|
||||||
|
SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool LoadSettings()
|
||||||
|
{
|
||||||
|
if (!File.Exists(SETTINGS_PATH))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var file = File.OpenRead(SETTINGS_PATH))
|
||||||
|
Instance = (ModConfig)Serializer.Deserialize(file);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Instance != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveSettings()
|
||||||
|
{
|
||||||
|
if (File.Exists(SETTINGS_PATH))
|
||||||
|
File.Delete(SETTINGS_PATH);
|
||||||
|
|
||||||
|
using (var file = File.Create(SETTINGS_PATH))
|
||||||
|
Serializer.Serialize(file, Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,214 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class CppExplorer : MelonMod
|
|
||||||
{
|
|
||||||
// consts
|
|
||||||
|
|
||||||
public const string ID = "com.sinai.cppexplorer";
|
|
||||||
public const string NAME = "IL2CPP Runtime Explorer";
|
|
||||||
public const string VERSION = "1.1.0";
|
|
||||||
public const string AUTHOR = "Sinai";
|
|
||||||
|
|
||||||
// fields
|
|
||||||
|
|
||||||
public static CppExplorer Instance;
|
|
||||||
private string m_objUnderMouseName = "";
|
|
||||||
private Camera m_main;
|
|
||||||
|
|
||||||
// props
|
|
||||||
|
|
||||||
public static bool ShowMenu { get; set; } = false;
|
|
||||||
public static int ArrayLimit { get; set; } = 100;
|
|
||||||
public bool MouseInspect { get; set; } = false;
|
|
||||||
|
|
||||||
public static string ActiveSceneName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Camera MainCamera
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (m_main == null)
|
|
||||||
{
|
|
||||||
m_main = Camera.main;
|
|
||||||
}
|
|
||||||
return m_main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods
|
|
||||||
|
|
||||||
public override void OnApplicationStart()
|
|
||||||
{
|
|
||||||
base.OnApplicationStart();
|
|
||||||
|
|
||||||
Instance = this;
|
|
||||||
|
|
||||||
new MainMenu();
|
|
||||||
new WindowManager();
|
|
||||||
|
|
||||||
//var harmony = HarmonyInstance.Create(ID);
|
|
||||||
//harmony.PatchAll();
|
|
||||||
|
|
||||||
// done init
|
|
||||||
ShowMenu = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnLevelWasLoaded(int level)
|
|
||||||
{
|
|
||||||
if (ScenePage.Instance != null)
|
|
||||||
{
|
|
||||||
ScenePage.Instance.OnSceneChange();
|
|
||||||
SearchPage.Instance.OnSceneChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnUpdate()
|
|
||||||
{
|
|
||||||
if (Input.GetKeyDown(KeyCode.F7))
|
|
||||||
{
|
|
||||||
ShowMenu = !ShowMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShowMenu)
|
|
||||||
{
|
|
||||||
MainMenu.Instance.Update();
|
|
||||||
WindowManager.Instance.Update();
|
|
||||||
|
|
||||||
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
|
|
||||||
{
|
|
||||||
MouseInspect = !MouseInspect;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MouseInspect)
|
|
||||||
{
|
|
||||||
InspectUnderMouse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (MouseInspect)
|
|
||||||
{
|
|
||||||
MouseInspect = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InspectUnderMouse()
|
|
||||||
{
|
|
||||||
Ray ray = MainCamera.ScreenPointToRay(Input.mousePosition);
|
|
||||||
|
|
||||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
|
||||||
{
|
|
||||||
var obj = hit.transform.gameObject;
|
|
||||||
|
|
||||||
m_objUnderMouseName = GetGameObjectPath(obj.transform);
|
|
||||||
|
|
||||||
if (Input.GetMouseButtonDown(0))
|
|
||||||
{
|
|
||||||
MouseInspect = false;
|
|
||||||
m_objUnderMouseName = "";
|
|
||||||
|
|
||||||
WindowManager.InspectObject(obj, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_objUnderMouseName = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnGUI()
|
|
||||||
{
|
|
||||||
base.OnGUI();
|
|
||||||
|
|
||||||
MainMenu.Instance.OnGUI();
|
|
||||||
WindowManager.Instance.OnGUI();
|
|
||||||
|
|
||||||
if (MouseInspect)
|
|
||||||
{
|
|
||||||
if (m_objUnderMouseName != "")
|
|
||||||
{
|
|
||||||
var pos = Input.mousePosition;
|
|
||||||
var rect = new Rect(
|
|
||||||
pos.x - (Screen.width / 2), // x
|
|
||||||
Screen.height - pos.y - 50, // y
|
|
||||||
Screen.width, // w
|
|
||||||
50 // h
|
|
||||||
);
|
|
||||||
|
|
||||||
var origAlign = GUI.skin.label.alignment;
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
|
||||||
|
|
||||||
//shadow text
|
|
||||||
GUI.Label(rect, $"<color=black>{m_objUnderMouseName}</color>");
|
|
||||||
//white text
|
|
||||||
GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName);
|
|
||||||
|
|
||||||
GUI.skin.label.alignment = origAlign;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************** public helpers **************
|
|
||||||
|
|
||||||
public static object Il2CppCast(object obj, Type castTo)
|
|
||||||
{
|
|
||||||
var method = typeof(Il2CppObjectBase).GetMethod("TryCast");
|
|
||||||
var generic = method.MakeGenericMethod(castTo);
|
|
||||||
return generic.Invoke(obj, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetGameObjectPath(Transform _transform)
|
|
||||||
{
|
|
||||||
return GetGameObjectPath(_transform, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetGameObjectPath(Transform _transform, bool _includeItemName)
|
|
||||||
{
|
|
||||||
string text = _includeItemName ? ("/" + _transform.name) : "";
|
|
||||||
GameObject gameObject = _transform.gameObject;
|
|
||||||
while (gameObject.transform.parent != null)
|
|
||||||
{
|
|
||||||
gameObject = gameObject.transform.parent.gameObject;
|
|
||||||
text = "/" + gameObject.name + text;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type GetType(string _type)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (asm.GetType(_type) is Type type)
|
|
||||||
{
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
|
||||||
<RootNamespace>CppExplorer</RootNamespace>
|
|
||||||
<AssemblyName>CppExplorer</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<Deterministic>true</Deterministic>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>false</DebugSymbols>
|
|
||||||
<DebugType>none</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>..\Release\</OutputPath>
|
|
||||||
<DefineConstants>
|
|
||||||
</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Il2Cppmscorlib">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Il2CppSystem.Core">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="mcs">
|
|
||||||
<HintPath>..\lib\mcs.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="MelonLoader.ModHandler">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="UnhollowerBaseLib">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnhollowerRuntimeLib">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Unity.TextMeshPro">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Unity.TextMeshPro.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.CoreModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.IMGUIModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.InputLegacyModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.InputModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.ParticleSystemModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.ParticleSystemModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.Physics2DModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.Physics2DModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.PhysicsModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.TextRenderingModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.UI">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.UIElementsModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.UIModule">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="ILBehaviour.cs" />
|
|
||||||
<Compile Include="CppExplorer.cs" />
|
|
||||||
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
|
||||||
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
|
||||||
<Compile Include="MainMenu\Pages\Console\REPLHelper.cs" />
|
|
||||||
<Compile Include="WindowManager.cs" />
|
|
||||||
<Compile Include="MainMenu\MainMenu.cs" />
|
|
||||||
<Compile Include="Inspectors\GameObjectWindow.cs" />
|
|
||||||
<Compile Include="Inspectors\ReflectionWindow.cs" />
|
|
||||||
<Compile Include="MainMenu\Pages\ScenePage.cs" />
|
|
||||||
<Compile Include="MainMenu\Pages\SearchPage.cs" />
|
|
||||||
<Compile Include="UIStyles.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="utils\AccessTools.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 16
|
|
||||||
VisualStudioVersion = 16.0.30128.74
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppExplorer", "CppExplorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {DD5C0A5D-03F1-4CC3-8B4D-E10834908C5A}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
92
src/ExplorerBepInPlugin.cs
Normal file
92
src/ExplorerBepInPlugin.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#if BIE
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using BepInEx;
|
||||||
|
using BepInEx.Logging;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
#if CPP
|
||||||
|
using UnhollowerRuntimeLib;
|
||||||
|
using BepInEx.IL2CPP;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer
|
||||||
|
{
|
||||||
|
#if MONO
|
||||||
|
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
|
||||||
|
public class ExplorerBepInPlugin : BaseUnityPlugin
|
||||||
|
{
|
||||||
|
public static ExplorerBepInPlugin Instance;
|
||||||
|
|
||||||
|
public static ManualLogSource Logging => Instance?.Logger;
|
||||||
|
|
||||||
|
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
|
||||||
|
|
||||||
|
internal void Awake()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
new ExplorerCore();
|
||||||
|
|
||||||
|
// HarmonyInstance.PatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update()
|
||||||
|
{
|
||||||
|
ExplorerCore.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
|
||||||
|
public class ExplorerBepInPlugin : BasePlugin
|
||||||
|
{
|
||||||
|
public static ExplorerBepInPlugin Instance;
|
||||||
|
|
||||||
|
public static ManualLogSource Logging => Instance?.Log;
|
||||||
|
|
||||||
|
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
|
||||||
|
|
||||||
|
// Init
|
||||||
|
public override void Load()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
|
||||||
|
|
||||||
|
var obj = new GameObject(
|
||||||
|
"ExplorerBehaviour",
|
||||||
|
new Il2CppSystem.Type[] { Il2CppType.Of<ExplorerBehaviour>() }
|
||||||
|
);
|
||||||
|
obj.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
GameObject.DontDestroyOnLoad(obj);
|
||||||
|
|
||||||
|
new ExplorerCore();
|
||||||
|
|
||||||
|
// HarmonyInstance.PatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.
|
||||||
|
public class ExplorerBehaviour : MonoBehaviour
|
||||||
|
{
|
||||||
|
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
|
||||||
|
|
||||||
|
internal void Awake()
|
||||||
|
{
|
||||||
|
Logging.LogMessage("ExplorerBehaviour.Awake");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update()
|
||||||
|
{
|
||||||
|
ExplorerCore.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
215
src/ExplorerCore.cs
Normal file
215
src/ExplorerCore.cs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
using System;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Inspectors;
|
||||||
|
using System.IO;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer
|
||||||
|
{
|
||||||
|
public class ExplorerCore
|
||||||
|
{
|
||||||
|
public const string NAME = "UnityExplorer";
|
||||||
|
public const string VERSION = "3.0.2";
|
||||||
|
public const string AUTHOR = "Sinai";
|
||||||
|
public const string GUID = "com.sinai.unityexplorer";
|
||||||
|
public const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
|
||||||
|
|
||||||
|
public static ExplorerCore Instance { get; private set; }
|
||||||
|
|
||||||
|
public static bool ShowMenu
|
||||||
|
{
|
||||||
|
get => s_showMenu;
|
||||||
|
set => SetShowMenu(value);
|
||||||
|
}
|
||||||
|
public static bool s_showMenu;
|
||||||
|
|
||||||
|
private static bool s_doneUIInit;
|
||||||
|
private static float s_timeSinceStartup;
|
||||||
|
|
||||||
|
public ExplorerCore()
|
||||||
|
{
|
||||||
|
if (Instance != null)
|
||||||
|
{
|
||||||
|
Log("An instance of Explorer is already active!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
ReflectionHelpers.TryLoadGameModules();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!Directory.Exists(EXPLORER_FOLDER))
|
||||||
|
Directory.CreateDirectory(EXPLORER_FOLDER);
|
||||||
|
|
||||||
|
ModConfig.OnLoad();
|
||||||
|
|
||||||
|
InputManager.Init();
|
||||||
|
ForceUnlockCursor.Init();
|
||||||
|
|
||||||
|
SetupEvents();
|
||||||
|
|
||||||
|
ShowMenu = true;
|
||||||
|
|
||||||
|
Log($"{NAME} {VERSION} initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Update()
|
||||||
|
{
|
||||||
|
if (!s_doneUIInit)
|
||||||
|
CheckUIInit();
|
||||||
|
|
||||||
|
if (MouseInspector.Enabled)
|
||||||
|
MouseInspector.UpdateInspect();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
|
||||||
|
ShowMenu = !ShowMenu;
|
||||||
|
|
||||||
|
if (ShowMenu && s_doneUIInit)
|
||||||
|
UIManager.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckUIInit()
|
||||||
|
{
|
||||||
|
s_timeSinceStartup += Time.deltaTime;
|
||||||
|
|
||||||
|
if (s_timeSinceStartup > 0.1f)
|
||||||
|
{
|
||||||
|
s_doneUIInit = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UIManager.Init();
|
||||||
|
Log("Initialized UnityExplorer UI.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogWarning($"Exception setting up UI: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupEvents()
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Application.add_logMessageReceived(new Action<string, string, LogType>(OnUnityLog));
|
||||||
|
SceneManager.add_sceneLoaded(new Action<Scene, LoadSceneMode>((Scene a, LoadSceneMode b) => { OnSceneLoaded(); }));
|
||||||
|
SceneManager.add_activeSceneChanged(new Action<Scene, Scene>((Scene a, Scene b) => { OnSceneLoaded(); }));
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
#else
|
||||||
|
Application.logMessageReceived += OnUnityLog;
|
||||||
|
SceneManager.sceneLoaded += (Scene a, LoadSceneMode b) => { OnSceneLoaded(); };
|
||||||
|
SceneManager.activeSceneChanged += (Scene a, Scene b) => { OnSceneLoaded(); };
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnSceneLoaded()
|
||||||
|
{
|
||||||
|
UIManager.OnSceneChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetShowMenu(bool show)
|
||||||
|
{
|
||||||
|
s_showMenu = show;
|
||||||
|
|
||||||
|
if (UIManager.CanvasRoot)
|
||||||
|
{
|
||||||
|
UIManager.CanvasRoot.SetActive(show);
|
||||||
|
|
||||||
|
if (show)
|
||||||
|
ForceUnlockCursor.SetEventSystem();
|
||||||
|
else
|
||||||
|
ForceUnlockCursor.ReleaseEventSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
ForceUnlockCursor.UpdateCursorControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnityLog(string message, string stackTrace, LogType type)
|
||||||
|
{
|
||||||
|
if (!DebugConsole.LogUnity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
message = $"[UNITY] {message}";
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case LogType.Assert:
|
||||||
|
case LogType.Log:
|
||||||
|
Log(message, true);
|
||||||
|
break;
|
||||||
|
case LogType.Warning:
|
||||||
|
LogWarning(message, true);
|
||||||
|
break;
|
||||||
|
case LogType.Exception:
|
||||||
|
case LogType.Error:
|
||||||
|
LogError(message, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(object message, bool unity = false)
|
||||||
|
{
|
||||||
|
DebugConsole.Log(message?.ToString());
|
||||||
|
|
||||||
|
if (unity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if ML
|
||||||
|
MelonLoader.MelonLogger.Log(message?.ToString());
|
||||||
|
#else
|
||||||
|
ExplorerBepInPlugin.Logging?.LogMessage(message?.ToString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogWarning(object message, bool unity = false)
|
||||||
|
{
|
||||||
|
DebugConsole.Log(message?.ToString(), "FFFF00");
|
||||||
|
|
||||||
|
if (unity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if ML
|
||||||
|
MelonLoader.MelonLogger.LogWarning(message?.ToString());
|
||||||
|
#else
|
||||||
|
ExplorerBepInPlugin.Logging?.LogWarning(message?.ToString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogError(object message, bool unity = false)
|
||||||
|
{
|
||||||
|
DebugConsole.Log(message?.ToString(), "FF0000");
|
||||||
|
|
||||||
|
if (unity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if ML
|
||||||
|
MelonLoader.MelonLogger.LogError(message?.ToString());
|
||||||
|
#else
|
||||||
|
ExplorerBepInPlugin.Logging?.LogError(message?.ToString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static string RemoveInvalidFilenameChars(string s)
|
||||||
|
{
|
||||||
|
var invalid = System.IO.Path.GetInvalidFileNameChars();
|
||||||
|
foreach (var c in invalid)
|
||||||
|
{
|
||||||
|
s = s.Replace(c.ToString(), "");
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/ExplorerMelonMod.cs
Normal file
29
src/ExplorerMelonMod.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#if ML
|
||||||
|
using System;
|
||||||
|
using MelonLoader;
|
||||||
|
|
||||||
|
namespace UnityExplorer
|
||||||
|
{
|
||||||
|
public class ExplorerMelonMod : MelonMod
|
||||||
|
{
|
||||||
|
public static ExplorerMelonMod Instance;
|
||||||
|
|
||||||
|
public override void OnApplicationStart()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
new ExplorerCore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
ExplorerCore.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnLevelWasLoaded(int level)
|
||||||
|
{
|
||||||
|
ExplorerCore.Instance.OnSceneLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
33
src/Helpers/EventHelper.cs
Normal file
33
src/Helpers/EventHelper.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#if CPP
|
||||||
|
using System;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Helpers
|
||||||
|
{
|
||||||
|
// Possibly temporary, just so Il2Cpp can do the same style "AddListener" as Mono.
|
||||||
|
// Just saves me having a preprocessor directive for every single AddListener.
|
||||||
|
|
||||||
|
public static class EventHelper
|
||||||
|
{
|
||||||
|
public static void AddListener(this UnityEvent action, Action listener)
|
||||||
|
{
|
||||||
|
action.AddListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
|
||||||
|
{
|
||||||
|
action.AddListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddListener<T0, T1>(this UnityEvent<T0, T1> action, Action<T0, T1> listener)
|
||||||
|
{
|
||||||
|
action.AddListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddListener<T0, T1, T2>(this UnityEvent<T0, T1, T2> action, Action<T0, T1, T2> listener)
|
||||||
|
{
|
||||||
|
action.AddListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
36
src/Helpers/ICallHelper.cs
Normal file
36
src/Helpers/ICallHelper.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#if CPP
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Helpers
|
||||||
|
{
|
||||||
|
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
|
||||||
|
public static class ICallHelper
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, Delegate> iCallCache = new Dictionary<string, Delegate>();
|
||||||
|
|
||||||
|
public static T GetICall<T>(string iCallName) where T : Delegate
|
||||||
|
{
|
||||||
|
if (iCallCache.ContainsKey(iCallName))
|
||||||
|
return (T)iCallCache[iCallName];
|
||||||
|
|
||||||
|
IntPtr ptr = il2cpp_resolve_icall(iCallName);
|
||||||
|
|
||||||
|
if (ptr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new MissingMethodException($"Could not resolve internal call by name '{iCallName}'!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
|
||||||
|
iCallCache.Add(iCallName, iCall);
|
||||||
|
|
||||||
|
return (T)iCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
|
public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
276
src/Helpers/ReflectionHelpers.cs
Normal file
276
src/Helpers/ReflectionHelpers.cs
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using BF = System.Reflection.BindingFlags;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
#if CPP
|
||||||
|
using CppType = Il2CppSystem.Type;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
using UnhollowerRuntimeLib;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.Helpers
|
||||||
|
{
|
||||||
|
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
|
||||||
|
public static class ReflectionHelpers
|
||||||
|
{
|
||||||
|
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
||||||
|
|
||||||
|
public static Type GetTypeByName(string fullName)
|
||||||
|
{
|
||||||
|
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (var type in asm.TryGetTypes())
|
||||||
|
{
|
||||||
|
if (type.FullName == fullName)
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(GetActualType(obj));
|
||||||
|
|
||||||
|
public static Type[] GetAllBaseTypes(Type type)
|
||||||
|
{
|
||||||
|
List<Type> list = new List<Type>();
|
||||||
|
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
list.Add(type);
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type GetActualType(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var type = obj.GetType();
|
||||||
|
#if CPP
|
||||||
|
if (obj is Il2CppSystem.Object ilObject)
|
||||||
|
{
|
||||||
|
if (ilObject is CppType)
|
||||||
|
return typeof(CppType);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(type.Namespace))
|
||||||
|
{
|
||||||
|
// Il2CppSystem-namespace objects should just return GetType,
|
||||||
|
// because using GetIl2CppType returns the System namespace type instead.
|
||||||
|
if (type.Namespace.StartsWith("System.") || type.Namespace.StartsWith("Il2CppSystem."))
|
||||||
|
return ilObject.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
var il2cppType = ilObject.GetIl2CppType();
|
||||||
|
|
||||||
|
// check if type is injected
|
||||||
|
IntPtr classPtr = il2cpp_object_get_class(ilObject.Pointer);
|
||||||
|
if (RuntimeSpecificsStore.IsInjected(classPtr))
|
||||||
|
{
|
||||||
|
var typeByName = GetTypeByName(il2cppType.FullName);
|
||||||
|
if (typeByName != null)
|
||||||
|
return typeByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should be fine for all other il2cpp objects
|
||||||
|
var getType = GetMonoType(il2cppType);
|
||||||
|
if (getType != null)
|
||||||
|
return getType;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
private static readonly Dictionary<CppType, Type> Il2CppToMonoType = new Dictionary<CppType, Type>();
|
||||||
|
|
||||||
|
public static Type GetMonoType(CppType cppType)
|
||||||
|
{
|
||||||
|
if (Il2CppToMonoType.ContainsKey(cppType))
|
||||||
|
return Il2CppToMonoType[cppType];
|
||||||
|
|
||||||
|
var getType = Type.GetType(cppType.AssemblyQualifiedName);
|
||||||
|
Il2CppToMonoType.Add(cppType, getType);
|
||||||
|
return getType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, IntPtr> CppClassPointers = new Dictionary<Type, IntPtr>();
|
||||||
|
|
||||||
|
public static object Il2CppCast(this object obj, Type castTo)
|
||||||
|
{
|
||||||
|
if (!(obj is Il2CppSystem.Object ilObj))
|
||||||
|
return obj;
|
||||||
|
|
||||||
|
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
|
||||||
|
return obj;
|
||||||
|
|
||||||
|
IntPtr castFromPtr = il2cpp_object_get_class(ilObj.Pointer);
|
||||||
|
|
||||||
|
if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
|
||||||
|
return obj;
|
||||||
|
|
||||||
|
if (RuntimeSpecificsStore.IsInjected(castToPtr))
|
||||||
|
return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ilObj.Pointer);
|
||||||
|
|
||||||
|
return Activator.CreateInstance(castTo, ilObj.Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
|
||||||
|
|
||||||
|
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
|
||||||
|
{
|
||||||
|
if (!CppClassPointers.ContainsKey(type))
|
||||||
|
{
|
||||||
|
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
|
||||||
|
.MakeGenericType(new Type[] { type })
|
||||||
|
.GetField("NativeClassPtr", BF.Public | BF.Static)
|
||||||
|
.GetValue(null);
|
||||||
|
|
||||||
|
CppClassPointers.Add(type, il2cppPtr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
il2cppPtr = CppClassPointers[type];
|
||||||
|
|
||||||
|
return il2cppPtr != IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
|
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
|
||||||
|
|
||||||
|
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
|
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return asm.GetTypes();
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return asm.GetExportedTypes();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return e.Types.Where(t => t != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Type>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
internal static void TryLoadGameModules()
|
||||||
|
{
|
||||||
|
LoadModule("Assembly-CSharp");
|
||||||
|
LoadModule("Assembly-CSharp-firstpass");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool LoadModule(string module)
|
||||||
|
{
|
||||||
|
#if ML
|
||||||
|
var path = $@"MelonLoader\Managed\{module}.dll";
|
||||||
|
#else
|
||||||
|
var path = $@"BepInEx\unhollowed\{module}.dll";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return LoadModuleInternal(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool LoadModuleInternal(string fullPath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(fullPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Assembly.Load(File.ReadAllBytes(fullPath));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.GetType() + ", " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static bool IsEnumerable(Type t)
|
||||||
|
{
|
||||||
|
if (typeof(IEnumerable).IsAssignableFrom(t))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||||
|
{
|
||||||
|
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|
||||||
|
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g)
|
||||||
|
|| typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDictionary(Type t)
|
||||||
|
{
|
||||||
|
if (typeof(IDictionary).IsAssignableFrom(t))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||||
|
{
|
||||||
|
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|
||||||
|
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t)
|
||||||
|
|| typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(t);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ExceptionToString(Exception e, bool innerMost = false)
|
||||||
|
{
|
||||||
|
while (innerMost && e.InnerException != null)
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException runtimeEx)
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
e = e.InnerException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.GetType() + ", " + e.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
src/Helpers/Texture2DHelpers.cs
Normal file
171
src/Helpers/Texture2DHelpers.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
#if CPP
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.Helpers
|
||||||
|
{
|
||||||
|
public static class Texture2DHelpers
|
||||||
|
{
|
||||||
|
#if CPP // If Mono
|
||||||
|
#else
|
||||||
|
private static bool isNewEncodeMethod = false;
|
||||||
|
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
|
||||||
|
private static MethodInfo m_encodeToPNGMethod;
|
||||||
|
|
||||||
|
private static MethodInfo GetEncodeToPNGMethod()
|
||||||
|
{
|
||||||
|
if (ReflectionHelpers.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
|
||||||
|
{
|
||||||
|
isNewEncodeMethod = true;
|
||||||
|
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionHelpers.CommonFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionHelpers.CommonFlags);
|
||||||
|
if (method != null)
|
||||||
|
{
|
||||||
|
return m_encodeToPNGMethod = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExplorerCore.Log("ERROR: Cannot get any EncodeToPNG method!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
public static bool IsReadable(this Texture2D tex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// This will cause an exception if it's not readable.
|
||||||
|
// Reason for doing it this way is not all Unity versions
|
||||||
|
// ship with the 'Texture.isReadable' property.
|
||||||
|
|
||||||
|
tex.GetPixel(0, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Texture2D Copy(Texture2D orig, Rect rect) //, bool isDTXnmNormal = false)
|
||||||
|
{
|
||||||
|
Color[] pixels;
|
||||||
|
|
||||||
|
if (!orig.IsReadable())
|
||||||
|
orig = ForceReadTexture(orig);
|
||||||
|
|
||||||
|
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||||
|
|
||||||
|
var _newTex = new Texture2D((int)rect.width, (int)rect.height);
|
||||||
|
_newTex.SetPixels(pixels);
|
||||||
|
|
||||||
|
return _newTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Texture2D ForceReadTexture(Texture2D tex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FilterMode origFilter = tex.filterMode;
|
||||||
|
tex.filterMode = FilterMode.Point;
|
||||||
|
|
||||||
|
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
|
||||||
|
rt.filterMode = FilterMode.Point;
|
||||||
|
RenderTexture.active = rt;
|
||||||
|
Graphics.Blit(tex, rt);
|
||||||
|
|
||||||
|
var _newTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
|
||||||
|
|
||||||
|
_newTex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
|
||||||
|
_newTex.Apply(false, false);
|
||||||
|
|
||||||
|
RenderTexture.active = null;
|
||||||
|
tex.filterMode = origFilter;
|
||||||
|
|
||||||
|
return _newTex;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
string savepath = dir + @"\" + name + ".png";
|
||||||
|
|
||||||
|
// Make sure we can EncodeToPNG it.
|
||||||
|
if (tex.format != TextureFormat.ARGB32 || !tex.IsReadable())
|
||||||
|
{
|
||||||
|
tex = ForceReadTexture(tex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDTXnmNormal)
|
||||||
|
{
|
||||||
|
tex = DTXnmToRGBA(tex);
|
||||||
|
tex.Apply(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
data = tex.EncodeToPNG();
|
||||||
|
#else
|
||||||
|
if (isNewEncodeMethod)
|
||||||
|
{
|
||||||
|
data = (byte[])EncodeToPNGMethod.Invoke(null, new object[] { tex });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = (byte[])EncodeToPNGMethod.Invoke(tex, new object[0]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (data == null || data.Length < 1)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.WriteAllBytes(savepath, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.
|
||||||
|
public static Texture2D DTXnmToRGBA(Texture2D tex)
|
||||||
|
{
|
||||||
|
Color[] colors = tex.GetPixels();
|
||||||
|
|
||||||
|
for (int i = 0; i < colors.Length; i++)
|
||||||
|
{
|
||||||
|
var c = colors[i];
|
||||||
|
|
||||||
|
c.r = c.a * 2 - 1; // red <- alpha
|
||||||
|
c.g = c.g * 2 - 1; // green is always the same
|
||||||
|
|
||||||
|
var rg = new Vector2(c.r, c.g); //this is the red-green vector
|
||||||
|
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
|
||||||
|
|
||||||
|
colors[i] = new Color(
|
||||||
|
(c.r * 0.5f) + 0.5f,
|
||||||
|
(c.g * 0.5f) + 0.25f,
|
||||||
|
(c.b * 0.5f) + 0.5f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newtex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
|
||||||
|
newtex.SetPixels(colors);
|
||||||
|
|
||||||
|
return newtex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
src/Helpers/UnityHelpers.cs
Normal file
62
src/Helpers/UnityHelpers.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Helpers
|
||||||
|
{
|
||||||
|
public static class UnityHelpers
|
||||||
|
{
|
||||||
|
private static Camera m_mainCamera;
|
||||||
|
|
||||||
|
public static Camera MainCamera
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!m_mainCamera)
|
||||||
|
{
|
||||||
|
m_mainCamera = Camera.main;
|
||||||
|
}
|
||||||
|
return m_mainCamera;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
|
||||||
|
{
|
||||||
|
var unityObj = obj as Object;
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
if (!suppressWarning)
|
||||||
|
ExplorerCore.LogWarning("The target instance is null!");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (obj is Object)
|
||||||
|
{
|
||||||
|
if (!unityObj)
|
||||||
|
{
|
||||||
|
if (!suppressWarning)
|
||||||
|
ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToStringLong(this Vector3 vec)
|
||||||
|
{
|
||||||
|
return $"{vec.x:F3}, {vec.y:F3}, {vec.z:F3}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetTransformPath(this Transform t, bool includeThisName = false)
|
||||||
|
{
|
||||||
|
string path = includeThisName ? t.transform.name : "";
|
||||||
|
|
||||||
|
while (t.parent != null)
|
||||||
|
{
|
||||||
|
t = t.parent;
|
||||||
|
path = $"{t.name}/{path}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerRuntimeLib;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
//public class ILBehaviour : MonoBehaviour
|
|
||||||
//{
|
|
||||||
// public ILBehaviour(IntPtr intPtr) : base(intPtr) { }
|
|
||||||
|
|
||||||
// public static T AddToGameObject<T>(GameObject _go) where T : ILBehaviour
|
|
||||||
// {
|
|
||||||
// Il2CppSystem.Type ilType = UnhollowerRuntimeLib.Il2CppType.Of<T>();
|
|
||||||
|
|
||||||
// if (ilType == null)
|
|
||||||
// {
|
|
||||||
// MelonLogger.Log("Error - could not get MB as ilType");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var obj = typeof(T)
|
|
||||||
// .GetConstructor(new Type[] { typeof(IntPtr) })
|
|
||||||
// .Invoke(new object[] { _go.AddComponent(UnhollowerRuntimeLib.Il2CppType.Of<T>()).Pointer });
|
|
||||||
|
|
||||||
// return (T)obj;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
|
21
src/ILRepack.targets
Normal file
21
src/ILRepack.targets
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Target Name="ILRepacker" AfterTargets="Build">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
|
||||||
|
<InputAssemblies Include="..\lib\mcs.dll" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ILRepack
|
||||||
|
Parallel="true"
|
||||||
|
Internalize="true"
|
||||||
|
DebugInfo="false"
|
||||||
|
LibraryPath="..\lib\"
|
||||||
|
InputAssemblies="@(InputAssemblies)"
|
||||||
|
TargetKind="Dll"
|
||||||
|
OutputFile="$(OutputPath)$(AssemblyName).dll"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Target>
|
||||||
|
</Project>
|
15
src/Input/IHandleInput.cs
Normal file
15
src/Input/IHandleInput.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Input
|
||||||
|
{
|
||||||
|
public interface IHandleInput
|
||||||
|
{
|
||||||
|
Vector2 MousePosition { get; }
|
||||||
|
|
||||||
|
bool GetKeyDown(KeyCode key);
|
||||||
|
bool GetKey(KeyCode key);
|
||||||
|
|
||||||
|
bool GetMouseButtonDown(int btn);
|
||||||
|
bool GetMouseButton(int btn);
|
||||||
|
}
|
||||||
|
}
|
43
src/Input/InputManager.cs
Normal file
43
src/Input/InputManager.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
#if CPP
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.Input
|
||||||
|
{
|
||||||
|
public static class InputManager
|
||||||
|
{
|
||||||
|
private static IHandleInput m_inputModule;
|
||||||
|
|
||||||
|
public static Vector3 MousePosition => m_inputModule.MousePosition;
|
||||||
|
|
||||||
|
public static bool GetKeyDown(KeyCode key) => m_inputModule.GetKeyDown(key);
|
||||||
|
public static bool GetKey(KeyCode key) => m_inputModule.GetKey(key);
|
||||||
|
|
||||||
|
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
|
||||||
|
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
if (InputSystem.TKeyboard != null || (ReflectionHelpers.LoadModule("Unity.InputSystem") && InputSystem.TKeyboard != null))
|
||||||
|
{
|
||||||
|
m_inputModule = new InputSystem();
|
||||||
|
}
|
||||||
|
else if (LegacyInput.TInput != null || (ReflectionHelpers.LoadModule("UnityEngine.InputLegacyModule") && LegacyInput.TInput != null))
|
||||||
|
{
|
||||||
|
m_inputModule = new LegacyInput();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (m_inputModule == null)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Could not find any Input module!");
|
||||||
|
m_inputModule = new NoInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
src/Input/InputSystem.cs
Normal file
107
src/Input/InputSystem.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Input
|
||||||
|
{
|
||||||
|
public class InputSystem : IHandleInput
|
||||||
|
{
|
||||||
|
public InputSystem()
|
||||||
|
{
|
||||||
|
ExplorerCore.Log("Initializing new InputSystem support...");
|
||||||
|
|
||||||
|
m_kbCurrentProp = TKeyboard.GetProperty("current");
|
||||||
|
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
|
||||||
|
|
||||||
|
var btnControl = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
|
||||||
|
m_btnIsPressedProp = btnControl.GetProperty("isPressed");
|
||||||
|
m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
|
||||||
|
|
||||||
|
m_mouseCurrentProp = TMouse.GetProperty("current");
|
||||||
|
m_leftButtonProp = TMouse.GetProperty("leftButton");
|
||||||
|
m_rightButtonProp = TMouse.GetProperty("rightButton");
|
||||||
|
|
||||||
|
m_positionProp = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Pointer")
|
||||||
|
.GetProperty("position");
|
||||||
|
|
||||||
|
m_readVector2InputMethod = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
|
||||||
|
.MakeGenericType(typeof(Vector2))
|
||||||
|
.GetMethod("ReadValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
|
||||||
|
private static Type m_tKeyboard;
|
||||||
|
|
||||||
|
public static Type TMouse => m_tMouse ?? (m_tMouse = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Mouse"));
|
||||||
|
private static Type m_tMouse;
|
||||||
|
|
||||||
|
public static Type TKey => m_tKey ?? (m_tKey = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Key"));
|
||||||
|
private static Type m_tKey;
|
||||||
|
|
||||||
|
private static PropertyInfo m_btnIsPressedProp;
|
||||||
|
private static PropertyInfo m_btnWasPressedProp;
|
||||||
|
|
||||||
|
private static object CurrentKeyboard => m_currentKeyboard ?? (m_currentKeyboard = m_kbCurrentProp.GetValue(null, null));
|
||||||
|
private static object m_currentKeyboard;
|
||||||
|
private static PropertyInfo m_kbCurrentProp;
|
||||||
|
private static PropertyInfo m_kbIndexer;
|
||||||
|
|
||||||
|
private static object CurrentMouse => m_currentMouse ?? (m_currentMouse = m_mouseCurrentProp.GetValue(null, null));
|
||||||
|
private static object m_currentMouse;
|
||||||
|
private static PropertyInfo m_mouseCurrentProp;
|
||||||
|
|
||||||
|
private static object LeftMouseButton => m_lmb ?? (m_lmb = m_leftButtonProp.GetValue(CurrentMouse, null));
|
||||||
|
private static object m_lmb;
|
||||||
|
private static PropertyInfo m_leftButtonProp;
|
||||||
|
|
||||||
|
private static object RightMouseButton => m_rmb ?? (m_rmb = m_rightButtonProp.GetValue(CurrentMouse, null));
|
||||||
|
private static object m_rmb;
|
||||||
|
private static PropertyInfo m_rightButtonProp;
|
||||||
|
|
||||||
|
private static object MousePositionInfo => m_pos ?? (m_pos = m_positionProp.GetValue(CurrentMouse, null));
|
||||||
|
private static object m_pos;
|
||||||
|
private static PropertyInfo m_positionProp;
|
||||||
|
private static MethodInfo m_readVector2InputMethod;
|
||||||
|
|
||||||
|
public Vector2 MousePosition => (Vector2)m_readVector2InputMethod.Invoke(MousePositionInfo, new object[0]);
|
||||||
|
|
||||||
|
public bool GetKeyDown(KeyCode key)
|
||||||
|
{
|
||||||
|
var parsedKey = Enum.Parse(TKey, key.ToString());
|
||||||
|
var actualKey = m_kbIndexer.GetValue(CurrentKeyboard, new object[] { parsedKey });
|
||||||
|
|
||||||
|
return (bool)m_btnWasPressedProp.GetValue(actualKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetKey(KeyCode key)
|
||||||
|
{
|
||||||
|
var parsed = Enum.Parse(TKey, key.ToString());
|
||||||
|
var actualKey = m_kbIndexer.GetValue(CurrentKeyboard, new object[] { parsed });
|
||||||
|
|
||||||
|
return (bool)m_btnIsPressedProp.GetValue(actualKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetMouseButtonDown(int btn)
|
||||||
|
{
|
||||||
|
switch (btn)
|
||||||
|
{
|
||||||
|
case 0: return (bool)m_btnWasPressedProp.GetValue(LeftMouseButton, null);
|
||||||
|
case 1: return (bool)m_btnWasPressedProp.GetValue(RightMouseButton, null);
|
||||||
|
// case 2: return (bool)_btnWasPressedProp.GetValue(MiddleMouseButton, null);
|
||||||
|
default: throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetMouseButton(int btn)
|
||||||
|
{
|
||||||
|
switch (btn)
|
||||||
|
{
|
||||||
|
case 0: return (bool)m_btnIsPressedProp.GetValue(LeftMouseButton, null);
|
||||||
|
case 1: return (bool)m_btnIsPressedProp.GetValue(RightMouseButton, null);
|
||||||
|
// case 2: return (bool)_btnIsPressedProp.GetValue(MiddleMouseButton, null);
|
||||||
|
default: throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/Input/LegacyInput.cs
Normal file
40
src/Input/LegacyInput.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Input
|
||||||
|
{
|
||||||
|
public class LegacyInput : IHandleInput
|
||||||
|
{
|
||||||
|
public LegacyInput()
|
||||||
|
{
|
||||||
|
ExplorerCore.Log("Initializing Legacy Input support...");
|
||||||
|
|
||||||
|
m_mousePositionProp = TInput.GetProperty("mousePosition");
|
||||||
|
m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
|
||||||
|
m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
|
||||||
|
m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
|
||||||
|
m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type TInput => m_tInput ?? (m_tInput = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
|
||||||
|
private static Type m_tInput;
|
||||||
|
|
||||||
|
private static PropertyInfo m_mousePositionProp;
|
||||||
|
private static MethodInfo m_getKeyMethod;
|
||||||
|
private static MethodInfo m_getKeyDownMethod;
|
||||||
|
private static MethodInfo m_getMouseButtonMethod;
|
||||||
|
private static MethodInfo m_getMouseButtonDownMethod;
|
||||||
|
|
||||||
|
public Vector2 MousePosition => (Vector3)m_mousePositionProp.GetValue(null, null);
|
||||||
|
|
||||||
|
public bool GetKey(KeyCode key) => (bool)m_getKeyMethod.Invoke(null, new object[] { key });
|
||||||
|
|
||||||
|
public bool GetKeyDown(KeyCode key) => (bool)m_getKeyDownMethod.Invoke(null, new object[] { key });
|
||||||
|
|
||||||
|
public bool GetMouseButton(int btn) => (bool)m_getMouseButtonMethod.Invoke(null, new object[] { btn });
|
||||||
|
|
||||||
|
public bool GetMouseButtonDown(int btn) => (bool)m_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
|
||||||
|
}
|
||||||
|
}
|
17
src/Input/NoInput.cs
Normal file
17
src/Input/NoInput.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Input
|
||||||
|
{
|
||||||
|
// Just a stub for games where no Input module was able to load at all.
|
||||||
|
|
||||||
|
public class NoInput : IHandleInput
|
||||||
|
{
|
||||||
|
public Vector2 MousePosition => Vector2.zero;
|
||||||
|
|
||||||
|
public bool GetKey(KeyCode key) => false;
|
||||||
|
public bool GetKeyDown(KeyCode key) => false;
|
||||||
|
|
||||||
|
public bool GetMouseButton(int btn) => false;
|
||||||
|
public bool GetMouseButtonDown(int btn) => false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,448 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerRuntimeLib;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.SceneManagement;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class GameObjectWindow : WindowManager.UIWindow
|
|
||||||
{
|
|
||||||
public override Il2CppSystem.String Name { get => "GameObject Inspector"; set => Name = value; }
|
|
||||||
|
|
||||||
public GameObject m_object;
|
|
||||||
|
|
||||||
// gui element holders
|
|
||||||
private string m_name;
|
|
||||||
private string m_scene;
|
|
||||||
|
|
||||||
private Vector2 m_transformScroll = Vector2.zero;
|
|
||||||
private Transform[] m_children;
|
|
||||||
|
|
||||||
private Vector2 m_compScroll = Vector2.zero;
|
|
||||||
//private Component[] m_components;
|
|
||||||
|
|
||||||
private float m_translateAmount = 0.3f;
|
|
||||||
private float m_rotateAmount = 50f;
|
|
||||||
private float m_scaleAmount = 0.1f;
|
|
||||||
|
|
||||||
List<Component> m_cachedDestroyList = new List<Component>();
|
|
||||||
//private string m_addComponentInput = "";
|
|
||||||
|
|
||||||
private string m_setParentInput = "";
|
|
||||||
|
|
||||||
public bool GetObjectAsGameObject()
|
|
||||||
{
|
|
||||||
if (Target == null)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Target is null!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetType = Target.GetType();
|
|
||||||
|
|
||||||
if (targetType == typeof(GameObject))
|
|
||||||
{
|
|
||||||
m_object = Target as GameObject;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (targetType == typeof(Transform))
|
|
||||||
{
|
|
||||||
m_object = (Target as Transform).gameObject;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
MelonLogger.Log("Error: Target is null or not a GameObject/Transform!");
|
|
||||||
DestroyWindow();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
if (!GetObjectAsGameObject())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_name = m_object.name;
|
|
||||||
m_scene = m_object.scene == null ? "null" : m_object.scene.name;
|
|
||||||
|
|
||||||
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>();
|
|
||||||
//m_object.GetComponents(listComps);
|
|
||||||
//m_components = listComps.ToArray();
|
|
||||||
|
|
||||||
var list = new List<Transform>();
|
|
||||||
for (int i = 0; i < m_object.transform.childCount; i++)
|
|
||||||
{
|
|
||||||
list.Add(m_object.transform.GetChild(i));
|
|
||||||
}
|
|
||||||
m_children = list.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update()
|
|
||||||
{
|
|
||||||
if (!m_object && !GetObjectAsGameObject())
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Object is null! Destroying window...");
|
|
||||||
DestroyWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InspectGameObject(GameObject obj)
|
|
||||||
{
|
|
||||||
var window = WindowManager.InspectObject(obj, out bool created);
|
|
||||||
|
|
||||||
if (created)
|
|
||||||
{
|
|
||||||
window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height);
|
|
||||||
DestroyWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReflectObject(Il2CppSystem.Object obj)
|
|
||||||
{
|
|
||||||
var window = WindowManager.InspectObject(obj, out bool created);
|
|
||||||
|
|
||||||
if (created)
|
|
||||||
{
|
|
||||||
if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100))
|
|
||||||
{
|
|
||||||
window.m_rect = new Rect(
|
|
||||||
this.m_rect.x + this.m_rect.width + 20,
|
|
||||||
this.m_rect.y,
|
|
||||||
550,
|
|
||||||
700);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WindowFunction(int windowID)
|
|
||||||
{
|
|
||||||
Header();
|
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
|
||||||
|
|
||||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
|
|
||||||
if (m_scene == CppExplorer.ActiveSceneName)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
|
|
||||||
{
|
|
||||||
ScenePage.Instance.SetTransformTarget(m_object);
|
|
||||||
MainMenu.SetCurrentPage(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
|
|
||||||
string pathlabel = CppExplorer.GetGameObjectPath(m_object.transform);
|
|
||||||
if (m_object.transform.parent != null)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
|
||||||
{
|
|
||||||
InspectGameObject(m_object.transform.parent.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GUILayout.TextArea(pathlabel, null);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
|
|
||||||
GUILayout.TextArea(m_name, null);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
// --- Horizontal Columns section ---
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
|
|
||||||
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
|
|
||||||
TransformList();
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
|
|
||||||
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
|
|
||||||
ComponentList();
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal(); // end horiz columns
|
|
||||||
|
|
||||||
GameObjectControls();
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
|
|
||||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
|
||||||
|
|
||||||
GUILayout.EndArea();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TransformList()
|
|
||||||
{
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
|
||||||
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView);
|
|
||||||
|
|
||||||
GUILayout.Label("<b>Children:</b>", null);
|
|
||||||
if (m_children != null && m_children.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (var obj in m_children.Where(x => x.childCount > 0))
|
|
||||||
{
|
|
||||||
if (!obj)
|
|
||||||
{
|
|
||||||
GUILayout.Label("null", null);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
|
||||||
}
|
|
||||||
foreach (var obj in m_children.Where(x => x.childCount == 0))
|
|
||||||
{
|
|
||||||
if (!obj)
|
|
||||||
{
|
|
||||||
GUILayout.Label("null", null);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i>None</i>", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void ComponentList()
|
|
||||||
{
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
|
||||||
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView);
|
|
||||||
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (m_cachedDestroyList.Count > 0)
|
|
||||||
{
|
|
||||||
m_cachedDestroyList.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
var m_components = new Il2CppSystem.Collections.Generic.List<Component>();
|
|
||||||
m_object.GetComponentsInternal(Il2CppType.Of<Component>(), false, false, true, false, m_components);
|
|
||||||
|
|
||||||
var ilTypeOfTransform = Il2CppType.Of<Transform>();
|
|
||||||
var ilTypeOfBehaviour = Il2CppType.Of<Behaviour>();
|
|
||||||
foreach (var component in m_components)
|
|
||||||
{
|
|
||||||
var ilType = component.GetIl2CppType();
|
|
||||||
if (ilType == ilTypeOfTransform)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
if (ilTypeOfBehaviour.IsAssignableFrom(ilType))
|
|
||||||
{
|
|
||||||
BehaviourEnabledBtn(component.TryCast<Behaviour>());
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
|
|
||||||
{
|
|
||||||
ReflectObject(component);
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
|
|
||||||
{
|
|
||||||
m_cachedDestroyList.Add(component);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
if (m_cachedDestroyList.Count > 0)
|
|
||||||
{
|
|
||||||
for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var comp = m_cachedDestroyList[i];
|
|
||||||
GameObject.Destroy(comp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
|
|
||||||
//GUILayout.BeginHorizontal(null);
|
|
||||||
//m_addComponentInput = GUILayout.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 150) });
|
|
||||||
//if (GUILayout.Button("Add Component", new GUILayoutOption[] { GUILayout.Width(120) }))
|
|
||||||
//{
|
|
||||||
// if (HPExplorer.GetType(m_addComponentInput) is Type type && typeof(Component).IsAssignableFrom(type))
|
|
||||||
// {
|
|
||||||
// var comp = m_object.AddComponent(type);
|
|
||||||
// var list = m_components.ToList();
|
|
||||||
// list.Add(comp);
|
|
||||||
// m_components = list.ToArray();
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// MelonLogger.LogWarning($"Could not get type '{m_addComponentInput}'. If it's not a typo, try the fully qualified name.");
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BehaviourEnabledBtn(Behaviour obj)
|
|
||||||
{
|
|
||||||
var _col = GUI.color;
|
|
||||||
bool _enabled = obj.enabled;
|
|
||||||
if (_enabled)
|
|
||||||
{
|
|
||||||
GUI.color = Color.green;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.color = Color.red;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ toggle active button ------
|
|
||||||
|
|
||||||
_enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
|
||||||
if (obj.enabled != _enabled)
|
|
||||||
{
|
|
||||||
obj.enabled = _enabled;
|
|
||||||
}
|
|
||||||
GUI.color = _col;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GameObjectControls()
|
|
||||||
{
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(530) });
|
|
||||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
bool m_active = m_object.activeSelf;
|
|
||||||
m_active = GUILayout.Toggle(m_active, (m_active ? "<color=lime>Enabled " : "<color=red>Disabled") + "</color>",
|
|
||||||
new GUILayoutOption[] { GUILayout.Width(80) });
|
|
||||||
if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); }
|
|
||||||
|
|
||||||
UIStyles.InstantiateButton(m_object, 100);
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
|
|
||||||
if (GUILayout.Button("Remove from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
|
|
||||||
{
|
|
||||||
m_object.transform.parent = null;
|
|
||||||
}
|
|
||||||
m_setParentInput = GUILayout.TextField(m_setParentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width - 280) });
|
|
||||||
if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
|
|
||||||
{
|
|
||||||
if (GameObject.Find(m_setParentInput) is GameObject newparent)
|
|
||||||
{
|
|
||||||
m_object.transform.parent = newparent.transform;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
|
||||||
|
|
||||||
var t = m_object.transform;
|
|
||||||
TranslateControl(t, TranslateType.Position, ref m_translateAmount, false);
|
|
||||||
TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true);
|
|
||||||
TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false);
|
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
|
|
||||||
if (GUILayout.Button("<color=red><b>Destroy</b></color>", null))
|
|
||||||
{
|
|
||||||
GameObject.Destroy(m_object);
|
|
||||||
DestroyWindow();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TranslateType
|
|
||||||
{
|
|
||||||
Position,
|
|
||||||
Rotation,
|
|
||||||
Scale
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime)
|
|
||||||
{
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("<color=cyan><b>" + mode + "</b></color>:", new GUILayoutOption[] { GUILayout.Width(65) });
|
|
||||||
|
|
||||||
Vector3 vector = Vector3.zero;
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case TranslateType.Position: vector = transform.localPosition; break;
|
|
||||||
case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break;
|
|
||||||
case TranslateType.Scale: vector = transform.localScale; break;
|
|
||||||
}
|
|
||||||
GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
||||||
|
|
||||||
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
|
||||||
PlusMinusFloat(ref vector.x, amount, multByTime);
|
|
||||||
|
|
||||||
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
|
||||||
PlusMinusFloat(ref vector.y, amount, multByTime);
|
|
||||||
|
|
||||||
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
|
||||||
PlusMinusFloat(ref vector.z, amount, multByTime);
|
|
||||||
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case TranslateType.Position: transform.localPosition = vector; break;
|
|
||||||
case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break;
|
|
||||||
case TranslateType.Scale: transform.localScale = vector; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
|
|
||||||
var input = amount.ToString("F3");
|
|
||||||
input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) });
|
|
||||||
if (float.TryParse(input, out float f))
|
|
||||||
{
|
|
||||||
amount = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
|
|
||||||
{
|
|
||||||
string s = f.ToString("F3");
|
|
||||||
s = GUILayout.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
|
|
||||||
if (float.TryParse(s, out float f2))
|
|
||||||
{
|
|
||||||
f = f2;
|
|
||||||
}
|
|
||||||
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
|
||||||
{
|
|
||||||
f -= multByTime ? amount * Time.deltaTime : amount;
|
|
||||||
}
|
|
||||||
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
|
||||||
{
|
|
||||||
f += multByTime ? amount * Time.deltaTime : amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
219
src/Inspectors/GameObjects/ChildList.cs
Normal file
219
src/Inspectors/GameObjects/ChildList.cs
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.GameObjects
|
||||||
|
{
|
||||||
|
public class ChildList
|
||||||
|
{
|
||||||
|
internal static ChildList Instance;
|
||||||
|
|
||||||
|
public ChildList()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PageHandler s_childListPageHandler;
|
||||||
|
private static GameObject s_childListContent;
|
||||||
|
|
||||||
|
private static GameObject[] s_allChildren = new GameObject[0];
|
||||||
|
private static readonly List<GameObject> s_childrenShortlist = new List<GameObject>();
|
||||||
|
private static int s_lastChildCount;
|
||||||
|
|
||||||
|
private static readonly List<Text> s_childListTexts = new List<Text>();
|
||||||
|
private static readonly List<Toggle> s_childListToggles = new List<Toggle>();
|
||||||
|
|
||||||
|
internal void RefreshChildObjectList()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
|
||||||
|
s_allChildren = new GameObject[go.transform.childCount];
|
||||||
|
for (int i = 0; i < go.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
var child = go.transform.GetChild(i);
|
||||||
|
s_allChildren[i] = child.gameObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
var objects = s_allChildren;
|
||||||
|
s_childListPageHandler.ListCount = objects.Length;
|
||||||
|
|
||||||
|
int newCount = 0;
|
||||||
|
|
||||||
|
foreach (var itemIndex in s_childListPageHandler)
|
||||||
|
{
|
||||||
|
newCount++;
|
||||||
|
|
||||||
|
// normalized index starting from 0
|
||||||
|
var i = itemIndex - s_childListPageHandler.StartIndex;
|
||||||
|
|
||||||
|
if (itemIndex >= objects.Length)
|
||||||
|
{
|
||||||
|
if (i > s_lastChildCount || i >= s_childListTexts.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
GameObject label = s_childListTexts[i].transform.parent.parent.gameObject;
|
||||||
|
if (label.activeSelf)
|
||||||
|
label.SetActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameObject obj = objects[itemIndex];
|
||||||
|
|
||||||
|
if (!obj)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i >= s_childrenShortlist.Count)
|
||||||
|
{
|
||||||
|
s_childrenShortlist.Add(obj);
|
||||||
|
AddChildListButton();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_childrenShortlist[i] = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = s_childListTexts[i];
|
||||||
|
|
||||||
|
var name = obj.name;
|
||||||
|
|
||||||
|
if (obj.transform.childCount > 0)
|
||||||
|
name = $"<color=grey>[{obj.transform.childCount}]</color> {name}";
|
||||||
|
|
||||||
|
text.text = name;
|
||||||
|
text.color = obj.activeSelf ? Color.green : Color.red;
|
||||||
|
|
||||||
|
var tog = s_childListToggles[i];
|
||||||
|
tog.isOn = obj.activeSelf;
|
||||||
|
|
||||||
|
var label = text.transform.parent.parent.gameObject;
|
||||||
|
if (!label.activeSelf)
|
||||||
|
{
|
||||||
|
label.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_lastChildCount = newCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnChildListObjectClicked(int index)
|
||||||
|
{
|
||||||
|
if (GameObjectInspector.ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index])
|
||||||
|
return;
|
||||||
|
|
||||||
|
GameObjectInspector.ActiveInstance.ChangeInspectorTarget(s_childrenShortlist[index]);
|
||||||
|
GameObjectInspector.ActiveInstance.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnChildListPageTurn()
|
||||||
|
{
|
||||||
|
if (Instance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Instance.RefreshChildObjectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnToggleClicked(int index, bool newVal)
|
||||||
|
{
|
||||||
|
if (GameObjectInspector.ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index])
|
||||||
|
return;
|
||||||
|
|
||||||
|
var obj = s_childrenShortlist[index];
|
||||||
|
obj.SetActive(newVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructChildList(GameObject parent)
|
||||||
|
{
|
||||||
|
var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var vertGroup = vertGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
vertGroup.childForceExpandHeight = false;
|
||||||
|
vertGroup.childForceExpandWidth = false;
|
||||||
|
vertGroup.childControlWidth = true;
|
||||||
|
vertGroup.spacing = 5;
|
||||||
|
var vertLayout = vertGroupObj.AddComponent<LayoutElement>();
|
||||||
|
vertLayout.minWidth = 120;
|
||||||
|
vertLayout.flexibleWidth = 25000;
|
||||||
|
vertLayout.minHeight = 200;
|
||||||
|
vertLayout.flexibleHeight = 5000;
|
||||||
|
|
||||||
|
var childTitleObj = UIFactory.CreateLabel(vertGroupObj, TextAnchor.MiddleLeft);
|
||||||
|
var childTitleText = childTitleObj.GetComponent<Text>();
|
||||||
|
childTitleText.text = "Children";
|
||||||
|
childTitleText.color = Color.grey;
|
||||||
|
childTitleText.fontSize = 14;
|
||||||
|
var childTitleLayout = childTitleObj.AddComponent<LayoutElement>();
|
||||||
|
childTitleLayout.minHeight = 30;
|
||||||
|
|
||||||
|
var childrenScrollObj = UIFactory.CreateScrollView(vertGroupObj, out s_childListContent, out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
|
||||||
|
var contentLayout = childrenScrollObj.GetComponent<LayoutElement>();
|
||||||
|
contentLayout.minHeight = 50;
|
||||||
|
|
||||||
|
s_childListPageHandler = new PageHandler(scroller);
|
||||||
|
s_childListPageHandler.ConstructUI(vertGroupObj);
|
||||||
|
s_childListPageHandler.OnPageChanged += OnChildListPageTurn;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddChildListButton()
|
||||||
|
{
|
||||||
|
int thisIndex = s_childListTexts.Count;
|
||||||
|
|
||||||
|
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_childListContent, new Color(0.07f, 0.07f, 0.07f));
|
||||||
|
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
btnGroup.childForceExpandWidth = true;
|
||||||
|
btnGroup.childControlWidth = true;
|
||||||
|
btnGroup.childForceExpandHeight = false;
|
||||||
|
btnGroup.childControlHeight = true;
|
||||||
|
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.flexibleWidth = 320;
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.flexibleHeight = 0;
|
||||||
|
btnGroupObj.AddComponent<Mask>();
|
||||||
|
|
||||||
|
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
|
||||||
|
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||||
|
toggleLayout.minHeight = 25;
|
||||||
|
toggleLayout.minWidth = 25;
|
||||||
|
toggleText.text = "";
|
||||||
|
toggle.isOn = false;
|
||||||
|
s_childListToggles.Add(toggle);
|
||||||
|
toggle.onValueChanged.AddListener((bool val) => { OnToggleClicked(thisIndex, val); });
|
||||||
|
|
||||||
|
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||||
|
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||||
|
mainBtnLayout.minHeight = 25;
|
||||||
|
mainBtnLayout.flexibleHeight = 0;
|
||||||
|
mainBtnLayout.minWidth = 25;
|
||||||
|
mainBtnLayout.flexibleWidth = 999;
|
||||||
|
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||||
|
ColorBlock mainColors = mainBtn.colors;
|
||||||
|
mainColors.normalColor = new Color(0.07f, 0.07f, 0.07f);
|
||||||
|
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
mainBtn.colors = mainColors;
|
||||||
|
mainBtn.onClick.AddListener(() => { OnChildListObjectClicked(thisIndex); });
|
||||||
|
|
||||||
|
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||||
|
mainText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
mainText.resizeTextForBestFit = true;
|
||||||
|
mainText.resizeTextMaxSize = 14;
|
||||||
|
mainText.resizeTextMinSize = 10;
|
||||||
|
s_childListTexts.Add(mainText);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
228
src/Inspectors/GameObjects/ComponentList.cs
Normal file
228
src/Inspectors/GameObjects/ComponentList.cs
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
//using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.GameObjects
|
||||||
|
{
|
||||||
|
public class ComponentList
|
||||||
|
{
|
||||||
|
internal static ComponentList Instance;
|
||||||
|
|
||||||
|
public ComponentList()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PageHandler s_compListPageHandler;
|
||||||
|
private static Component[] s_allComps = new Component[0];
|
||||||
|
private static readonly List<Component> s_compShortlist = new List<Component>();
|
||||||
|
private static GameObject s_compListContent;
|
||||||
|
private static readonly List<Text> s_compListTexts = new List<Text>();
|
||||||
|
private static int s_lastCompCount;
|
||||||
|
public static readonly List<Toggle> s_compToggles = new List<Toggle>();
|
||||||
|
|
||||||
|
internal void RefreshComponentList()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
|
||||||
|
s_allComps = go.GetComponents<Component>().ToArray();
|
||||||
|
|
||||||
|
var components = s_allComps;
|
||||||
|
s_compListPageHandler.ListCount = components.Length;
|
||||||
|
|
||||||
|
//int startIndex = m_sceneListPageHandler.StartIndex;
|
||||||
|
|
||||||
|
int newCount = 0;
|
||||||
|
|
||||||
|
foreach (var itemIndex in s_compListPageHandler)
|
||||||
|
{
|
||||||
|
newCount++;
|
||||||
|
|
||||||
|
// normalized index starting from 0
|
||||||
|
var i = itemIndex - s_compListPageHandler.StartIndex;
|
||||||
|
|
||||||
|
if (itemIndex >= components.Length)
|
||||||
|
{
|
||||||
|
if (i > s_lastCompCount || i >= s_compListTexts.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
GameObject label = s_compListTexts[i].transform.parent.parent.gameObject;
|
||||||
|
if (label.activeSelf)
|
||||||
|
label.SetActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Component comp = components[itemIndex];
|
||||||
|
|
||||||
|
if (!comp)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i >= s_compShortlist.Count)
|
||||||
|
{
|
||||||
|
s_compShortlist.Add(comp);
|
||||||
|
AddCompListButton();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_compShortlist[i] = comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = s_compListTexts[i];
|
||||||
|
|
||||||
|
text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true);
|
||||||
|
|
||||||
|
var toggle = s_compToggles[i];
|
||||||
|
if (comp is Behaviour behaviour)
|
||||||
|
{
|
||||||
|
if (!toggle.gameObject.activeSelf)
|
||||||
|
toggle.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
toggle.isOn = behaviour.enabled;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (toggle.gameObject.activeSelf)
|
||||||
|
toggle.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var label = text.transform.parent.parent.gameObject;
|
||||||
|
if (!label.activeSelf)
|
||||||
|
{
|
||||||
|
label.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_lastCompCount = newCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnCompToggleClicked(int index, bool value)
|
||||||
|
{
|
||||||
|
var comp = s_compShortlist[index];
|
||||||
|
|
||||||
|
(comp as Behaviour).enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnCompListObjectClicked(int index)
|
||||||
|
{
|
||||||
|
if (index >= s_compShortlist.Count || !s_compShortlist[index])
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InspectorManager.Instance.Inspect(s_compShortlist[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnCompListPageTurn()
|
||||||
|
{
|
||||||
|
if (Instance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Instance.RefreshComponentList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructCompList(GameObject parent)
|
||||||
|
{
|
||||||
|
var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var vertGroup = vertGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
vertGroup.childForceExpandHeight = false;
|
||||||
|
vertGroup.childForceExpandWidth = false;
|
||||||
|
vertGroup.childControlWidth = true;
|
||||||
|
vertGroup.spacing = 5;
|
||||||
|
var vertLayout = vertGroupObj.AddComponent<LayoutElement>();
|
||||||
|
vertLayout.minWidth = 120;
|
||||||
|
vertLayout.flexibleWidth = 25000;
|
||||||
|
vertLayout.minHeight = 200;
|
||||||
|
vertLayout.flexibleHeight = 5000;
|
||||||
|
|
||||||
|
var compTitleObj = UIFactory.CreateLabel(vertGroupObj, TextAnchor.MiddleLeft);
|
||||||
|
var compTitleText = compTitleObj.GetComponent<Text>();
|
||||||
|
compTitleText.text = "Components";
|
||||||
|
compTitleText.color = Color.grey;
|
||||||
|
compTitleText.fontSize = 14;
|
||||||
|
var childTitleLayout = compTitleObj.AddComponent<LayoutElement>();
|
||||||
|
childTitleLayout.minHeight = 30;
|
||||||
|
|
||||||
|
var compScrollObj = UIFactory.CreateScrollView(vertGroupObj, out s_compListContent, out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
|
||||||
|
var contentLayout = compScrollObj.AddComponent<LayoutElement>();
|
||||||
|
contentLayout.minHeight = 50;
|
||||||
|
|
||||||
|
s_compListPageHandler = new PageHandler(scroller);
|
||||||
|
s_compListPageHandler.ConstructUI(vertGroupObj);
|
||||||
|
s_compListPageHandler.OnPageChanged += OnCompListPageTurn;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddCompListButton()
|
||||||
|
{
|
||||||
|
int thisIndex = s_compListTexts.Count;
|
||||||
|
|
||||||
|
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
|
||||||
|
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
btnGroup.childForceExpandWidth = true;
|
||||||
|
btnGroup.childControlWidth = true;
|
||||||
|
btnGroup.childForceExpandHeight = false;
|
||||||
|
btnGroup.childControlHeight = true;
|
||||||
|
btnGroup.childAlignment = TextAnchor.MiddleLeft;
|
||||||
|
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minWidth = 25;
|
||||||
|
btnLayout.flexibleWidth = 999;
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.flexibleHeight = 0;
|
||||||
|
btnGroupObj.AddComponent<Mask>();
|
||||||
|
|
||||||
|
// Behaviour enabled toggle
|
||||||
|
|
||||||
|
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
|
||||||
|
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||||
|
toggleLayout.minHeight = 25;
|
||||||
|
toggleLayout.minWidth = 25;
|
||||||
|
toggleText.text = "";
|
||||||
|
toggle.isOn = false;
|
||||||
|
s_compToggles.Add(toggle);
|
||||||
|
toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); });
|
||||||
|
|
||||||
|
// Main component button
|
||||||
|
|
||||||
|
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||||
|
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||||
|
mainBtnLayout.minHeight = 25;
|
||||||
|
mainBtnLayout.flexibleHeight = 0;
|
||||||
|
mainBtnLayout.minWidth = 25;
|
||||||
|
mainBtnLayout.flexibleWidth = 999;
|
||||||
|
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||||
|
ColorBlock mainColors = mainBtn.colors;
|
||||||
|
mainColors.normalColor = new Color(0.07f, 0.07f, 0.07f);
|
||||||
|
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
mainBtn.colors = mainColors;
|
||||||
|
mainBtn.onClick.AddListener(() => { OnCompListObjectClicked(thisIndex); });
|
||||||
|
|
||||||
|
// Component button text
|
||||||
|
|
||||||
|
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||||
|
mainText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
//mainText.color = SyntaxColors.Class_Instance.ToColor();
|
||||||
|
mainText.resizeTextForBestFit = true;
|
||||||
|
mainText.resizeTextMaxSize = 14;
|
||||||
|
mainText.resizeTextMinSize = 8;
|
||||||
|
|
||||||
|
s_compListTexts.Add(mainText);
|
||||||
|
|
||||||
|
// TODO remove component button
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
630
src/Inspectors/GameObjects/GameObjectControls.cs
Normal file
630
src/Inspectors/GameObjects/GameObjectControls.cs
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
//using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.GameObjects
|
||||||
|
{
|
||||||
|
public class GameObjectControls
|
||||||
|
{
|
||||||
|
internal static GameObjectControls Instance;
|
||||||
|
|
||||||
|
public GameObjectControls()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InputField s_setParentInput;
|
||||||
|
|
||||||
|
private static ControlEditor s_positionControl;
|
||||||
|
private static ControlEditor s_localPosControl;
|
||||||
|
private static ControlEditor s_rotationControl;
|
||||||
|
private static ControlEditor s_scaleControl;
|
||||||
|
|
||||||
|
// Transform Vector editors
|
||||||
|
|
||||||
|
internal struct ControlEditor
|
||||||
|
{
|
||||||
|
public InputField fullValue;
|
||||||
|
public Slider[] sliders;
|
||||||
|
public InputField[] inputs;
|
||||||
|
public Text[] values;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool s_sliderChangedWanted;
|
||||||
|
private static Slider s_currentSlider;
|
||||||
|
private static ControlType s_currentSliderType;
|
||||||
|
private static VectorValue s_currentSliderValueType;
|
||||||
|
private static float s_currentSliderValue;
|
||||||
|
|
||||||
|
internal enum ControlType
|
||||||
|
{
|
||||||
|
position,
|
||||||
|
localPosition,
|
||||||
|
eulerAngles,
|
||||||
|
localScale
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum VectorValue
|
||||||
|
{
|
||||||
|
x, y, z
|
||||||
|
};
|
||||||
|
|
||||||
|
internal void RefreshControls()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
|
||||||
|
s_positionControl.fullValue.text = go.transform.position.ToStringLong();
|
||||||
|
s_positionControl.values[0].text = go.transform.position.x.ToString("F3");
|
||||||
|
s_positionControl.values[1].text = go.transform.position.y.ToString("F3");
|
||||||
|
s_positionControl.values[2].text = go.transform.position.z.ToString("F3");
|
||||||
|
|
||||||
|
s_localPosControl.fullValue.text = go.transform.localPosition.ToStringLong();
|
||||||
|
s_localPosControl.values[0].text = go.transform.localPosition.x.ToString("F3");
|
||||||
|
s_localPosControl.values[1].text = go.transform.localPosition.y.ToString("F3");
|
||||||
|
s_localPosControl.values[2].text = go.transform.localPosition.z.ToString("F3");
|
||||||
|
|
||||||
|
s_rotationControl.fullValue.text = go.transform.eulerAngles.ToStringLong();
|
||||||
|
s_rotationControl.values[0].text = go.transform.eulerAngles.x.ToString("F3");
|
||||||
|
s_rotationControl.values[1].text = go.transform.eulerAngles.y.ToString("F3");
|
||||||
|
s_rotationControl.values[2].text = go.transform.eulerAngles.z.ToString("F3");
|
||||||
|
|
||||||
|
s_scaleControl.fullValue.text = go.transform.localScale.ToStringLong();
|
||||||
|
s_scaleControl.values[0].text = go.transform.localScale.x.ToString("F3");
|
||||||
|
s_scaleControl.values[1].text = go.transform.localScale.y.ToString("F3");
|
||||||
|
s_scaleControl.values[2].text = go.transform.localScale.z.ToString("F3");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnSetParentClicked()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
|
||||||
|
if (!go)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var input = s_setParentInput.text;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
go.transform.parent = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (GameObject.Find(input) is GameObject newParent)
|
||||||
|
{
|
||||||
|
go.transform.parent = newParent.transform;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExplorerCore.Log($"Could not find any GameObject from name or path '{input}'! Note: The target must be enabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnSliderControlChanged(float value, Slider slider, ControlType controlType, VectorValue vectorValue)
|
||||||
|
{
|
||||||
|
if (value == 0)
|
||||||
|
s_sliderChangedWanted = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!s_sliderChangedWanted)
|
||||||
|
{
|
||||||
|
s_sliderChangedWanted = true;
|
||||||
|
s_currentSlider = slider;
|
||||||
|
s_currentSliderType = controlType;
|
||||||
|
s_currentSliderValueType = vectorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_currentSliderValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UpdateSliderControl()
|
||||||
|
{
|
||||||
|
if (!InputManager.GetMouseButton(0))
|
||||||
|
{
|
||||||
|
s_sliderChangedWanted = false;
|
||||||
|
s_currentSlider.value = 0;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameObjectInspector.ActiveInstance == null) return;
|
||||||
|
|
||||||
|
var transform = GameObjectInspector.ActiveInstance.TargetGO.transform;
|
||||||
|
|
||||||
|
// get the current vector for the control type
|
||||||
|
Vector3 vector = Vector2.zero;
|
||||||
|
switch (s_currentSliderType)
|
||||||
|
{
|
||||||
|
case ControlType.position:
|
||||||
|
vector = transform.position; break;
|
||||||
|
case ControlType.localPosition:
|
||||||
|
vector = transform.localPosition; break;
|
||||||
|
case ControlType.eulerAngles:
|
||||||
|
vector = transform.eulerAngles; break;
|
||||||
|
case ControlType.localScale:
|
||||||
|
vector = transform.localScale; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply vector value change
|
||||||
|
switch (s_currentSliderValueType)
|
||||||
|
{
|
||||||
|
case VectorValue.x:
|
||||||
|
vector.x += s_currentSliderValue; break;
|
||||||
|
case VectorValue.y:
|
||||||
|
vector.y += s_currentSliderValue; break;
|
||||||
|
case VectorValue.z:
|
||||||
|
vector.z += s_currentSliderValue; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set vector to transform member
|
||||||
|
switch (s_currentSliderType)
|
||||||
|
{
|
||||||
|
case ControlType.position:
|
||||||
|
transform.position = vector; break;
|
||||||
|
case ControlType.localPosition:
|
||||||
|
transform.localPosition = vector; break;
|
||||||
|
case ControlType.eulerAngles:
|
||||||
|
transform.eulerAngles = vector; break;
|
||||||
|
case ControlType.localScale:
|
||||||
|
transform.localScale = vector; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnVectorControlInputApplied(ControlType controlType, VectorValue vectorValue)
|
||||||
|
{
|
||||||
|
if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return;
|
||||||
|
|
||||||
|
// get relevant input for controltype + value
|
||||||
|
|
||||||
|
InputField[] inputs = null;
|
||||||
|
switch (controlType)
|
||||||
|
{
|
||||||
|
case ControlType.position:
|
||||||
|
inputs = s_positionControl.inputs; break;
|
||||||
|
case ControlType.localPosition:
|
||||||
|
inputs = s_localPosControl.inputs; break;
|
||||||
|
case ControlType.eulerAngles:
|
||||||
|
inputs = s_rotationControl.inputs; break;
|
||||||
|
case ControlType.localScale:
|
||||||
|
inputs = s_scaleControl.inputs; break;
|
||||||
|
}
|
||||||
|
InputField input = inputs[(int)vectorValue];
|
||||||
|
|
||||||
|
float val = float.Parse(input.text);
|
||||||
|
|
||||||
|
// apply transform value
|
||||||
|
|
||||||
|
Vector3 vector = Vector3.zero;
|
||||||
|
var transform = instance.TargetGO.transform;
|
||||||
|
switch (controlType)
|
||||||
|
{
|
||||||
|
case ControlType.position:
|
||||||
|
vector = transform.position; break;
|
||||||
|
case ControlType.localPosition:
|
||||||
|
vector = transform.localPosition; break;
|
||||||
|
case ControlType.eulerAngles:
|
||||||
|
vector = transform.eulerAngles; break;
|
||||||
|
case ControlType.localScale:
|
||||||
|
vector = transform.localScale; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (vectorValue)
|
||||||
|
{
|
||||||
|
case VectorValue.x:
|
||||||
|
vector.x = val; break;
|
||||||
|
case VectorValue.y:
|
||||||
|
vector.y = val; break;
|
||||||
|
case VectorValue.z:
|
||||||
|
vector.z = val; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set back to transform
|
||||||
|
switch (controlType)
|
||||||
|
{
|
||||||
|
case ControlType.position:
|
||||||
|
transform.position = vector; break;
|
||||||
|
case ControlType.localPosition:
|
||||||
|
transform.localPosition = vector; break;
|
||||||
|
case ControlType.eulerAngles:
|
||||||
|
transform.eulerAngles = vector; break;
|
||||||
|
case ControlType.localScale:
|
||||||
|
transform.localScale = vector; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructControls(GameObject parent)
|
||||||
|
{
|
||||||
|
var controlsObj = UIFactory.CreateVerticalGroup(parent, new Color(0.07f, 0.07f, 0.07f));
|
||||||
|
var controlsGroup = controlsObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
controlsGroup.childForceExpandWidth = false;
|
||||||
|
controlsGroup.childControlWidth = true;
|
||||||
|
controlsGroup.childForceExpandHeight = false;
|
||||||
|
controlsGroup.spacing = 5;
|
||||||
|
controlsGroup.padding.top = 4;
|
||||||
|
controlsGroup.padding.left = 4;
|
||||||
|
controlsGroup.padding.right = 4;
|
||||||
|
controlsGroup.padding.bottom = 4;
|
||||||
|
|
||||||
|
// ~~~~~~ Top row ~~~~~~
|
||||||
|
|
||||||
|
var topRow = UIFactory.CreateHorizontalGroup(controlsObj, new Color(1, 1, 1, 0));
|
||||||
|
var topRowGroup = topRow.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
topRowGroup.childForceExpandWidth = false;
|
||||||
|
topRowGroup.childControlWidth = true;
|
||||||
|
topRowGroup.childForceExpandHeight = false;
|
||||||
|
topRowGroup.childControlHeight = true;
|
||||||
|
topRowGroup.spacing = 5;
|
||||||
|
|
||||||
|
var hideButtonObj = UIFactory.CreateButton(topRow);
|
||||||
|
var hideButton = hideButtonObj.GetComponent<Button>();
|
||||||
|
var hideColors = hideButton.colors;
|
||||||
|
hideColors.normalColor = new Color(0.16f, 0.16f, 0.16f);
|
||||||
|
hideButton.colors = hideColors;
|
||||||
|
var hideText = hideButtonObj.GetComponentInChildren<Text>();
|
||||||
|
hideText.text = "Show";
|
||||||
|
hideText.fontSize = 14;
|
||||||
|
var hideButtonLayout = hideButtonObj.AddComponent<LayoutElement>();
|
||||||
|
hideButtonLayout.minWidth = 40;
|
||||||
|
hideButtonLayout.flexibleWidth = 0;
|
||||||
|
hideButtonLayout.minHeight = 25;
|
||||||
|
hideButtonLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var topTitle = UIFactory.CreateLabel(topRow, TextAnchor.MiddleLeft);
|
||||||
|
var topText = topTitle.GetComponent<Text>();
|
||||||
|
topText.text = "Controls";
|
||||||
|
var titleLayout = topTitle.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minWidth = 100;
|
||||||
|
titleLayout.flexibleWidth = 9500;
|
||||||
|
titleLayout.minHeight = 25;
|
||||||
|
|
||||||
|
//// ~~~~~~~~ Content ~~~~~~~~ //
|
||||||
|
|
||||||
|
var contentObj = UIFactory.CreateVerticalGroup(controlsObj, new Color(1, 1, 1, 0));
|
||||||
|
var contentGroup = contentObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
contentGroup.childForceExpandHeight = false;
|
||||||
|
contentGroup.childControlHeight = true;
|
||||||
|
contentGroup.spacing = 5;
|
||||||
|
contentGroup.childForceExpandWidth = true;
|
||||||
|
contentGroup.childControlWidth = true;
|
||||||
|
|
||||||
|
// ~~ add hide button callback now that we have scroll reference ~~
|
||||||
|
hideButton.onClick.AddListener(OnHideClicked);
|
||||||
|
void OnHideClicked()
|
||||||
|
{
|
||||||
|
if (hideText.text == "Show")
|
||||||
|
{
|
||||||
|
hideText.text = "Hide";
|
||||||
|
contentObj.SetActive(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hideText.text = "Show";
|
||||||
|
contentObj.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform controls
|
||||||
|
ConstructVector3Editor(contentObj, "Position", ControlType.position, out s_positionControl);
|
||||||
|
ConstructVector3Editor(contentObj, "Local Position", ControlType.localPosition, out s_localPosControl);
|
||||||
|
ConstructVector3Editor(contentObj, "Rotation", ControlType.eulerAngles, out s_rotationControl);
|
||||||
|
ConstructVector3Editor(contentObj, "Scale", ControlType.localScale, out s_scaleControl);
|
||||||
|
|
||||||
|
// set parent
|
||||||
|
ConstructSetParent(contentObj);
|
||||||
|
|
||||||
|
// bottom row buttons
|
||||||
|
ConstructBottomButtons(contentObj);
|
||||||
|
|
||||||
|
// set controls content inactive now that content is made (otherwise TMP font size goes way too big?)
|
||||||
|
contentObj.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructSetParent(GameObject contentObj)
|
||||||
|
{
|
||||||
|
var setParentGroupObj = UIFactory.CreateHorizontalGroup(contentObj, new Color(1, 1, 1, 0));
|
||||||
|
var setParentGroup = setParentGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
setParentGroup.childForceExpandHeight = false;
|
||||||
|
setParentGroup.childControlHeight = true;
|
||||||
|
setParentGroup.childForceExpandWidth = false;
|
||||||
|
setParentGroup.childControlWidth = true;
|
||||||
|
setParentGroup.spacing = 5;
|
||||||
|
var setParentLayout = setParentGroupObj.AddComponent<LayoutElement>();
|
||||||
|
setParentLayout.minHeight = 25;
|
||||||
|
setParentLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var setParentLabelObj = UIFactory.CreateLabel(setParentGroupObj, TextAnchor.MiddleLeft);
|
||||||
|
var setParentLabel = setParentLabelObj.GetComponent<Text>();
|
||||||
|
setParentLabel.text = "Set Parent:";
|
||||||
|
setParentLabel.color = Color.grey;
|
||||||
|
setParentLabel.fontSize = 14;
|
||||||
|
var setParentLabelLayout = setParentLabelObj.AddComponent<LayoutElement>();
|
||||||
|
setParentLabelLayout.minWidth = 110;
|
||||||
|
setParentLabelLayout.minHeight = 25;
|
||||||
|
setParentLabelLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
var setParentInputObj = UIFactory.CreateInputField(setParentGroupObj);
|
||||||
|
s_setParentInput = setParentInputObj.GetComponent<InputField>();
|
||||||
|
var placeholderInput = s_setParentInput.placeholder.GetComponent<Text>();
|
||||||
|
placeholderInput.text = "Enter a GameObject name or path...";
|
||||||
|
var setParentInputLayout = setParentInputObj.AddComponent<LayoutElement>();
|
||||||
|
setParentInputLayout.minHeight = 25;
|
||||||
|
setParentInputLayout.preferredWidth = 400;
|
||||||
|
setParentInputLayout.flexibleWidth = 9999;
|
||||||
|
|
||||||
|
var applyButtonObj = UIFactory.CreateButton(setParentGroupObj);
|
||||||
|
var applyButton = applyButtonObj.GetComponent<Button>();
|
||||||
|
applyButton.onClick.AddListener(OnSetParentClicked);
|
||||||
|
var applyText = applyButtonObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
var applyLayout = applyButtonObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minWidth = 55;
|
||||||
|
applyLayout.flexibleWidth = 0;
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.flexibleHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructVector3Editor(GameObject parent, string title, ControlType type, out ControlEditor editor)
|
||||||
|
{
|
||||||
|
editor = new ControlEditor();
|
||||||
|
|
||||||
|
var topBarObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var topGroup = topBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
topGroup.childForceExpandWidth = false;
|
||||||
|
topGroup.childControlWidth = true;
|
||||||
|
topGroup.childForceExpandHeight = false;
|
||||||
|
topGroup.childControlHeight = true;
|
||||||
|
topGroup.spacing = 5;
|
||||||
|
var topLayout = topBarObj.AddComponent<LayoutElement>();
|
||||||
|
topLayout.minHeight = 25;
|
||||||
|
topLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var titleObj = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
|
||||||
|
var titleText = titleObj.GetComponent<Text>();
|
||||||
|
titleText.text = title;
|
||||||
|
titleText.color = Color.grey;
|
||||||
|
titleText.fontSize = 14;
|
||||||
|
var titleLayout = titleObj.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minWidth = 110;
|
||||||
|
titleLayout.flexibleWidth = 0;
|
||||||
|
titleLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// expand button
|
||||||
|
var expandButtonObj = UIFactory.CreateButton(topBarObj);
|
||||||
|
var expandButton = expandButtonObj.GetComponent<Button>();
|
||||||
|
var expandText = expandButtonObj.GetComponentInChildren<Text>();
|
||||||
|
expandText.text = "▼";
|
||||||
|
expandText.fontSize = 12;
|
||||||
|
var btnLayout = expandButtonObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minWidth = 35;
|
||||||
|
btnLayout.flexibleWidth = 0;
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// readonly value input
|
||||||
|
|
||||||
|
var valueInputObj = UIFactory.CreateInputField(topBarObj);
|
||||||
|
var valueInput = valueInputObj.GetComponent<InputField>();
|
||||||
|
valueInput.readOnly = true;
|
||||||
|
//valueInput.richText = true;
|
||||||
|
//valueInput.isRichTextEditingAllowed = true;
|
||||||
|
var valueInputLayout = valueInputObj.AddComponent<LayoutElement>();
|
||||||
|
valueInputLayout.minHeight = 25;
|
||||||
|
valueInputLayout.flexibleHeight = 0;
|
||||||
|
valueInputLayout.preferredWidth = 400;
|
||||||
|
valueInputLayout.flexibleWidth = 9999;
|
||||||
|
|
||||||
|
editor.fullValue = valueInput;
|
||||||
|
|
||||||
|
editor.sliders = new Slider[3];
|
||||||
|
editor.inputs = new InputField[3];
|
||||||
|
editor.values = new Text[3];
|
||||||
|
|
||||||
|
var xRow = ConstructEditorRow(parent, editor, type, VectorValue.x);
|
||||||
|
xRow.SetActive(false);
|
||||||
|
var yRow = ConstructEditorRow(parent, editor, type, VectorValue.y);
|
||||||
|
yRow.SetActive(false);
|
||||||
|
var zRow = ConstructEditorRow(parent, editor, type, VectorValue.z);
|
||||||
|
zRow.SetActive(false);
|
||||||
|
|
||||||
|
// add expand callback now that we have group reference
|
||||||
|
expandButton.onClick.AddListener(ToggleExpand);
|
||||||
|
void ToggleExpand()
|
||||||
|
{
|
||||||
|
if (xRow.activeSelf)
|
||||||
|
{
|
||||||
|
xRow.SetActive(false);
|
||||||
|
yRow.SetActive(false);
|
||||||
|
zRow.SetActive(false);
|
||||||
|
expandText.text = "▼";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xRow.SetActive(true);
|
||||||
|
yRow.SetActive(true);
|
||||||
|
zRow.SetActive(true);
|
||||||
|
expandText.text = "▲";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal GameObject ConstructEditorRow(GameObject parent, ControlEditor editor, ControlType type, VectorValue vectorValue)
|
||||||
|
{
|
||||||
|
var rowObject = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObject.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childForceExpandWidth = false;
|
||||||
|
rowGroup.childControlWidth = true;
|
||||||
|
rowGroup.childForceExpandHeight = false;
|
||||||
|
rowGroup.childControlHeight = true;
|
||||||
|
rowGroup.spacing = 5;
|
||||||
|
var rowLayout = rowObject.AddComponent<LayoutElement>();
|
||||||
|
rowLayout.minHeight = 25;
|
||||||
|
rowLayout.flexibleHeight = 0;
|
||||||
|
rowLayout.minWidth = 100;
|
||||||
|
|
||||||
|
// Value labels
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObject, TextAnchor.MiddleLeft);
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.color = Color.cyan;
|
||||||
|
labelText.text = $"{vectorValue.ToString().ToUpper()}:";
|
||||||
|
labelText.fontSize = 14;
|
||||||
|
labelText.resizeTextMaxSize = 14;
|
||||||
|
labelText.resizeTextForBestFit = true;
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
labelLayout.flexibleHeight = 0;
|
||||||
|
labelLayout.minWidth = 25;
|
||||||
|
labelLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
// actual value label
|
||||||
|
var valueLabelObj = UIFactory.CreateLabel(rowObject, TextAnchor.MiddleLeft);
|
||||||
|
var valueLabel = valueLabelObj.GetComponent<Text>();
|
||||||
|
editor.values[(int)vectorValue] = valueLabel;
|
||||||
|
var valueLabelLayout = valueLabelObj.AddComponent<LayoutElement>();
|
||||||
|
valueLabelLayout.minWidth = 85;
|
||||||
|
valueLabelLayout.flexibleWidth = 0;
|
||||||
|
valueLabelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// input field
|
||||||
|
|
||||||
|
var inputHolder = UIFactory.CreateVerticalGroup(rowObject, new Color(1, 1, 1, 0));
|
||||||
|
var inputHolderGroup = inputHolder.GetComponent<VerticalLayoutGroup>();
|
||||||
|
inputHolderGroup.childForceExpandHeight = false;
|
||||||
|
inputHolderGroup.childControlHeight = true;
|
||||||
|
inputHolderGroup.childForceExpandWidth = false;
|
||||||
|
inputHolderGroup.childControlWidth = true;
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateInputField(inputHolder);
|
||||||
|
var input = inputObj.GetComponent<InputField>();
|
||||||
|
input.characterValidation = InputField.CharacterValidation.Decimal;
|
||||||
|
|
||||||
|
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||||
|
inputLayout.minHeight = 25;
|
||||||
|
inputLayout.flexibleHeight = 0;
|
||||||
|
inputLayout.minWidth = 90;
|
||||||
|
inputLayout.flexibleWidth = 50;
|
||||||
|
|
||||||
|
editor.inputs[(int)vectorValue] = input;
|
||||||
|
|
||||||
|
// apply button
|
||||||
|
|
||||||
|
var applyBtnObj = UIFactory.CreateButton(rowObject);
|
||||||
|
var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
applyText.fontSize = 14;
|
||||||
|
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minWidth = 60;
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
|
||||||
|
applyBtn.onClick.AddListener(() => { OnVectorControlInputApplied(type, vectorValue); });
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
|
||||||
|
var sliderObj = UIFactory.CreateSlider(rowObject);
|
||||||
|
sliderObj.transform.Find("Fill Area").gameObject.SetActive(false);
|
||||||
|
var sliderLayout = sliderObj.AddComponent<LayoutElement>();
|
||||||
|
sliderLayout.minHeight = 20;
|
||||||
|
sliderLayout.flexibleHeight = 0;
|
||||||
|
sliderLayout.minWidth = 200;
|
||||||
|
sliderLayout.flexibleWidth = 9000;
|
||||||
|
var slider = sliderObj.GetComponent<Slider>();
|
||||||
|
var sliderColors = slider.colors;
|
||||||
|
sliderColors.normalColor = new Color(0.65f, 0.65f, 0.65f);
|
||||||
|
slider.colors = sliderColors;
|
||||||
|
slider.minValue = -2;
|
||||||
|
slider.maxValue = 2;
|
||||||
|
slider.value = 0;
|
||||||
|
slider.onValueChanged.AddListener((float val) => { OnSliderControlChanged(val, slider, type, vectorValue); });
|
||||||
|
editor.sliders[(int)vectorValue] = slider;
|
||||||
|
|
||||||
|
return rowObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructBottomButtons(GameObject contentObj)
|
||||||
|
{
|
||||||
|
var bottomRow = UIFactory.CreateHorizontalGroup(contentObj, new Color(1, 1, 1, 0));
|
||||||
|
var bottomGroup = bottomRow.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
bottomGroup.childForceExpandWidth = true;
|
||||||
|
bottomGroup.childControlWidth = true;
|
||||||
|
bottomGroup.spacing = 4;
|
||||||
|
var bottomLayout = bottomRow.AddComponent<LayoutElement>();
|
||||||
|
bottomLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var instantiateBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var instantiateBtn = instantiateBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
instantiateBtn.onClick.AddListener(InstantiateBtn);
|
||||||
|
|
||||||
|
var instantiateText = instantiateBtnObj.GetComponentInChildren<Text>();
|
||||||
|
instantiateText.text = "Instantiate";
|
||||||
|
instantiateText.fontSize = 14;
|
||||||
|
var instantiateLayout = instantiateBtnObj.AddComponent<LayoutElement>();
|
||||||
|
instantiateLayout.minWidth = 150;
|
||||||
|
|
||||||
|
void InstantiateBtn()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
if (!go)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var clone = GameObject.Instantiate(go);
|
||||||
|
InspectorManager.Instance.Inspect(clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dontDestroyBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var dontDestroyBtn = dontDestroyBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
dontDestroyBtn.onClick.AddListener(DontDestroyOnLoadBtn);
|
||||||
|
|
||||||
|
var dontDestroyText = dontDestroyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
dontDestroyText.text = "Set DontDestroyOnLoad";
|
||||||
|
dontDestroyText.fontSize = 14;
|
||||||
|
var dontDestroyLayout = dontDestroyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
dontDestroyLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
void DontDestroyOnLoadBtn()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
if (!go)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GameObject.DontDestroyOnLoad(go);
|
||||||
|
}
|
||||||
|
|
||||||
|
var destroyBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var destroyBtn = destroyBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
destroyBtn.onClick.AddListener(DestroyBtn);
|
||||||
|
|
||||||
|
var destroyText = destroyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
destroyText.text = "Destroy";
|
||||||
|
destroyText.fontSize = 14;
|
||||||
|
destroyText.color = Color.red;
|
||||||
|
var destroyLayout = destroyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
destroyLayout.minWidth = 150;
|
||||||
|
|
||||||
|
void DestroyBtn()
|
||||||
|
{
|
||||||
|
var go = GameObjectInspector.ActiveInstance.TargetGO;
|
||||||
|
if (!go)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GameObject.Destroy(go);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
444
src/Inspectors/GameObjects/GameObjectInspector.cs
Normal file
444
src/Inspectors/GameObjects/GameObjectInspector.cs
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
//using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Inspectors.GameObjects;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors
|
||||||
|
{
|
||||||
|
public class GameObjectInspector : InspectorBase
|
||||||
|
{
|
||||||
|
public override string TabLabel => $" <color=cyan>[G]</color> {TargetGO?.name}";
|
||||||
|
|
||||||
|
public static GameObjectInspector ActiveInstance { get; private set; }
|
||||||
|
|
||||||
|
public GameObject TargetGO;
|
||||||
|
|
||||||
|
// sub modules
|
||||||
|
private static ChildList s_childList;
|
||||||
|
private static ComponentList s_compList;
|
||||||
|
private static GameObjectControls s_controls;
|
||||||
|
|
||||||
|
// static UI elements (only constructed once)
|
||||||
|
|
||||||
|
private static bool m_UIConstructed;
|
||||||
|
|
||||||
|
private static GameObject s_content;
|
||||||
|
public override GameObject Content
|
||||||
|
{
|
||||||
|
get => s_content;
|
||||||
|
set => s_content = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string m_lastName;
|
||||||
|
public static InputField m_nameInput;
|
||||||
|
|
||||||
|
private static string m_lastPath;
|
||||||
|
public static InputField m_pathInput;
|
||||||
|
private static RectTransform m_pathInputRect;
|
||||||
|
private static GameObject m_pathGroupObj;
|
||||||
|
private static Text m_hiddenPathText;
|
||||||
|
private static RectTransform m_hiddenPathRect;
|
||||||
|
|
||||||
|
private static Toggle m_enabledToggle;
|
||||||
|
private static Text m_enabledText;
|
||||||
|
private static bool? m_lastEnabledState;
|
||||||
|
|
||||||
|
private static Dropdown m_layerDropdown;
|
||||||
|
private static int m_lastLayer = -1;
|
||||||
|
|
||||||
|
private static Text m_sceneText;
|
||||||
|
private static string m_lastScene;
|
||||||
|
|
||||||
|
public GameObjectInspector(GameObject target) : base(target)
|
||||||
|
{
|
||||||
|
ActiveInstance = this;
|
||||||
|
|
||||||
|
TargetGO = target;
|
||||||
|
|
||||||
|
if (!TargetGO)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Target GameObject is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// one UI is used for all gameobject inspectors. no point recreating it.
|
||||||
|
if (!m_UIConstructed)
|
||||||
|
{
|
||||||
|
m_UIConstructed = true;
|
||||||
|
|
||||||
|
s_childList = new ChildList();
|
||||||
|
s_compList = new ComponentList();
|
||||||
|
s_controls = new GameObjectControls();
|
||||||
|
|
||||||
|
ConstructUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetActive()
|
||||||
|
{
|
||||||
|
base.SetActive();
|
||||||
|
ActiveInstance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetInactive()
|
||||||
|
{
|
||||||
|
base.SetInactive();
|
||||||
|
ActiveInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ChangeInspectorTarget(GameObject newTarget)
|
||||||
|
{
|
||||||
|
if (!newTarget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.Target = this.TargetGO = newTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (m_pendingDestroy || !this.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RefreshTopInfo();
|
||||||
|
|
||||||
|
s_childList.RefreshChildObjectList();
|
||||||
|
|
||||||
|
s_compList.RefreshComponentList();
|
||||||
|
|
||||||
|
s_controls.RefreshControls();
|
||||||
|
|
||||||
|
if (GameObjectControls.s_sliderChangedWanted)
|
||||||
|
GameObjectControls.UpdateSliderControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshTopInfo()
|
||||||
|
{
|
||||||
|
if (m_lastName != TargetGO.name)
|
||||||
|
{
|
||||||
|
m_lastName = TargetGO.name;
|
||||||
|
m_nameInput.text = m_lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TargetGO.transform.parent)
|
||||||
|
{
|
||||||
|
if (!m_pathGroupObj.activeSelf)
|
||||||
|
m_pathGroupObj.SetActive(true);
|
||||||
|
|
||||||
|
var path = TargetGO.transform.GetTransformPath(true);
|
||||||
|
if (m_lastPath != path)
|
||||||
|
{
|
||||||
|
m_lastPath = path;
|
||||||
|
|
||||||
|
m_pathInput.text = path;
|
||||||
|
m_hiddenPathText.text = path;
|
||||||
|
|
||||||
|
LayoutRebuilder.ForceRebuildLayoutImmediate(m_pathInputRect);
|
||||||
|
LayoutRebuilder.ForceRebuildLayoutImmediate(m_hiddenPathRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_pathGroupObj.activeSelf)
|
||||||
|
m_pathGroupObj.SetActive(false);
|
||||||
|
|
||||||
|
if (m_lastEnabledState != TargetGO.activeSelf)
|
||||||
|
{
|
||||||
|
m_lastEnabledState = TargetGO.activeSelf;
|
||||||
|
|
||||||
|
m_enabledToggle.isOn = TargetGO.activeSelf;
|
||||||
|
m_enabledText.text = TargetGO.activeSelf ? "Enabled" : "Disabled";
|
||||||
|
m_enabledText.color = TargetGO.activeSelf ? Color.green : Color.red;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lastLayer != TargetGO.layer)
|
||||||
|
{
|
||||||
|
m_lastLayer = TargetGO.layer;
|
||||||
|
m_layerDropdown.value = TargetGO.layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_lastScene != TargetGO.scene.name)
|
||||||
|
{
|
||||||
|
m_lastScene = TargetGO.scene.name;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(TargetGO.scene.name))
|
||||||
|
m_sceneText.text = m_lastScene;
|
||||||
|
else
|
||||||
|
m_sceneText.text = "None (Asset/Resource)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Callbacks
|
||||||
|
|
||||||
|
private static void OnApplyNameClicked()
|
||||||
|
{
|
||||||
|
if (ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActiveInstance.TargetGO.name = m_nameInput.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnEnableToggled(bool enabled)
|
||||||
|
{
|
||||||
|
if (ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActiveInstance.TargetGO.SetActive(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnLayerSelected(int layer)
|
||||||
|
{
|
||||||
|
if (ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActiveInstance.TargetGO.layer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnBackButtonClicked()
|
||||||
|
{
|
||||||
|
if (ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActiveInstance.ChangeInspectorTarget(ActiveInstance.TargetGO.transform.parent.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
private void ConstructUI()
|
||||||
|
{
|
||||||
|
var parent = InspectorManager.Instance.m_inspectorContent;
|
||||||
|
|
||||||
|
s_content = UIFactory.CreateScrollView(parent, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
|
||||||
|
var scrollGroup = scrollContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
scrollGroup.childForceExpandHeight = true;
|
||||||
|
scrollGroup.childControlHeight = true;
|
||||||
|
scrollGroup.spacing = 5;
|
||||||
|
|
||||||
|
ConstructTopArea(scrollContent);
|
||||||
|
|
||||||
|
s_controls.ConstructControls(scrollContent);
|
||||||
|
|
||||||
|
var midGroupObj = ConstructMidGroup(scrollContent);
|
||||||
|
|
||||||
|
s_childList.ConstructChildList(midGroupObj);
|
||||||
|
s_compList.ConstructCompList(midGroupObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConstructTopArea(GameObject scrollContent)
|
||||||
|
{
|
||||||
|
// path row
|
||||||
|
|
||||||
|
m_pathGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var pathGroup = m_pathGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
pathGroup.childForceExpandHeight = false;
|
||||||
|
pathGroup.childForceExpandWidth = false;
|
||||||
|
pathGroup.childControlHeight = false;
|
||||||
|
pathGroup.childControlWidth = true;
|
||||||
|
pathGroup.spacing = 5;
|
||||||
|
var pathRect = m_pathGroupObj.GetComponent<RectTransform>();
|
||||||
|
pathRect.sizeDelta = new Vector2(pathRect.sizeDelta.x, 20);
|
||||||
|
var pathLayout = m_pathGroupObj.AddComponent<LayoutElement>();
|
||||||
|
pathLayout.minHeight = 20;
|
||||||
|
pathLayout.flexibleHeight = 75;
|
||||||
|
|
||||||
|
var backButtonObj = UIFactory.CreateButton(m_pathGroupObj);
|
||||||
|
var backButton = backButtonObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
backButton.onClick.AddListener(OnBackButtonClicked);
|
||||||
|
|
||||||
|
var backColors = backButton.colors;
|
||||||
|
backColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
|
||||||
|
backButton.colors = backColors;
|
||||||
|
var backText = backButtonObj.GetComponentInChildren<Text>();
|
||||||
|
backText.text = "◄";
|
||||||
|
var backLayout = backButtonObj.AddComponent<LayoutElement>();
|
||||||
|
backLayout.minWidth = 55;
|
||||||
|
backLayout.flexibleWidth = 0;
|
||||||
|
backLayout.minHeight = 25;
|
||||||
|
backLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var pathHiddenTextObj = UIFactory.CreateLabel(m_pathGroupObj, TextAnchor.MiddleLeft);
|
||||||
|
m_hiddenPathText = pathHiddenTextObj.GetComponent<Text>();
|
||||||
|
m_hiddenPathText.color = Color.clear;
|
||||||
|
m_hiddenPathText.fontSize = 14;
|
||||||
|
//m_hiddenPathText.lineSpacing = 1.5f;
|
||||||
|
m_hiddenPathText.raycastTarget = false;
|
||||||
|
var hiddenFitter = pathHiddenTextObj.AddComponent<ContentSizeFitter>();
|
||||||
|
hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
var hiddenLayout = pathHiddenTextObj.AddComponent<LayoutElement>();
|
||||||
|
hiddenLayout.minHeight = 25;
|
||||||
|
hiddenLayout.flexibleHeight = 125;
|
||||||
|
hiddenLayout.minWidth = 250;
|
||||||
|
hiddenLayout.flexibleWidth = 9000;
|
||||||
|
var hiddenGroup = pathHiddenTextObj.AddComponent<HorizontalLayoutGroup>();
|
||||||
|
hiddenGroup.childForceExpandWidth = true;
|
||||||
|
hiddenGroup.childControlWidth = true;
|
||||||
|
hiddenGroup.childForceExpandHeight = true;
|
||||||
|
hiddenGroup.childControlHeight = true;
|
||||||
|
|
||||||
|
var pathInputObj = UIFactory.CreateInputField(pathHiddenTextObj);
|
||||||
|
var pathInputRect = pathInputObj.GetComponent<RectTransform>();
|
||||||
|
pathInputRect.sizeDelta = new Vector2(pathInputRect.sizeDelta.x, 25);
|
||||||
|
m_pathInput = pathInputObj.GetComponent<InputField>();
|
||||||
|
m_pathInput.text = TargetGO.transform.GetTransformPath();
|
||||||
|
m_pathInput.readOnly = true;
|
||||||
|
m_pathInput.lineType = InputField.LineType.MultiLineNewline;
|
||||||
|
var pathInputLayout = pathInputObj.AddComponent<LayoutElement>();
|
||||||
|
pathInputLayout.minHeight = 25;
|
||||||
|
pathInputLayout.flexibleHeight = 75;
|
||||||
|
pathInputLayout.preferredWidth = 400;
|
||||||
|
pathInputLayout.flexibleWidth = 9999;
|
||||||
|
var textRect = m_pathInput.textComponent.GetComponent<RectTransform>();
|
||||||
|
textRect.offsetMin = new Vector2(3, 3);
|
||||||
|
textRect.offsetMax = new Vector2(3, 3);
|
||||||
|
m_pathInput.textComponent.color = new Color(0.75f, 0.75f, 0.75f);
|
||||||
|
//m_pathInput.textComponent.lineSpacing = 1.5f;
|
||||||
|
|
||||||
|
m_pathInputRect = m_pathInput.GetComponent<RectTransform>();
|
||||||
|
m_hiddenPathRect = m_hiddenPathText.GetComponent<RectTransform>();
|
||||||
|
|
||||||
|
// name row
|
||||||
|
|
||||||
|
var nameRowObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var nameGroup = nameRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
nameGroup.childForceExpandHeight = false;
|
||||||
|
nameGroup.childForceExpandWidth = false;
|
||||||
|
nameGroup.childControlHeight = true;
|
||||||
|
nameGroup.childControlWidth = true;
|
||||||
|
nameGroup.spacing = 5;
|
||||||
|
var nameRect = nameRowObj.GetComponent<RectTransform>();
|
||||||
|
nameRect.sizeDelta = new Vector2(nameRect.sizeDelta.x, 25);
|
||||||
|
var nameLayout = nameRowObj.AddComponent<LayoutElement>();
|
||||||
|
nameLayout.minHeight = 25;
|
||||||
|
nameLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var nameTextObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleCenter);
|
||||||
|
var nameTextText = nameTextObj.GetComponent<Text>();
|
||||||
|
nameTextText.text = "Name:";
|
||||||
|
nameTextText.fontSize = 14;
|
||||||
|
nameTextText.color = Color.grey;
|
||||||
|
var nameTextLayout = nameTextObj.AddComponent<LayoutElement>();
|
||||||
|
nameTextLayout.minHeight = 25;
|
||||||
|
nameTextLayout.flexibleHeight = 0;
|
||||||
|
nameTextLayout.minWidth = 55;
|
||||||
|
nameTextLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
var nameInputObj = UIFactory.CreateInputField(nameRowObj);
|
||||||
|
var nameInputRect = nameInputObj.GetComponent<RectTransform>();
|
||||||
|
nameInputRect.sizeDelta = new Vector2(nameInputRect.sizeDelta.x, 25);
|
||||||
|
m_nameInput = nameInputObj.GetComponent<InputField>();
|
||||||
|
m_nameInput.text = TargetGO.name;
|
||||||
|
|
||||||
|
var applyNameBtnObj = UIFactory.CreateButton(nameRowObj);
|
||||||
|
var applyNameBtn = applyNameBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
applyNameBtn.onClick.AddListener(OnApplyNameClicked);
|
||||||
|
|
||||||
|
var applyNameText = applyNameBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyNameText.text = "Apply";
|
||||||
|
applyNameText.fontSize = 14;
|
||||||
|
var applyNameLayout = applyNameBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyNameLayout.minWidth = 65;
|
||||||
|
applyNameLayout.minHeight = 25;
|
||||||
|
applyNameLayout.flexibleHeight = 0;
|
||||||
|
var applyNameRect = applyNameBtnObj.GetComponent<RectTransform>();
|
||||||
|
applyNameRect.sizeDelta = new Vector2(applyNameRect.sizeDelta.x, 25);
|
||||||
|
|
||||||
|
var activeLabel = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleCenter);
|
||||||
|
var activeLabelLayout = activeLabel.AddComponent<LayoutElement>();
|
||||||
|
activeLabelLayout.minWidth = 55;
|
||||||
|
activeLabelLayout.minHeight = 25;
|
||||||
|
var activeText = activeLabel.GetComponent<Text>();
|
||||||
|
activeText.text = "Active:";
|
||||||
|
activeText.color = Color.grey;
|
||||||
|
activeText.fontSize = 14;
|
||||||
|
|
||||||
|
var enabledToggleObj = UIFactory.CreateToggle(nameRowObj, out m_enabledToggle, out m_enabledText);
|
||||||
|
var toggleLayout = enabledToggleObj.AddComponent<LayoutElement>();
|
||||||
|
toggleLayout.minHeight = 25;
|
||||||
|
toggleLayout.minWidth = 100;
|
||||||
|
toggleLayout.flexibleWidth = 0;
|
||||||
|
m_enabledText.text = "Enabled";
|
||||||
|
m_enabledText.color = Color.green;
|
||||||
|
|
||||||
|
m_enabledToggle.onValueChanged.AddListener(OnEnableToggled);
|
||||||
|
|
||||||
|
// layer and scene row
|
||||||
|
|
||||||
|
var sceneLayerRow = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var sceneLayerGroup = sceneLayerRow.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
sceneLayerGroup.childForceExpandWidth = false;
|
||||||
|
sceneLayerGroup.childControlWidth = true;
|
||||||
|
sceneLayerGroup.spacing = 5;
|
||||||
|
|
||||||
|
var layerLabel = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleCenter);
|
||||||
|
var layerText = layerLabel.GetComponent<Text>();
|
||||||
|
layerText.text = "Layer:";
|
||||||
|
layerText.fontSize = 14;
|
||||||
|
layerText.color = Color.grey;
|
||||||
|
var layerTextLayout = layerLabel.AddComponent<LayoutElement>();
|
||||||
|
layerTextLayout.minWidth = 55;
|
||||||
|
layerTextLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
var layerDropdownObj = UIFactory.CreateDropdown(sceneLayerRow, out m_layerDropdown);
|
||||||
|
m_layerDropdown.options.Clear();
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
var layer = LayerMaskUnstrip.LayerToName(i);
|
||||||
|
m_layerDropdown.options.Add(new Dropdown.OptionData { text = $"{i}: {layer}" });
|
||||||
|
}
|
||||||
|
//var itemText = layerDropdownObj.transform.Find("Label").GetComponent<Text>();
|
||||||
|
//itemText.resizeTextForBestFit = true;
|
||||||
|
var layerDropdownLayout = layerDropdownObj.AddComponent<LayoutElement>();
|
||||||
|
layerDropdownLayout.minWidth = 120;
|
||||||
|
layerDropdownLayout.flexibleWidth = 2000;
|
||||||
|
layerDropdownLayout.minHeight = 25;
|
||||||
|
|
||||||
|
m_layerDropdown.onValueChanged.AddListener(OnLayerSelected);
|
||||||
|
|
||||||
|
var scenelabelObj = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleCenter);
|
||||||
|
var sceneLabel = scenelabelObj.GetComponent<Text>();
|
||||||
|
sceneLabel.text = "Scene:";
|
||||||
|
sceneLabel.color = Color.grey;
|
||||||
|
sceneLabel.fontSize = 14;
|
||||||
|
var sceneLabelLayout = scenelabelObj.AddComponent<LayoutElement>();
|
||||||
|
sceneLabelLayout.minWidth = 55;
|
||||||
|
sceneLabelLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
var objectSceneText = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleLeft);
|
||||||
|
m_sceneText = objectSceneText.GetComponent<Text>();
|
||||||
|
m_sceneText.fontSize = 14;
|
||||||
|
m_sceneText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
var sceneTextLayout = objectSceneText.AddComponent<LayoutElement>();
|
||||||
|
sceneTextLayout.minWidth = 120;
|
||||||
|
sceneTextLayout.flexibleWidth = 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameObject ConstructMidGroup(GameObject parent)
|
||||||
|
{
|
||||||
|
var midGroupObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var midGroup = midGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
midGroup.spacing = 5;
|
||||||
|
midGroup.childForceExpandWidth = true;
|
||||||
|
midGroup.childControlWidth = true;
|
||||||
|
midGroup.childForceExpandHeight = true;
|
||||||
|
midGroup.childControlHeight = true;
|
||||||
|
var midlayout = midGroupObj.AddComponent<LayoutElement>();
|
||||||
|
midlayout.minHeight = 350;
|
||||||
|
midlayout.flexibleHeight = 10000;
|
||||||
|
midlayout.minWidth = 200;
|
||||||
|
midlayout.flexibleWidth = 25000;
|
||||||
|
|
||||||
|
return midGroupObj;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
138
src/Inspectors/InspectorBase.cs
Normal file
138
src/Inspectors/InspectorBase.cs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors
|
||||||
|
{
|
||||||
|
public abstract class InspectorBase
|
||||||
|
{
|
||||||
|
public object Target;
|
||||||
|
|
||||||
|
public abstract string TabLabel { get; }
|
||||||
|
|
||||||
|
public bool IsActive { get; private set; }
|
||||||
|
public abstract GameObject Content { get; set; }
|
||||||
|
public Button tabButton;
|
||||||
|
public Text tabText;
|
||||||
|
|
||||||
|
internal bool m_pendingDestroy;
|
||||||
|
|
||||||
|
public InspectorBase(object target)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
|
||||||
|
if (Target.IsNullOrDestroyed(false))
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddInspectorTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetActive()
|
||||||
|
{
|
||||||
|
this.IsActive = true;
|
||||||
|
Content?.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetInactive()
|
||||||
|
{
|
||||||
|
this.IsActive = false;
|
||||||
|
Content?.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update()
|
||||||
|
{
|
||||||
|
if (Target.IsNullOrDestroyed(false))
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabText.text = TabLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destroy()
|
||||||
|
{
|
||||||
|
m_pendingDestroy = true;
|
||||||
|
|
||||||
|
GameObject tabGroup = tabButton?.transform.parent.gameObject;
|
||||||
|
|
||||||
|
if (tabGroup)
|
||||||
|
{
|
||||||
|
GameObject.Destroy(tabGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
int thisIndex = -1;
|
||||||
|
if (InspectorManager.Instance.m_currentInspectors.Contains(this))
|
||||||
|
{
|
||||||
|
thisIndex = InspectorManager.Instance.m_currentInspectors.IndexOf(this);
|
||||||
|
InspectorManager.Instance.m_currentInspectors.Remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(InspectorManager.Instance.m_activeInspector, this))
|
||||||
|
{
|
||||||
|
InspectorManager.Instance.UnsetInspectorTab();
|
||||||
|
|
||||||
|
if (InspectorManager.Instance.m_currentInspectors.Count > 0)
|
||||||
|
{
|
||||||
|
var prevTab = InspectorManager.Instance.m_currentInspectors[thisIndex > 0 ? thisIndex - 1 : 0];
|
||||||
|
InspectorManager.Instance.SetInspectorTab(prevTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
public void AddInspectorTab()
|
||||||
|
{
|
||||||
|
var tabContent = InspectorManager.Instance.m_tabBarContent;
|
||||||
|
|
||||||
|
var tabGroupObj = UIFactory.CreateHorizontalGroup(tabContent);
|
||||||
|
var tabGroup = tabGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
tabGroup.childForceExpandWidth = true;
|
||||||
|
tabGroup.childControlWidth = true;
|
||||||
|
var tabLayout = tabGroupObj.AddComponent<LayoutElement>();
|
||||||
|
tabLayout.minWidth = 185;
|
||||||
|
tabLayout.flexibleWidth = 0;
|
||||||
|
tabGroupObj.AddComponent<Mask>();
|
||||||
|
|
||||||
|
var targetButtonObj = UIFactory.CreateButton(tabGroupObj);
|
||||||
|
targetButtonObj.AddComponent<Mask>();
|
||||||
|
var targetButtonLayout = targetButtonObj.AddComponent<LayoutElement>();
|
||||||
|
targetButtonLayout.minWidth = 165;
|
||||||
|
targetButtonLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
tabText = targetButtonObj.GetComponentInChildren<Text>();
|
||||||
|
tabText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
tabText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
|
||||||
|
tabButton = targetButtonObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
tabButton.onClick.AddListener(() => { InspectorManager.Instance.SetInspectorTab(this); });
|
||||||
|
|
||||||
|
var closeBtnObj = UIFactory.CreateButton(tabGroupObj);
|
||||||
|
var closeBtnLayout = closeBtnObj.AddComponent<LayoutElement>();
|
||||||
|
closeBtnLayout.minWidth = 20;
|
||||||
|
closeBtnLayout.flexibleWidth = 0;
|
||||||
|
var closeBtnText = closeBtnObj.GetComponentInChildren<Text>();
|
||||||
|
closeBtnText.text = "X";
|
||||||
|
closeBtnText.color = new Color(1, 0, 0, 1);
|
||||||
|
|
||||||
|
var closeBtn = closeBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
closeBtn.onClick.AddListener(Destroy);
|
||||||
|
|
||||||
|
var closeColors = closeBtn.colors;
|
||||||
|
closeColors.normalColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
closeBtn.colors = closeColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
335
src/Inspectors/InspectorManager.cs
Normal file
335
src/Inspectors/InspectorManager.cs
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Inspectors.Reflection;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors
|
||||||
|
{
|
||||||
|
public class InspectorManager
|
||||||
|
{
|
||||||
|
public static InspectorManager Instance { get; private set; }
|
||||||
|
|
||||||
|
public InspectorManager()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
ConstructInspectorPane();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InspectorBase m_activeInspector;
|
||||||
|
public readonly List<InspectorBase> m_currentInspectors = new List<InspectorBase>();
|
||||||
|
|
||||||
|
public GameObject m_tabBarContent;
|
||||||
|
public GameObject m_inspectorContent;
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_currentInspectors.Count; i++)
|
||||||
|
{
|
||||||
|
if (i >= m_currentInspectors.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
m_currentInspectors[i].Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Inspect(object obj)
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
obj = obj.Il2CppCast(ReflectionHelpers.GetActualType(obj));
|
||||||
|
#endif
|
||||||
|
UnityEngine.Object unityObj = obj as UnityEngine.Object;
|
||||||
|
|
||||||
|
if (obj.IsNullOrDestroyed(false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if currently inspecting this object
|
||||||
|
foreach (InspectorBase tab in m_currentInspectors)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(obj, tab.Target))
|
||||||
|
{
|
||||||
|
SetInspectorTab(tab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#if CPP
|
||||||
|
else if (unityObj && tab.Target is UnityEngine.Object uTabObj)
|
||||||
|
{
|
||||||
|
if (unityObj.m_CachedPtr == uTabObj.m_CachedPtr)
|
||||||
|
{
|
||||||
|
SetInspectorTab(tab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
InspectorBase inspector;
|
||||||
|
if (obj is GameObject go)
|
||||||
|
inspector = new GameObjectInspector(go);
|
||||||
|
else
|
||||||
|
inspector = new InstanceInspector(obj);
|
||||||
|
|
||||||
|
m_currentInspectors.Add(inspector);
|
||||||
|
SetInspectorTab(inspector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Inspect(Type type)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("The provided type was null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tab in m_currentInspectors.Where(x => x is StaticInspector))
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(tab.Target as Type, type))
|
||||||
|
{
|
||||||
|
SetInspectorTab(tab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inspector = new StaticInspector(type);
|
||||||
|
|
||||||
|
m_currentInspectors.Add(inspector);
|
||||||
|
SetInspectorTab(inspector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInspectorTab(InspectorBase inspector)
|
||||||
|
{
|
||||||
|
MainMenu.Instance.SetPage(HomePage.Instance);
|
||||||
|
|
||||||
|
if (m_activeInspector == inspector)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UnsetInspectorTab();
|
||||||
|
|
||||||
|
m_activeInspector = inspector;
|
||||||
|
inspector.SetActive();
|
||||||
|
|
||||||
|
Color activeColor = new Color(0, 0.25f, 0, 1);
|
||||||
|
ColorBlock colors = inspector.tabButton.colors;
|
||||||
|
colors.normalColor = activeColor;
|
||||||
|
colors.highlightedColor = activeColor;
|
||||||
|
inspector.tabButton.colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsetInspectorTab()
|
||||||
|
{
|
||||||
|
if (m_activeInspector == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_activeInspector.SetInactive();
|
||||||
|
|
||||||
|
ColorBlock colors = m_activeInspector.tabButton.colors;
|
||||||
|
colors.normalColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
colors.highlightedColor = new Color(0.1f, 0.3f, 0.1f, 1);
|
||||||
|
m_activeInspector.tabButton.colors = colors;
|
||||||
|
|
||||||
|
m_activeInspector = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region INSPECTOR PANE
|
||||||
|
|
||||||
|
public void ConstructInspectorPane()
|
||||||
|
{
|
||||||
|
var mainObj = UIFactory.CreateVerticalGroup(HomePage.Instance.Content, new Color(72f / 255f, 72f / 255f, 72f / 255f));
|
||||||
|
LayoutElement mainLayout = mainObj.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.preferredHeight = 400;
|
||||||
|
mainLayout.flexibleHeight = 9000;
|
||||||
|
mainLayout.preferredWidth = 620;
|
||||||
|
mainLayout.flexibleWidth = 9000;
|
||||||
|
|
||||||
|
var mainGroup = mainObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.spacing = 4;
|
||||||
|
mainGroup.padding.left = 4;
|
||||||
|
mainGroup.padding.right = 4;
|
||||||
|
mainGroup.padding.top = 4;
|
||||||
|
mainGroup.padding.bottom = 4;
|
||||||
|
|
||||||
|
var topRowObj = UIFactory.CreateHorizontalGroup(mainObj, new Color(1, 1, 1, 0));
|
||||||
|
var topRowGroup = topRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
topRowGroup.childForceExpandWidth = false;
|
||||||
|
topRowGroup.childControlWidth = true;
|
||||||
|
topRowGroup.childForceExpandHeight = true;
|
||||||
|
topRowGroup.childControlHeight = true;
|
||||||
|
topRowGroup.spacing = 15;
|
||||||
|
|
||||||
|
var inspectorTitle = UIFactory.CreateLabel(topRowObj, TextAnchor.MiddleLeft);
|
||||||
|
Text title = inspectorTitle.GetComponent<Text>();
|
||||||
|
title.text = "Inspector";
|
||||||
|
title.fontSize = 20;
|
||||||
|
var titleLayout = inspectorTitle.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minHeight = 30;
|
||||||
|
titleLayout.flexibleHeight = 0;
|
||||||
|
titleLayout.minWidth = 90;
|
||||||
|
titleLayout.flexibleWidth = 20000;
|
||||||
|
|
||||||
|
ConstructToolbar(topRowObj);
|
||||||
|
|
||||||
|
// inspector tab bar
|
||||||
|
|
||||||
|
m_tabBarContent = UIFactory.CreateGridGroup(mainObj, new Vector2(185, 20), new Vector2(5, 2), new Color(0.1f, 0.1f, 0.1f, 1));
|
||||||
|
|
||||||
|
var gridGroup = m_tabBarContent.GetComponent<GridLayoutGroup>();
|
||||||
|
gridGroup.padding.top = 3;
|
||||||
|
gridGroup.padding.left = 3;
|
||||||
|
gridGroup.padding.right = 3;
|
||||||
|
gridGroup.padding.bottom = 3;
|
||||||
|
|
||||||
|
// inspector content area
|
||||||
|
|
||||||
|
m_inspectorContent = UIFactory.CreateVerticalGroup(mainObj, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var inspectorGroup = m_inspectorContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
inspectorGroup.childForceExpandHeight = true;
|
||||||
|
inspectorGroup.childForceExpandWidth = true;
|
||||||
|
inspectorGroup.childControlHeight = true;
|
||||||
|
inspectorGroup.childControlWidth = true;
|
||||||
|
|
||||||
|
m_inspectorContent = UIFactory.CreateVerticalGroup(mainObj, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var contentGroup = m_inspectorContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
contentGroup.childForceExpandHeight = true;
|
||||||
|
contentGroup.childForceExpandWidth = true;
|
||||||
|
contentGroup.childControlHeight = true;
|
||||||
|
contentGroup.childControlWidth = true;
|
||||||
|
contentGroup.padding.top = 2;
|
||||||
|
contentGroup.padding.left = 2;
|
||||||
|
contentGroup.padding.right = 2;
|
||||||
|
contentGroup.padding.bottom = 2;
|
||||||
|
|
||||||
|
var contentLayout = m_inspectorContent.AddComponent<LayoutElement>();
|
||||||
|
contentLayout.preferredHeight = 900;
|
||||||
|
contentLayout.flexibleHeight = 10000;
|
||||||
|
contentLayout.preferredWidth = 600;
|
||||||
|
contentLayout.flexibleWidth = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConstructToolbar(GameObject topRowObj)
|
||||||
|
{
|
||||||
|
var invisObj = UIFactory.CreateHorizontalGroup(topRowObj, new Color(1, 1, 1, 0));
|
||||||
|
var invisGroup = invisObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
invisGroup.childForceExpandWidth = false;
|
||||||
|
invisGroup.childForceExpandHeight = false;
|
||||||
|
invisGroup.childControlWidth = true;
|
||||||
|
invisGroup.childControlHeight = true;
|
||||||
|
invisGroup.padding.top = 2;
|
||||||
|
invisGroup.padding.bottom = 2;
|
||||||
|
invisGroup.padding.left = 2;
|
||||||
|
invisGroup.padding.right = 2;
|
||||||
|
invisGroup.spacing = 10;
|
||||||
|
|
||||||
|
// // time scale group
|
||||||
|
|
||||||
|
// var timeGroupObj = UIFactory.CreateHorizontalGroup(invisObj, new Color(1, 1, 1, 0));
|
||||||
|
// var timeGroup = timeGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
// timeGroup.childForceExpandWidth = false;
|
||||||
|
// timeGroup.childControlWidth = true;
|
||||||
|
// timeGroup.childForceExpandHeight = false;
|
||||||
|
// timeGroup.childControlHeight = true;
|
||||||
|
// timeGroup.padding.top = 2;
|
||||||
|
// timeGroup.padding.left = 5;
|
||||||
|
// timeGroup.padding.right = 2;
|
||||||
|
// timeGroup.padding.bottom = 2;
|
||||||
|
// timeGroup.spacing = 5;
|
||||||
|
// timeGroup.childAlignment = TextAnchor.MiddleCenter;
|
||||||
|
// var timeGroupLayout = timeGroupObj.AddComponent<LayoutElement>();
|
||||||
|
// timeGroupLayout.minWidth = 100;
|
||||||
|
// timeGroupLayout.flexibleWidth = 300;
|
||||||
|
// timeGroupLayout.minHeight = 25;
|
||||||
|
// timeGroupLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// // time scale title
|
||||||
|
|
||||||
|
// var timeTitleObj = UIFactory.CreateLabel(timeGroupObj, TextAnchor.MiddleLeft);
|
||||||
|
// var timeTitle = timeTitleObj.GetComponent<Text>();
|
||||||
|
// timeTitle.text = "Time Scale:";
|
||||||
|
// timeTitle.color = new Color(21f / 255f, 192f / 255f, 235f / 255f);
|
||||||
|
// var titleLayout = timeTitleObj.AddComponent<LayoutElement>();
|
||||||
|
// titleLayout.minHeight = 25;
|
||||||
|
// titleLayout.minWidth = 80;
|
||||||
|
// titleLayout.flexibleHeight = 0;
|
||||||
|
// timeTitle.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
|
||||||
|
// // actual active time label
|
||||||
|
|
||||||
|
// var timeLabelObj = UIFactory.CreateLabel(timeGroupObj, TextAnchor.MiddleLeft);
|
||||||
|
// var timeLabelLayout = timeLabelObj.AddComponent<LayoutElement>();
|
||||||
|
// timeLabelLayout.minWidth = 40;
|
||||||
|
// timeLabelLayout.minHeight = 25;
|
||||||
|
// timeLabelLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// // todo make static and update
|
||||||
|
// var s_timeText = timeLabelObj.GetComponent<Text>();
|
||||||
|
// s_timeText.text = Time.timeScale.ToString("F1");
|
||||||
|
|
||||||
|
// // time scale input
|
||||||
|
|
||||||
|
// var timeInputObj = UIFactory.CreateInputField(timeGroupObj);
|
||||||
|
// var timeInput = timeInputObj.GetComponent<InputField>();
|
||||||
|
// timeInput.characterValidation = InputField.CharacterValidation.Decimal;
|
||||||
|
// var timeInputLayout = timeInputObj.AddComponent<LayoutElement>();
|
||||||
|
// timeInputLayout.minWidth = 90;
|
||||||
|
// timeInputLayout.flexibleWidth = 0;
|
||||||
|
// timeInputLayout.minHeight = 25;
|
||||||
|
// timeInputLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// // time scale apply button
|
||||||
|
|
||||||
|
// var applyBtnObj = UIFactory.CreateButton(timeGroupObj);
|
||||||
|
// var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
// applyBtn.onClick.AddListener(SetTimeScale);
|
||||||
|
|
||||||
|
// var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
// applyText.text = "Apply";
|
||||||
|
// applyText.fontSize = 14;
|
||||||
|
// var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
// applyLayout.minWidth = 50;
|
||||||
|
// applyLayout.minHeight = 25;
|
||||||
|
// applyLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// void SetTimeScale()
|
||||||
|
// {
|
||||||
|
// var scale = float.Parse(timeInput.text);
|
||||||
|
// Time.timeScale = scale;
|
||||||
|
// s_timeText.text = Time.timeScale.ToString("F1");
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// inspect under mouse button
|
||||||
|
|
||||||
|
var inspectObj = UIFactory.CreateButton(topRowObj);
|
||||||
|
var inspectLayout = inspectObj.AddComponent<LayoutElement>();
|
||||||
|
inspectLayout.minWidth = 120;
|
||||||
|
inspectLayout.flexibleWidth = 0;
|
||||||
|
var inspectBtn = inspectObj.GetComponent<Button>();
|
||||||
|
var inspectColors = inspectBtn.colors;
|
||||||
|
inspectColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
|
||||||
|
inspectBtn.colors = inspectColors;
|
||||||
|
var inspectText = inspectObj.GetComponentInChildren<Text>();
|
||||||
|
inspectText.text = "Mouse Inspect";
|
||||||
|
inspectText.fontSize = 13;
|
||||||
|
|
||||||
|
inspectBtn.onClick.AddListener(OnInspectMouseClicked);
|
||||||
|
|
||||||
|
void OnInspectMouseClicked()
|
||||||
|
{
|
||||||
|
MouseInspector.StartInspect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
143
src/Inspectors/MouseInspector.cs
Normal file
143
src/Inspectors/MouseInspector.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors
|
||||||
|
{
|
||||||
|
public class MouseInspector
|
||||||
|
{
|
||||||
|
public static bool Enabled { get; set; }
|
||||||
|
|
||||||
|
//internal static Text s_objUnderMouseName;
|
||||||
|
internal static Text s_objNameLabel;
|
||||||
|
internal static Text s_objPathLabel;
|
||||||
|
internal static Text s_mousePosLabel;
|
||||||
|
|
||||||
|
private static GameObject s_lastHit;
|
||||||
|
private static Vector3 s_lastMousePos;
|
||||||
|
|
||||||
|
internal static GameObject s_UIContent;
|
||||||
|
|
||||||
|
public static void StartInspect()
|
||||||
|
{
|
||||||
|
Enabled = true;
|
||||||
|
MainMenu.Instance.MainPanel.SetActive(false);
|
||||||
|
s_UIContent.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopInspect()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
MainMenu.Instance.MainPanel.SetActive(true);
|
||||||
|
s_UIContent.SetActive(false);
|
||||||
|
|
||||||
|
ClearHitData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateInspect()
|
||||||
|
{
|
||||||
|
if (InputManager.GetKeyDown(KeyCode.Escape))
|
||||||
|
{
|
||||||
|
StopInspect();
|
||||||
|
}
|
||||||
|
|
||||||
|
var mousePos = InputManager.MousePosition;
|
||||||
|
|
||||||
|
if (mousePos != s_lastMousePos)
|
||||||
|
{
|
||||||
|
s_lastMousePos = mousePos;
|
||||||
|
|
||||||
|
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
|
||||||
|
|
||||||
|
s_mousePosLabel.text = $"<color=grey>Mouse Position:</color> {((Vector2)InputManager.MousePosition).ToString()}";
|
||||||
|
|
||||||
|
float yFix = mousePos.y < 120 ? 80 : -80;
|
||||||
|
|
||||||
|
s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UnityHelpers.MainCamera)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// actual inspect raycast
|
||||||
|
var ray = UnityHelpers.MainCamera.ScreenPointToRay(mousePos);
|
||||||
|
|
||||||
|
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||||
|
{
|
||||||
|
var obj = hit.transform.gameObject;
|
||||||
|
|
||||||
|
if (obj != s_lastHit)
|
||||||
|
{
|
||||||
|
s_lastHit = obj;
|
||||||
|
s_objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
|
||||||
|
s_objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InputManager.GetMouseButtonDown(0))
|
||||||
|
{
|
||||||
|
StopInspect();
|
||||||
|
InspectorManager.Instance.Inspect(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (s_lastHit)
|
||||||
|
ClearHitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ClearHitData()
|
||||||
|
{
|
||||||
|
s_lastHit = null;
|
||||||
|
s_objNameLabel.text = "No hits...";
|
||||||
|
s_objPathLabel.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI Construction
|
||||||
|
|
||||||
|
internal static void ConstructUI()
|
||||||
|
{
|
||||||
|
s_UIContent = UIFactory.CreatePanel(UIManager.CanvasRoot, "MouseInspect", out GameObject content);
|
||||||
|
|
||||||
|
s_UIContent.AddComponent<Mask>();
|
||||||
|
|
||||||
|
var baseRect = s_UIContent.GetComponent<RectTransform>();
|
||||||
|
var half = new Vector2(0.5f, 0.5f);
|
||||||
|
baseRect.anchorMin = half;
|
||||||
|
baseRect.anchorMax = half;
|
||||||
|
baseRect.pivot = half;
|
||||||
|
baseRect.sizeDelta = new Vector2(700, 100);
|
||||||
|
|
||||||
|
// Title text
|
||||||
|
|
||||||
|
var titleObj = UIFactory.CreateLabel(content, TextAnchor.MiddleCenter);
|
||||||
|
var titleText = titleObj.GetComponent<Text>();
|
||||||
|
titleText.text = "<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)";
|
||||||
|
|
||||||
|
var mousePosObj = UIFactory.CreateLabel(content, TextAnchor.MiddleCenter);
|
||||||
|
s_mousePosLabel = mousePosObj.GetComponent<Text>();
|
||||||
|
s_mousePosLabel.text = "Mouse Position:";
|
||||||
|
|
||||||
|
var hitLabelObj = UIFactory.CreateLabel(content, TextAnchor.MiddleLeft);
|
||||||
|
s_objNameLabel = hitLabelObj.GetComponent<Text>();
|
||||||
|
s_objNameLabel.text = "No hits...";
|
||||||
|
s_objNameLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
|
||||||
|
var pathLabelObj = UIFactory.CreateLabel(content, TextAnchor.MiddleLeft);
|
||||||
|
s_objPathLabel = pathLabelObj.GetComponent<Text>();
|
||||||
|
s_objPathLabel.color = Color.grey;
|
||||||
|
s_objPathLabel.fontStyle = FontStyle.Italic;
|
||||||
|
s_objPathLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
|
||||||
|
s_UIContent.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
63
src/Inspectors/Reflection/CacheObject/CacheEnumerated.cs
Normal file
63
src/Inspectors/Reflection/CacheObject/CacheEnumerated.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class CacheEnumerated : CacheObjectBase
|
||||||
|
{
|
||||||
|
public override Type FallbackType => ParentEnumeration.m_baseEntryType;
|
||||||
|
public override bool CanWrite => RefIList != null && ParentEnumeration.Owner.CanWrite;
|
||||||
|
|
||||||
|
public int Index { get; set; }
|
||||||
|
public IList RefIList { get; set; }
|
||||||
|
public InteractiveEnumerable ParentEnumeration { get; set; }
|
||||||
|
|
||||||
|
public CacheEnumerated(int index, InteractiveEnumerable parentEnumeration, IList refIList, GameObject parentContent)
|
||||||
|
{
|
||||||
|
this.ParentEnumeration = parentEnumeration;
|
||||||
|
this.Index = index;
|
||||||
|
this.RefIList = refIList;
|
||||||
|
this.m_parentContent = parentContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CreateIValue(object value, Type fallbackType)
|
||||||
|
{
|
||||||
|
IValue = InteractiveValue.Create(value, fallbackType);
|
||||||
|
IValue.Owner = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
RefIList[Index] = IValue.Value;
|
||||||
|
ParentEnumeration.Value = RefIList;
|
||||||
|
|
||||||
|
ParentEnumeration.Owner.SetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void ConstructUI()
|
||||||
|
{
|
||||||
|
base.ConstructUI();
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.padding.left = 5;
|
||||||
|
rowGroup.padding.right = 2;
|
||||||
|
|
||||||
|
var indexLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var indexLayout = indexLabelObj.AddComponent<LayoutElement>();
|
||||||
|
indexLayout.minWidth = 20;
|
||||||
|
indexLayout.flexibleWidth = 30;
|
||||||
|
indexLayout.minHeight = 25;
|
||||||
|
var indexText = indexLabelObj.GetComponent<Text>();
|
||||||
|
indexText.text = this.Index + ":";
|
||||||
|
|
||||||
|
IValue.m_mainContentParent = rowObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/Inspectors/Reflection/CacheObject/CacheField.cs
Normal file
38
src/Inspectors/Reflection/CacheObject/CacheField.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class CacheField : CacheMember
|
||||||
|
{
|
||||||
|
public override bool IsStatic => (MemInfo as FieldInfo).IsStatic;
|
||||||
|
|
||||||
|
public override Type FallbackType => (MemInfo as FieldInfo).FieldType;
|
||||||
|
|
||||||
|
public CacheField(FieldInfo fieldInfo, object declaringInstance, GameObject parent) : base(fieldInfo, declaringInstance, parent)
|
||||||
|
{
|
||||||
|
CreateIValue(null, fieldInfo.FieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateReflection()
|
||||||
|
{
|
||||||
|
var fi = MemInfo as FieldInfo;
|
||||||
|
IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
||||||
|
|
||||||
|
m_evaluated = true;
|
||||||
|
ReflectionException = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
var fi = MemInfo as FieldInfo;
|
||||||
|
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
503
src/Inspectors/Reflection/CacheObject/CacheMember.cs
Normal file
503
src/Inspectors/Reflection/CacheObject/CacheMember.cs
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
#if CPP
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public abstract class CacheMember : CacheObjectBase
|
||||||
|
{
|
||||||
|
public override bool IsMember => true;
|
||||||
|
|
||||||
|
public override Type FallbackType { get; }
|
||||||
|
|
||||||
|
public MemberInfo MemInfo { get; set; }
|
||||||
|
public Type DeclaringType { get; set; }
|
||||||
|
public object DeclaringInstance { get; set; }
|
||||||
|
public virtual bool IsStatic { get; private set; }
|
||||||
|
|
||||||
|
public string ReflectionException { get; set; }
|
||||||
|
|
||||||
|
public override bool CanWrite => m_canWrite ?? GetCanWrite();
|
||||||
|
private bool? m_canWrite;
|
||||||
|
|
||||||
|
public override bool HasParameters => ParamCount > 0;
|
||||||
|
public virtual int ParamCount => m_arguments.Length;
|
||||||
|
public override bool HasEvaluated => m_evaluated;
|
||||||
|
public bool m_evaluated = false;
|
||||||
|
public bool m_isEvaluating;
|
||||||
|
public ParameterInfo[] m_arguments = new ParameterInfo[0];
|
||||||
|
public string[] m_argumentInput = new string[0];
|
||||||
|
|
||||||
|
public string NameForFiltering => m_nameForFilter ?? (m_nameForFilter = $"{MemInfo.DeclaringType.Name}.{MemInfo.Name}".ToLower());
|
||||||
|
private string m_nameForFilter;
|
||||||
|
|
||||||
|
public string RichTextName => m_richTextName ?? GetRichTextName();
|
||||||
|
private string m_richTextName;
|
||||||
|
|
||||||
|
public CacheMember(MemberInfo memberInfo, object declaringInstance, GameObject parentContent)
|
||||||
|
{
|
||||||
|
MemInfo = memberInfo;
|
||||||
|
DeclaringType = memberInfo.DeclaringType;
|
||||||
|
DeclaringInstance = declaringInstance;
|
||||||
|
this.m_parentContent = parentContent;
|
||||||
|
#if CPP
|
||||||
|
if (DeclaringInstance != null)
|
||||||
|
DeclaringInstance = DeclaringInstance.Il2CppCast(DeclaringType);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CanProcessArgs(ParameterInfo[] parameters)
|
||||||
|
{
|
||||||
|
foreach (var param in parameters)
|
||||||
|
{
|
||||||
|
var pType = param.ParameterType;
|
||||||
|
|
||||||
|
if (pType.IsByRef && pType.HasElementType)
|
||||||
|
pType = pType.GetElementType();
|
||||||
|
|
||||||
|
if (pType != null && (pType.IsPrimitive || pType == typeof(string)))
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CreateIValue(object value, Type fallbackType)
|
||||||
|
{
|
||||||
|
IValue = InteractiveValue.Create(value, fallbackType);
|
||||||
|
IValue.Owner = this;
|
||||||
|
IValue.m_mainContentParent = this.m_rightGroup;
|
||||||
|
IValue.m_subContentParent = this.m_subContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue()
|
||||||
|
{
|
||||||
|
if (!HasParameters || m_isEvaluating)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
if (!IsReflectionSupported())
|
||||||
|
throw new Exception("Type not supported with Reflection");
|
||||||
|
#endif
|
||||||
|
UpdateReflection();
|
||||||
|
#if CPP
|
||||||
|
if (IValue.Value != null)
|
||||||
|
IValue.Value = IValue.Value.Il2CppCast(ReflectionHelpers.GetActualType(IValue.Value));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ReflectionException = ReflectionHelpers.ExceptionToString(e, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.UpdateValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void UpdateReflection();
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
// no implementation for base class
|
||||||
|
}
|
||||||
|
|
||||||
|
public object[] ParseArguments()
|
||||||
|
{
|
||||||
|
if (m_arguments.Length < 1)
|
||||||
|
return new object[0];
|
||||||
|
|
||||||
|
var parsedArgs = new List<object>();
|
||||||
|
for (int i = 0; i < m_arguments.Length; i++)
|
||||||
|
{
|
||||||
|
var input = m_argumentInput[i];
|
||||||
|
var type = m_arguments[i].ParameterType;
|
||||||
|
|
||||||
|
if (type.IsByRef)
|
||||||
|
type = type.GetElementType();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
if (type == typeof(string))
|
||||||
|
{
|
||||||
|
parsedArgs.Add(input);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var arg = type.GetMethod("Parse", new Type[] { typeof(string) })
|
||||||
|
.Invoke(null, new object[] { input });
|
||||||
|
|
||||||
|
parsedArgs.Add(arg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ExplorerCore.Log($"Could not parse input '{input}' for argument #{i} '{m_arguments[i].Name}' ({type.FullName})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No input, see if there is a default value.
|
||||||
|
if (m_arguments[i].IsOptional)
|
||||||
|
{
|
||||||
|
parsedArgs.Add(m_arguments[i].DefaultValue);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try add a null arg I guess
|
||||||
|
parsedArgs.Add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedArgs.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetCanWrite()
|
||||||
|
{
|
||||||
|
if (MemInfo is FieldInfo fi)
|
||||||
|
m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
|
||||||
|
else if (MemInfo is PropertyInfo pi)
|
||||||
|
m_canWrite = pi.CanWrite;
|
||||||
|
else
|
||||||
|
m_canWrite = false;
|
||||||
|
|
||||||
|
return (bool)m_canWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRichTextName()
|
||||||
|
{
|
||||||
|
return m_richTextName = UISyntaxHighlight.ParseFullSyntax(MemInfo.DeclaringType, false, MemInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
internal bool IsReflectionSupported()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseType = ReflectionHelpers.GetActualType(IValue.Value) ?? IValue.FallbackType;
|
||||||
|
|
||||||
|
var gArgs = baseType.GetGenericArguments();
|
||||||
|
if (gArgs.Length < 1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (var arg in gArgs)
|
||||||
|
{
|
||||||
|
if (!Check(arg))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool Check(Type type)
|
||||||
|
{
|
||||||
|
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(type))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!ReflectionHelpers.Il2CppTypeNotNull(type, out IntPtr ptr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#region UI
|
||||||
|
|
||||||
|
internal float GetMemberLabelWidth(RectTransform scrollRect)
|
||||||
|
{
|
||||||
|
var textGenSettings = m_memLabelText.GetGenerationSettings(m_topRowRect.rect.size);
|
||||||
|
textGenSettings.scaleFactor = InputFieldScroller.canvasScaler.scaleFactor;
|
||||||
|
|
||||||
|
var textGen = m_memLabelText.cachedTextGeneratorForLayout;
|
||||||
|
float preferredWidth = textGen.GetPreferredWidth(RichTextName, textGenSettings);
|
||||||
|
|
||||||
|
float max = scrollRect.rect.width * 0.4f;
|
||||||
|
|
||||||
|
if (preferredWidth > max) preferredWidth = max;
|
||||||
|
|
||||||
|
return preferredWidth < 125f ? 125f : preferredWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetWidths(float labelWidth, float valueWidth)
|
||||||
|
{
|
||||||
|
m_leftLayout.preferredWidth = labelWidth;
|
||||||
|
m_rightLayout.preferredWidth = valueWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RectTransform m_topRowRect;
|
||||||
|
internal Text m_memLabelText;
|
||||||
|
internal GameObject m_leftGroup;
|
||||||
|
internal LayoutElement m_leftLayout;
|
||||||
|
internal GameObject m_rightGroup;
|
||||||
|
internal LayoutElement m_rightLayout;
|
||||||
|
|
||||||
|
internal override void ConstructUI()
|
||||||
|
{
|
||||||
|
base.ConstructUI();
|
||||||
|
|
||||||
|
var topGroupObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
|
||||||
|
m_topRowRect = topGroupObj.GetComponent<RectTransform>();
|
||||||
|
var topLayout = topGroupObj.AddComponent<LayoutElement>();
|
||||||
|
topLayout.minHeight = 25;
|
||||||
|
topLayout.flexibleHeight = 0;
|
||||||
|
topLayout.minWidth = 300;
|
||||||
|
topLayout.flexibleWidth = 5000;
|
||||||
|
var topGroup = topGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
topGroup.childForceExpandHeight = false;
|
||||||
|
topGroup.childForceExpandWidth = false;
|
||||||
|
topGroup.childControlHeight = true;
|
||||||
|
topGroup.childControlWidth = true;
|
||||||
|
topGroup.spacing = 10;
|
||||||
|
topGroup.padding.left = 3;
|
||||||
|
topGroup.padding.right = 3;
|
||||||
|
topGroup.padding.top = 0;
|
||||||
|
topGroup.padding.bottom = 0;
|
||||||
|
|
||||||
|
// left group
|
||||||
|
|
||||||
|
m_leftGroup = UIFactory.CreateHorizontalGroup(topGroupObj, new Color(1, 1, 1, 0));
|
||||||
|
var leftLayout = m_leftGroup.AddComponent<LayoutElement>();
|
||||||
|
leftLayout.minHeight = 25;
|
||||||
|
leftLayout.flexibleHeight = 0;
|
||||||
|
leftLayout.minWidth = 125;
|
||||||
|
leftLayout.flexibleWidth = 200;
|
||||||
|
var leftGroup = m_leftGroup.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
leftGroup.childForceExpandHeight = true;
|
||||||
|
leftGroup.childForceExpandWidth = false;
|
||||||
|
leftGroup.childControlHeight = true;
|
||||||
|
leftGroup.childControlWidth = true;
|
||||||
|
leftGroup.spacing = 4;
|
||||||
|
|
||||||
|
// member label
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(m_leftGroup, TextAnchor.MiddleLeft);
|
||||||
|
var leftRect = labelObj.GetComponent<RectTransform>();
|
||||||
|
leftRect.anchorMin = Vector2.zero;
|
||||||
|
leftRect.anchorMax = Vector2.one;
|
||||||
|
leftRect.offsetMin = Vector2.zero;
|
||||||
|
leftRect.offsetMax = Vector2.zero;
|
||||||
|
leftRect.sizeDelta = Vector2.zero;
|
||||||
|
m_leftLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
m_leftLayout.preferredWidth = 125;
|
||||||
|
m_leftLayout.minHeight = 25;
|
||||||
|
m_leftLayout.flexibleHeight = 100;
|
||||||
|
var labelFitter = labelObj.AddComponent<ContentSizeFitter>();
|
||||||
|
labelFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
labelFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
m_memLabelText = labelObj.GetComponent<Text>();
|
||||||
|
m_memLabelText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||||
|
m_memLabelText.text = this.RichTextName;
|
||||||
|
|
||||||
|
// right group
|
||||||
|
|
||||||
|
m_rightGroup = UIFactory.CreateVerticalGroup(topGroupObj, new Color(1, 1, 1, 0));
|
||||||
|
m_rightLayout = m_rightGroup.AddComponent<LayoutElement>();
|
||||||
|
m_rightLayout.minHeight = 25;
|
||||||
|
m_rightLayout.flexibleHeight = 480;
|
||||||
|
m_rightLayout.minWidth = 125;
|
||||||
|
m_rightLayout.flexibleWidth = 5000;
|
||||||
|
var rightGroup = m_rightGroup.GetComponent<VerticalLayoutGroup>();
|
||||||
|
rightGroup.childForceExpandHeight = true;
|
||||||
|
rightGroup.childForceExpandWidth = false;
|
||||||
|
rightGroup.childControlHeight = true;
|
||||||
|
rightGroup.childControlWidth = true;
|
||||||
|
rightGroup.spacing = 2;
|
||||||
|
rightGroup.padding.top = 4;
|
||||||
|
rightGroup.padding.bottom = 2;
|
||||||
|
|
||||||
|
ConstructArgInput(out GameObject argsHolder);
|
||||||
|
|
||||||
|
ConstructEvaluateButtons(argsHolder);
|
||||||
|
|
||||||
|
IValue.m_mainContentParent = m_rightGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructArgInput(out GameObject argsHolder)
|
||||||
|
{
|
||||||
|
argsHolder = null;
|
||||||
|
|
||||||
|
if (HasParameters)
|
||||||
|
{
|
||||||
|
argsHolder = UIFactory.CreateVerticalGroup(m_rightGroup, new Color(1, 1, 1, 0));
|
||||||
|
var argsGroup = argsHolder.GetComponent<VerticalLayoutGroup>();
|
||||||
|
argsGroup.spacing = 4;
|
||||||
|
|
||||||
|
if (this is CacheMethod cm && cm.GenericArgs.Length > 0)
|
||||||
|
{
|
||||||
|
cm.ConstructGenericArgInput(argsHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo normal args
|
||||||
|
|
||||||
|
if (m_arguments.Length > 0)
|
||||||
|
{
|
||||||
|
var titleObj = UIFactory.CreateLabel(argsHolder, TextAnchor.MiddleLeft);
|
||||||
|
var titleText = titleObj.GetComponent<Text>();
|
||||||
|
titleText.text = "<b>Arguments:</b>";
|
||||||
|
|
||||||
|
for (int i = 0; i < m_arguments.Length; i++)
|
||||||
|
{
|
||||||
|
AddArgRow(i, argsHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
argsHolder.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddArgRow(int i, GameObject parent)
|
||||||
|
{
|
||||||
|
var arg = m_arguments[i];
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
rowLayout.minHeight = 25;
|
||||||
|
rowLayout.flexibleWidth = 5000;
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childForceExpandHeight = false;
|
||||||
|
rowGroup.childForceExpandWidth = true;
|
||||||
|
rowGroup.spacing = 4;
|
||||||
|
|
||||||
|
var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var argLabelLayout = argLabelObj.AddComponent<LayoutElement>();
|
||||||
|
argLabelLayout.minHeight = 25;
|
||||||
|
var argText = argLabelObj.GetComponent<Text>();
|
||||||
|
var argTypeTxt = UISyntaxHighlight.ParseFullSyntax(arg.ParameterType, false);
|
||||||
|
argText.text = $"{argTypeTxt} <color={UISyntaxHighlight.Local}>{arg.Name}</color>";
|
||||||
|
|
||||||
|
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
|
||||||
|
var argInputLayout = argInputObj.AddComponent<LayoutElement>();
|
||||||
|
argInputLayout.flexibleWidth = 1200;
|
||||||
|
argInputLayout.preferredWidth = 150;
|
||||||
|
argInputLayout.minWidth = 20;
|
||||||
|
argInputLayout.minHeight = 25;
|
||||||
|
argInputLayout.flexibleHeight = 0;
|
||||||
|
//argInputLayout.layoutPriority = 2;
|
||||||
|
|
||||||
|
var argInput = argInputObj.GetComponent<InputField>();
|
||||||
|
argInput.onValueChanged.AddListener((string val) => { m_argumentInput[i] = val; });
|
||||||
|
|
||||||
|
if (arg.IsOptional)
|
||||||
|
{
|
||||||
|
var phInput = argInput.placeholder.GetComponent<Text>();
|
||||||
|
phInput.text = " = " + arg.DefaultValue?.ToString() ?? "null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructEvaluateButtons(GameObject argsHolder)
|
||||||
|
{
|
||||||
|
if (HasParameters)
|
||||||
|
{
|
||||||
|
var evalGroupObj = UIFactory.CreateHorizontalGroup(m_rightGroup, new Color(1, 1, 1, 0));
|
||||||
|
var evalGroup = evalGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
evalGroup.childForceExpandWidth = false;
|
||||||
|
evalGroup.childForceExpandHeight = false;
|
||||||
|
evalGroup.spacing = 5;
|
||||||
|
var evalGroupLayout = evalGroupObj.AddComponent<LayoutElement>();
|
||||||
|
evalGroupLayout.minHeight = 25;
|
||||||
|
evalGroupLayout.flexibleHeight = 0;
|
||||||
|
evalGroupLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
var evalButtonObj = UIFactory.CreateButton(evalGroupObj, new Color(0.4f, 0.4f, 0.4f));
|
||||||
|
var evalLayout = evalButtonObj.AddComponent<LayoutElement>();
|
||||||
|
evalLayout.minWidth = 100;
|
||||||
|
evalLayout.minHeight = 22;
|
||||||
|
evalLayout.flexibleWidth = 0;
|
||||||
|
var evalText = evalButtonObj.GetComponentInChildren<Text>();
|
||||||
|
evalText.text = $"Evaluate ({ParamCount})";
|
||||||
|
|
||||||
|
var evalButton = evalButtonObj.GetComponent<Button>();
|
||||||
|
var colors = evalButton.colors;
|
||||||
|
colors.highlightedColor = new Color(0.4f, 0.7f, 0.4f);
|
||||||
|
evalButton.colors = colors;
|
||||||
|
|
||||||
|
var cancelButtonObj = UIFactory.CreateButton(evalGroupObj, new Color(0.3f, 0.3f, 0.3f));
|
||||||
|
var cancelLayout = cancelButtonObj.AddComponent<LayoutElement>();
|
||||||
|
cancelLayout.minWidth = 100;
|
||||||
|
cancelLayout.minHeight = 22;
|
||||||
|
cancelLayout.flexibleWidth = 0;
|
||||||
|
var cancelText = cancelButtonObj.GetComponentInChildren<Text>();
|
||||||
|
cancelText.text = "Close";
|
||||||
|
|
||||||
|
cancelButtonObj.SetActive(false);
|
||||||
|
|
||||||
|
evalButton.onClick.AddListener(() =>
|
||||||
|
{
|
||||||
|
if (!m_isEvaluating)
|
||||||
|
{
|
||||||
|
argsHolder.SetActive(true);
|
||||||
|
m_isEvaluating = true;
|
||||||
|
evalText.text = "Evaluate";
|
||||||
|
colors = evalButton.colors;
|
||||||
|
colors.normalColor = new Color(0.3f, 0.6f, 0.3f);
|
||||||
|
evalButton.colors = colors;
|
||||||
|
|
||||||
|
cancelButtonObj.SetActive(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (this is CacheMethod cm)
|
||||||
|
cm.Evaluate();
|
||||||
|
else
|
||||||
|
UpdateValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var cancelButton = cancelButtonObj.GetComponent<Button>();
|
||||||
|
cancelButton.onClick.AddListener(() =>
|
||||||
|
{
|
||||||
|
cancelButtonObj.SetActive(false);
|
||||||
|
argsHolder.SetActive(false);
|
||||||
|
m_isEvaluating = false;
|
||||||
|
|
||||||
|
evalText.text = $"Evaluate ({ParamCount})";
|
||||||
|
colors = evalButton.colors;
|
||||||
|
colors.normalColor = new Color(0.4f, 0.4f, 0.4f);
|
||||||
|
evalButton.colors = colors;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (this is CacheMethod)
|
||||||
|
{
|
||||||
|
// simple method evaluate button
|
||||||
|
|
||||||
|
var evalButtonObj = UIFactory.CreateButton(m_rightGroup, new Color(0.3f, 0.6f, 0.3f));
|
||||||
|
var evalLayout = evalButtonObj.AddComponent<LayoutElement>();
|
||||||
|
evalLayout.minWidth = 100;
|
||||||
|
evalLayout.minHeight = 22;
|
||||||
|
evalLayout.flexibleWidth = 0;
|
||||||
|
var evalText = evalButtonObj.GetComponentInChildren<Text>();
|
||||||
|
evalText.text = "Evaluate";
|
||||||
|
|
||||||
|
var evalButton = evalButtonObj.GetComponent<Button>();
|
||||||
|
var colors = evalButton.colors;
|
||||||
|
colors.highlightedColor = new Color(0.4f, 0.7f, 0.4f);
|
||||||
|
evalButton.colors = colors;
|
||||||
|
|
||||||
|
evalButton.onClick.AddListener(OnMainEvaluateButton);
|
||||||
|
void OnMainEvaluateButton()
|
||||||
|
{
|
||||||
|
(this as CacheMethod).Evaluate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
187
src/Inspectors/Reflection/CacheObject/CacheMethod.cs
Normal file
187
src/Inspectors/Reflection/CacheObject/CacheMethod.cs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class CacheMethod : CacheMember
|
||||||
|
{
|
||||||
|
//private CacheObjectBase m_cachedReturnValue;
|
||||||
|
|
||||||
|
public override Type FallbackType => (MemInfo as MethodInfo).ReturnType;
|
||||||
|
|
||||||
|
public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
|
||||||
|
|
||||||
|
public override bool IsStatic => (MemInfo as MethodInfo).IsStatic;
|
||||||
|
|
||||||
|
public override int ParamCount => base.ParamCount + m_genericArgInput.Length;
|
||||||
|
|
||||||
|
public Type[] GenericArgs { get; private set; }
|
||||||
|
public Type[][] GenericConstraints { get; private set; }
|
||||||
|
|
||||||
|
public string[] m_genericArgInput = new string[0];
|
||||||
|
|
||||||
|
public CacheMethod(MethodInfo methodInfo, object declaringInstance, GameObject parent) : base(methodInfo, declaringInstance, parent)
|
||||||
|
{
|
||||||
|
GenericArgs = methodInfo.GetGenericArguments();
|
||||||
|
|
||||||
|
GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints())
|
||||||
|
.Where(x => x != null)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
m_genericArgInput = new string[GenericArgs.Length];
|
||||||
|
|
||||||
|
m_arguments = methodInfo.GetParameters();
|
||||||
|
m_argumentInput = new string[m_arguments.Length];
|
||||||
|
|
||||||
|
CreateIValue(null, methodInfo.ReturnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateReflection()
|
||||||
|
{
|
||||||
|
// CacheMethod cannot UpdateValue directly. Need to Evaluate.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Evaluate()
|
||||||
|
{
|
||||||
|
MethodInfo mi;
|
||||||
|
if (GenericArgs.Length > 0)
|
||||||
|
{
|
||||||
|
mi = MakeGenericMethodFromInput();
|
||||||
|
if (mi == null) return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mi = MemInfo as MethodInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
object ret = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
|
||||||
|
m_evaluated = true;
|
||||||
|
m_isEvaluating = false;
|
||||||
|
ReflectionException = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
|
||||||
|
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
IValue.Value = ret;
|
||||||
|
UpdateValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo MakeGenericMethodFromInput()
|
||||||
|
{
|
||||||
|
var mi = MemInfo as MethodInfo;
|
||||||
|
|
||||||
|
var list = new List<Type>();
|
||||||
|
for (int i = 0; i < GenericArgs.Length; i++)
|
||||||
|
{
|
||||||
|
var input = m_genericArgInput[i];
|
||||||
|
if (ReflectionHelpers.GetTypeByName(input) is Type t)
|
||||||
|
{
|
||||||
|
if (GenericConstraints[i].Length == 0)
|
||||||
|
{
|
||||||
|
list.Add(t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var constraint in GenericConstraints[i].Where(x => x != null))
|
||||||
|
{
|
||||||
|
if (!constraint.IsAssignableFrom(t))
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
|
||||||
|
$" Make sure you use the full name including the namespace.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make into a generic with type list
|
||||||
|
mi = mi.MakeGenericMethod(list.ToArray());
|
||||||
|
|
||||||
|
return mi;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructGenericArgInput(GameObject parent)
|
||||||
|
{
|
||||||
|
var titleObj = UIFactory.CreateLabel(parent, TextAnchor.MiddleLeft);
|
||||||
|
var titleText = titleObj.GetComponent<Text>();
|
||||||
|
titleText.text = "<b>Generic Arguments:</b>";
|
||||||
|
|
||||||
|
for (int i = 0; i < GenericArgs.Length; i++)
|
||||||
|
{
|
||||||
|
AddGenericArgRow(i, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddGenericArgRow(int i, GameObject parent)
|
||||||
|
{
|
||||||
|
var arg = GenericArgs[i];
|
||||||
|
|
||||||
|
string constrainTxt = "";
|
||||||
|
if (this.GenericConstraints[i].Length > 0)
|
||||||
|
{
|
||||||
|
foreach (var constraint in this.GenericConstraints[i])
|
||||||
|
{
|
||||||
|
if (constrainTxt != "")
|
||||||
|
constrainTxt += ", ";
|
||||||
|
|
||||||
|
constrainTxt += $"{UISyntaxHighlight.ParseFullSyntax(constraint, false)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
constrainTxt = $"Any";
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
rowLayout.minHeight = 25;
|
||||||
|
rowLayout.flexibleWidth = 5000;
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childForceExpandHeight = true;
|
||||||
|
rowGroup.spacing = 4;
|
||||||
|
|
||||||
|
var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
//var argLayout = argLabelObj.AddComponent<LayoutElement>();
|
||||||
|
//argLayout.minWidth = 20;
|
||||||
|
var argText = argLabelObj.GetComponent<Text>();
|
||||||
|
argText.text = $"{constrainTxt} <color={UISyntaxHighlight.Enum}>{arg.Name}</color>";
|
||||||
|
|
||||||
|
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
|
||||||
|
var argInputLayout = argInputObj.AddComponent<LayoutElement>();
|
||||||
|
argInputLayout.flexibleWidth = 1200;
|
||||||
|
|
||||||
|
var argInput = argInputObj.GetComponent<InputField>();
|
||||||
|
argInput.onValueChanged.AddListener((string val) => { m_genericArgInput[i] = val; });
|
||||||
|
|
||||||
|
//var constraintLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
//var constraintLayout = constraintLabelObj.AddComponent<LayoutElement>();
|
||||||
|
//constraintLayout.minWidth = 60;
|
||||||
|
//constraintLayout.flexibleWidth = 100;
|
||||||
|
//var constraintText = constraintLabelObj.GetComponent<Text>();
|
||||||
|
//constraintText.text = ;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
124
src/Inspectors/Reflection/CacheObject/CacheObjectBase.cs
Normal file
124
src/Inspectors/Reflection/CacheObject/CacheObjectBase.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public abstract class CacheObjectBase
|
||||||
|
{
|
||||||
|
public InteractiveValue IValue;
|
||||||
|
|
||||||
|
public virtual bool CanWrite => false;
|
||||||
|
public virtual bool HasParameters => false;
|
||||||
|
public virtual bool IsMember => false;
|
||||||
|
public virtual bool HasEvaluated => true;
|
||||||
|
|
||||||
|
public abstract Type FallbackType { get; }
|
||||||
|
|
||||||
|
public abstract void CreateIValue(object value, Type fallbackType);
|
||||||
|
|
||||||
|
public virtual void Enable()
|
||||||
|
{
|
||||||
|
if (!m_constructedUI)
|
||||||
|
{
|
||||||
|
ConstructUI();
|
||||||
|
UpdateValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_mainContent.SetActive(true);
|
||||||
|
m_mainContent.transform.SetAsLastSibling();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Disable()
|
||||||
|
{
|
||||||
|
if (m_mainContent)
|
||||||
|
m_mainContent.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
if (this.m_mainContent)
|
||||||
|
GameObject.Destroy(this.m_mainContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void UpdateValue()
|
||||||
|
{
|
||||||
|
var value = IValue.Value;
|
||||||
|
|
||||||
|
// if the type has changed fundamentally, make a new interactivevalue for it
|
||||||
|
var type = value == null
|
||||||
|
? FallbackType
|
||||||
|
: ReflectionHelpers.GetActualType(value);
|
||||||
|
|
||||||
|
var ivalueType = InteractiveValue.GetIValueForType(type);
|
||||||
|
|
||||||
|
if (ivalueType != IValue.GetType())
|
||||||
|
{
|
||||||
|
IValue.OnDestroy();
|
||||||
|
CreateIValue(value, FallbackType);
|
||||||
|
m_subContent.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
IValue.OnValueUpdated();
|
||||||
|
|
||||||
|
IValue.RefreshElementsAfterUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetValue() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal bool m_constructedUI;
|
||||||
|
internal GameObject m_parentContent;
|
||||||
|
internal RectTransform m_mainRect;
|
||||||
|
internal GameObject m_mainContent;
|
||||||
|
internal GameObject m_subContent;
|
||||||
|
|
||||||
|
// Make base UI holder for CacheObject, this doesnt actually display anything.
|
||||||
|
internal virtual void ConstructUI()
|
||||||
|
{
|
||||||
|
m_constructedUI = true;
|
||||||
|
|
||||||
|
m_mainContent = UIFactory.CreateVerticalGroup(m_parentContent, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
m_mainContent.name = "CacheObjectBase.MainContent";
|
||||||
|
m_mainRect = m_mainContent.GetComponent<RectTransform>();
|
||||||
|
m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||||
|
var mainGroup = m_mainContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
var mainLayout = m_mainContent.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.minHeight = 25;
|
||||||
|
mainLayout.flexibleHeight = 9999;
|
||||||
|
mainLayout.minWidth = 200;
|
||||||
|
mainLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
// subcontent
|
||||||
|
|
||||||
|
m_subContent = UIFactory.CreateVerticalGroup(m_mainContent, new Color(0.085f, 0.085f, 0.085f));
|
||||||
|
m_subContent.name = "CacheObjectBase.SubContent";
|
||||||
|
var subGroup = m_subContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
subGroup.childForceExpandWidth = true;
|
||||||
|
subGroup.childForceExpandHeight = false;
|
||||||
|
var subLayout = m_subContent.AddComponent<LayoutElement>();
|
||||||
|
subLayout.minHeight = 30;
|
||||||
|
subLayout.flexibleHeight = 9999;
|
||||||
|
subLayout.minWidth = 125;
|
||||||
|
subLayout.flexibleWidth = 9000;
|
||||||
|
|
||||||
|
m_subContent.SetActive(false);
|
||||||
|
|
||||||
|
IValue.m_subContentParent = m_subContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
71
src/Inspectors/Reflection/CacheObject/CachePaired.cs
Normal file
71
src/Inspectors/Reflection/CacheObject/CachePaired.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public enum PairTypes
|
||||||
|
{
|
||||||
|
Key,
|
||||||
|
Value
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CachePaired : CacheObjectBase
|
||||||
|
{
|
||||||
|
public override Type FallbackType => PairType == PairTypes.Key
|
||||||
|
? ParentDictionary.m_typeOfKeys
|
||||||
|
: ParentDictionary.m_typeofValues;
|
||||||
|
|
||||||
|
public override bool CanWrite => false; // todo?
|
||||||
|
|
||||||
|
public PairTypes PairType;
|
||||||
|
public int Index { get; private set; }
|
||||||
|
public InteractiveDictionary ParentDictionary { get; private set; }
|
||||||
|
internal IDictionary RefIDict;
|
||||||
|
|
||||||
|
public CachePaired(int index, InteractiveDictionary parentDict, IDictionary refIDict, PairTypes pairType, GameObject parentContent)
|
||||||
|
{
|
||||||
|
Index = index;
|
||||||
|
ParentDictionary = parentDict;
|
||||||
|
RefIDict = refIDict;
|
||||||
|
this.PairType = pairType;
|
||||||
|
this.m_parentContent = parentContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CreateIValue(object value, Type fallbackType)
|
||||||
|
{
|
||||||
|
IValue = InteractiveValue.Create(value, fallbackType);
|
||||||
|
IValue.Owner = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal override void ConstructUI()
|
||||||
|
{
|
||||||
|
base.ConstructUI();
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.padding.left = 5;
|
||||||
|
rowGroup.padding.right = 2;
|
||||||
|
|
||||||
|
var indexLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var indexLayout = indexLabelObj.AddComponent<LayoutElement>();
|
||||||
|
indexLayout.minWidth = 80;
|
||||||
|
indexLayout.flexibleWidth = 30;
|
||||||
|
indexLayout.minHeight = 25;
|
||||||
|
var indexText = indexLabelObj.GetComponent<Text>();
|
||||||
|
indexText.text = $"{this.PairType} {this.Index}:";
|
||||||
|
|
||||||
|
IValue.m_mainContentParent = rowObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
68
src/Inspectors/Reflection/CacheObject/CacheProperty.cs
Normal file
68
src/Inspectors/Reflection/CacheObject/CacheProperty.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class CacheProperty : CacheMember
|
||||||
|
{
|
||||||
|
public override Type FallbackType => (MemInfo as PropertyInfo).PropertyType;
|
||||||
|
|
||||||
|
public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors(true)[0].IsStatic;
|
||||||
|
|
||||||
|
public CacheProperty(PropertyInfo propertyInfo, object declaringInstance, GameObject parent) : base(propertyInfo, declaringInstance, parent)
|
||||||
|
{
|
||||||
|
this.m_arguments = propertyInfo.GetIndexParameters();
|
||||||
|
this.m_argumentInput = new string[m_arguments.Length];
|
||||||
|
|
||||||
|
CreateIValue(null, propertyInfo.PropertyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateReflection()
|
||||||
|
{
|
||||||
|
if (HasParameters && !m_isEvaluating)
|
||||||
|
{
|
||||||
|
// Need to enter parameters first.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pi = MemInfo as PropertyInfo;
|
||||||
|
|
||||||
|
if (pi.CanRead)
|
||||||
|
{
|
||||||
|
var target = pi.GetAccessors(true)[0].IsStatic ? null : DeclaringInstance;
|
||||||
|
|
||||||
|
IValue.Value = pi.GetValue(target, ParseArguments());
|
||||||
|
|
||||||
|
m_evaluated = true;
|
||||||
|
ReflectionException = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (FallbackType == typeof(string))
|
||||||
|
{
|
||||||
|
IValue.Value = "";
|
||||||
|
}
|
||||||
|
else if (FallbackType.IsPrimitive)
|
||||||
|
{
|
||||||
|
IValue.Value = Activator.CreateInstance(FallbackType);
|
||||||
|
}
|
||||||
|
m_evaluated = true;
|
||||||
|
ReflectionException = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
var pi = MemInfo as PropertyInfo;
|
||||||
|
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
|
||||||
|
|
||||||
|
pi.SetValue(target, IValue.Value, ParseArguments());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
284
src/Inspectors/Reflection/InstanceInspector.cs
Normal file
284
src/Inspectors/Reflection/InstanceInspector.cs
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public enum MemberScopes
|
||||||
|
{
|
||||||
|
All,
|
||||||
|
Instance,
|
||||||
|
Static
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InstanceInspector : ReflectionInspector
|
||||||
|
{
|
||||||
|
public override string TabLabel => $" <color=cyan>[R]</color> {base.TabLabel}";
|
||||||
|
|
||||||
|
internal MemberScopes m_scopeFilter;
|
||||||
|
internal Button m_lastActiveScopeButton;
|
||||||
|
|
||||||
|
public InstanceInspector(object target) : base(target) { }
|
||||||
|
|
||||||
|
private void OnScopeFilterClicked(MemberScopes type, Button button)
|
||||||
|
{
|
||||||
|
if (m_lastActiveScopeButton)
|
||||||
|
{
|
||||||
|
var lastColors = m_lastActiveScopeButton.colors;
|
||||||
|
lastColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
|
||||||
|
m_lastActiveScopeButton.colors = lastColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_scopeFilter = type;
|
||||||
|
m_lastActiveScopeButton = button;
|
||||||
|
|
||||||
|
var colors = m_lastActiveScopeButton.colors;
|
||||||
|
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||||
|
m_lastActiveScopeButton.colors = colors;
|
||||||
|
|
||||||
|
FilterMembers(null, true);
|
||||||
|
m_sliderScroller.m_slider.value = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConstructInstanceHelpers()
|
||||||
|
{
|
||||||
|
if (!typeof(Component).IsAssignableFrom(m_targetType) && !typeof(UnityEngine.Object).IsAssignableFrom(m_targetType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childForceExpandWidth = true;
|
||||||
|
rowGroup.childControlWidth = true;
|
||||||
|
rowGroup.spacing = 5;
|
||||||
|
rowGroup.padding.top = 2;
|
||||||
|
rowGroup.padding.bottom = 2;
|
||||||
|
rowGroup.padding.right = 2;
|
||||||
|
rowGroup.padding.left = 2;
|
||||||
|
var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
rowLayout.minHeight = 25;
|
||||||
|
rowLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
if (typeof(Component).IsAssignableFrom(m_targetType))
|
||||||
|
{
|
||||||
|
ConstructCompHelper(rowObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstructUObjHelper(rowObj);
|
||||||
|
|
||||||
|
// WIP
|
||||||
|
|
||||||
|
//if (m_targetType == typeof(Texture2D))
|
||||||
|
// ConstructTextureHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructCompHelper(GameObject rowObj)
|
||||||
|
{
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 90;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
labelLayout.flexibleWidth = 0;
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.text = "GameObject:";
|
||||||
|
|
||||||
|
#if MONO
|
||||||
|
var comp = Target as Component;
|
||||||
|
#else
|
||||||
|
var comp = (Target as Il2CppSystem.Object).TryCast<Component>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var goBtnObj = UIFactory.CreateButton(rowObj, new Color(0.2f, 0.5f, 0.2f));
|
||||||
|
var goBtnLayout = goBtnObj.AddComponent<LayoutElement>();
|
||||||
|
goBtnLayout.minHeight = 25;
|
||||||
|
goBtnLayout.minWidth = 200;
|
||||||
|
goBtnLayout.flexibleWidth = 0;
|
||||||
|
var text = goBtnObj.GetComponentInChildren<Text>();
|
||||||
|
text.text = comp.name;
|
||||||
|
var btn = goBtnObj.GetComponent<Button>();
|
||||||
|
btn.onClick.AddListener(() => { InspectorManager.Instance.Inspect(comp.gameObject); });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructUObjHelper(GameObject rowObj)
|
||||||
|
{
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 60;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
labelLayout.flexibleWidth = 0;
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.text = "Name:";
|
||||||
|
|
||||||
|
#if MONO
|
||||||
|
var uObj = Target as UnityEngine.Object;
|
||||||
|
#else
|
||||||
|
var uObj = (Target as Il2CppSystem.Object).TryCast<UnityEngine.Object>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateInputField(rowObj, 14, 3, 1);
|
||||||
|
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||||
|
inputLayout.minHeight = 25;
|
||||||
|
inputLayout.flexibleWidth = 2000;
|
||||||
|
var inputField = inputObj.GetComponent<InputField>();
|
||||||
|
inputField.readOnly = true;
|
||||||
|
inputField.text = uObj.name;
|
||||||
|
|
||||||
|
//var goBtnObj = UIFactory.CreateButton(rowObj, new Color(0.2f, 0.5f, 0.2f));
|
||||||
|
//var goBtnLayout = goBtnObj.AddComponent<LayoutElement>();
|
||||||
|
//goBtnLayout.minHeight = 25;
|
||||||
|
//goBtnLayout.minWidth = 200;
|
||||||
|
//goBtnLayout.flexibleWidth = 0;
|
||||||
|
//var text = goBtnObj.GetComponentInChildren<Text>();
|
||||||
|
//text.text = comp.name;
|
||||||
|
//var btn = goBtnObj.GetComponent<Button>();
|
||||||
|
//btn.onClick.AddListener(() => { InspectorManager.Instance.Inspect(comp.gameObject); });
|
||||||
|
}
|
||||||
|
|
||||||
|
//internal bool showingTextureHelper;
|
||||||
|
//internal bool constructedTextureViewer;
|
||||||
|
|
||||||
|
//internal void ConstructTextureHelper()
|
||||||
|
//{
|
||||||
|
// var rowObj = UIFactory.CreateHorizontalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
// var rowLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
// rowLayout.minHeight = 25;
|
||||||
|
// rowLayout.flexibleHeight = 0;
|
||||||
|
// var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
// rowGroup.childForceExpandHeight = true;
|
||||||
|
// rowGroup.childForceExpandWidth = false;
|
||||||
|
// rowGroup.padding.top = 3;
|
||||||
|
// rowGroup.padding.left = 3;
|
||||||
|
// rowGroup.padding.bottom = 3;
|
||||||
|
// rowGroup.padding.right = 3;
|
||||||
|
// rowGroup.spacing = 5;
|
||||||
|
|
||||||
|
// var showBtnObj = UIFactory.CreateButton(rowObj, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
// var showBtnLayout = showBtnObj.AddComponent<LayoutElement>();
|
||||||
|
// showBtnLayout.minWidth = 50;
|
||||||
|
// showBtnLayout.flexibleWidth = 0;
|
||||||
|
// var showText = showBtnObj.GetComponentInChildren<Text>();
|
||||||
|
// showText.text = "Show";
|
||||||
|
// var showBtn = showBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
// var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
// var labelText = labelObj.GetComponent<Text>();
|
||||||
|
// labelText.text = "Texture Viewer";
|
||||||
|
|
||||||
|
// var textureViewerObj = UIFactory.CreateScrollView(Content, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
// var viewerGroup = scrollContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
// viewerGroup.childForceExpandHeight = false;
|
||||||
|
// viewerGroup.childForceExpandWidth = false;
|
||||||
|
// viewerGroup.childControlHeight = true;
|
||||||
|
// viewerGroup.childControlWidth = true;
|
||||||
|
// var mainLayout = textureViewerObj.GetComponent<LayoutElement>();
|
||||||
|
// mainLayout.flexibleHeight = -1;
|
||||||
|
// mainLayout.flexibleWidth = 2000;
|
||||||
|
// mainLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// textureViewerObj.SetActive(false);
|
||||||
|
|
||||||
|
// showBtn.onClick.AddListener(() =>
|
||||||
|
// {
|
||||||
|
// showingTextureHelper = !showingTextureHelper;
|
||||||
|
|
||||||
|
// if (showingTextureHelper)
|
||||||
|
// {
|
||||||
|
// if (!constructedTextureViewer)
|
||||||
|
// ConstructTextureViewerArea(scrollContent);
|
||||||
|
|
||||||
|
// showText.text = "Hide";
|
||||||
|
// textureViewerObj.SetActive(true);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// showText.text = "Show";
|
||||||
|
// textureViewerObj.SetActive(false);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
//internal void ConstructTextureViewerArea(GameObject parent)
|
||||||
|
//{
|
||||||
|
// constructedTextureViewer = true;
|
||||||
|
|
||||||
|
// var tex = Target as Texture2D;
|
||||||
|
|
||||||
|
// if (!tex)
|
||||||
|
// {
|
||||||
|
// ExplorerCore.LogWarning("Could not cast the target instance to Texture2D!");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var imageObj = UIFactory.CreateUIObject("TextureViewerImage", parent, new Vector2(1, 1));
|
||||||
|
// var image = imageObj.AddComponent<Image>();
|
||||||
|
// var sprite = UIManager.CreateSprite(tex);
|
||||||
|
// image.sprite = sprite;
|
||||||
|
|
||||||
|
// var fitter = imageObj.AddComponent<ContentSizeFitter>();
|
||||||
|
// fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
// //fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
|
||||||
|
// var imageLayout = imageObj.AddComponent<LayoutElement>();
|
||||||
|
// imageLayout.preferredHeight = sprite.rect.height;
|
||||||
|
// imageLayout.preferredWidth = sprite.rect.width;
|
||||||
|
//}
|
||||||
|
|
||||||
|
public void ConstructInstanceFilters(GameObject parent)
|
||||||
|
{
|
||||||
|
var memberFilterRowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var memFilterGroup = memberFilterRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
memFilterGroup.childForceExpandHeight = false;
|
||||||
|
memFilterGroup.childForceExpandWidth = false;
|
||||||
|
memFilterGroup.childControlWidth = true;
|
||||||
|
memFilterGroup.childControlHeight = true;
|
||||||
|
memFilterGroup.spacing = 5;
|
||||||
|
var memFilterLayout = memberFilterRowObj.AddComponent<LayoutElement>();
|
||||||
|
memFilterLayout.minHeight = 25;
|
||||||
|
memFilterLayout.flexibleHeight = 0;
|
||||||
|
memFilterLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
var memLabelObj = UIFactory.CreateLabel(memberFilterRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var memLabelLayout = memLabelObj.AddComponent<LayoutElement>();
|
||||||
|
memLabelLayout.minWidth = 100;
|
||||||
|
memLabelLayout.minHeight = 25;
|
||||||
|
memLabelLayout.flexibleWidth = 0;
|
||||||
|
var memLabelText = memLabelObj.GetComponent<Text>();
|
||||||
|
memLabelText.text = "Filter scope:";
|
||||||
|
memLabelText.color = Color.grey;
|
||||||
|
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberScopes.All, true);
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberScopes.Instance);
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberScopes.Static);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFilterButton(GameObject parent, MemberScopes type, bool setEnabled = false)
|
||||||
|
{
|
||||||
|
var btnObj = UIFactory.CreateButton(parent, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
|
||||||
|
var btnLayout = btnObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.minWidth = 70;
|
||||||
|
|
||||||
|
var text = btnObj.GetComponentInChildren<Text>();
|
||||||
|
text.text = type.ToString();
|
||||||
|
|
||||||
|
var btn = btnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
btn.onClick.AddListener(() => { OnScopeFilterClicked(type, btn); });
|
||||||
|
|
||||||
|
var colors = btn.colors;
|
||||||
|
colors.highlightedColor = new Color(0.3f, 0.7f, 0.3f);
|
||||||
|
|
||||||
|
if (setEnabled)
|
||||||
|
{
|
||||||
|
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||||
|
m_scopeFilter = type;
|
||||||
|
m_lastActiveScopeButton = btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.colors = colors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
src/Inspectors/Reflection/InteractiveValue/InteractiveBool.cs
Normal file
114
src/Inspectors/Reflection/InteractiveValue/InteractiveBool.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveBool : InteractiveValue
|
||||||
|
{
|
||||||
|
public InteractiveBool(object value, Type valueType) : base(value, valueType) { }
|
||||||
|
|
||||||
|
public override bool HasSubContent => false;
|
||||||
|
public override bool SubContentWanted => false;
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
|
||||||
|
internal Toggle m_toggle;
|
||||||
|
internal Button m_applyBtn;
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
base.OnValueUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
GetDefaultLabel();
|
||||||
|
|
||||||
|
if (Owner.HasEvaluated)
|
||||||
|
{
|
||||||
|
var val = (bool)Value;
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
if (!m_toggle.gameObject.activeSelf)
|
||||||
|
m_toggle.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
if (!m_applyBtn.gameObject.activeSelf)
|
||||||
|
m_applyBtn.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
if (val != m_toggle.isOn)
|
||||||
|
m_toggle.isOn = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = val
|
||||||
|
? "6bc981" // on
|
||||||
|
: "c96b6b"; // off
|
||||||
|
|
||||||
|
m_baseLabel.text = $"<color=#{color}>{val}</color>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnException(CacheMember member)
|
||||||
|
{
|
||||||
|
base.OnException(member);
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
if (m_toggle.gameObject.activeSelf)
|
||||||
|
m_toggle.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
if (m_applyBtn.gameObject.activeSelf)
|
||||||
|
m_applyBtn.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnToggleValueChanged(bool val)
|
||||||
|
{
|
||||||
|
Value = val;
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
|
||||||
|
var baseLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
|
||||||
|
baseLayout.flexibleWidth = 0;
|
||||||
|
baseLayout.minWidth = 50;
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var toggleObj = UIFactory.CreateToggle(m_valueContent, out m_toggle, out _, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||||
|
toggleLayout.minWidth = 24;
|
||||||
|
|
||||||
|
m_toggle.onValueChanged.AddListener(OnToggleValueChanged);
|
||||||
|
|
||||||
|
m_baseLabel.transform.SetAsLastSibling();
|
||||||
|
|
||||||
|
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minWidth = 50;
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.flexibleWidth = 0;
|
||||||
|
m_applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
m_applyBtn.onClick.AddListener(() => { Owner.SetValue(); });
|
||||||
|
|
||||||
|
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
|
||||||
|
toggleObj.SetActive(false);
|
||||||
|
applyBtnObj.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,314 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveDictionary : InteractiveValue
|
||||||
|
{
|
||||||
|
public InteractiveDictionary(object value, Type valueType) : base(value, valueType)
|
||||||
|
{
|
||||||
|
if (valueType.IsGenericType)
|
||||||
|
{
|
||||||
|
var gArgs = valueType.GetGenericArguments();
|
||||||
|
m_typeOfKeys = gArgs[0];
|
||||||
|
m_typeofValues = gArgs[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_typeOfKeys = typeof(object);
|
||||||
|
m_typeofValues = typeof(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
public override bool HasSubContent => true;
|
||||||
|
// todo fix for il2cpp
|
||||||
|
public override bool SubContentWanted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_recacheWanted)
|
||||||
|
return true;
|
||||||
|
else return m_entries.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IDictionary RefIDictionary;
|
||||||
|
|
||||||
|
internal Type m_typeOfKeys;
|
||||||
|
internal Type m_typeofValues;
|
||||||
|
|
||||||
|
internal readonly List<KeyValuePair<CachePaired, CachePaired>> m_entries
|
||||||
|
= new List<KeyValuePair<CachePaired, CachePaired>>();
|
||||||
|
|
||||||
|
internal readonly KeyValuePair<CachePaired, CachePaired>[] m_displayedEntries
|
||||||
|
= new KeyValuePair<CachePaired, CachePaired>[ModConfig.Instance.Default_Page_Limit];
|
||||||
|
|
||||||
|
internal bool m_recacheWanted = true;
|
||||||
|
|
||||||
|
public override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
RefIDictionary = Value as IDictionary;
|
||||||
|
|
||||||
|
if (m_subContentParent.activeSelf)
|
||||||
|
{
|
||||||
|
GetCacheEntries();
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_recacheWanted = true;
|
||||||
|
|
||||||
|
base.OnValueUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnPageTurned()
|
||||||
|
{
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
GetDefaultLabel();
|
||||||
|
|
||||||
|
if (Value != null)
|
||||||
|
{
|
||||||
|
string count = "?";
|
||||||
|
if (m_recacheWanted && RefIDictionary != null)
|
||||||
|
count = RefIDictionary.Count.ToString();
|
||||||
|
else if (!m_recacheWanted)
|
||||||
|
count = m_entries.Count.ToString();
|
||||||
|
|
||||||
|
m_baseLabel.text = $"[{count}] {m_richValueType}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetCacheEntries()
|
||||||
|
{
|
||||||
|
if (m_entries.Any())
|
||||||
|
{
|
||||||
|
// maybe improve this, probably could be more efficient i guess
|
||||||
|
|
||||||
|
foreach (var pair in m_entries)
|
||||||
|
{
|
||||||
|
pair.Key.Destroy();
|
||||||
|
pair.Value.Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
if (RefIDictionary == null && Value != null)
|
||||||
|
RefIDictionary = EnumerateWithReflection();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (RefIDictionary != null)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
foreach (var key in RefIDictionary.Keys)
|
||||||
|
{
|
||||||
|
var value = RefIDictionary[key];
|
||||||
|
|
||||||
|
//if (index >= m_rowHolders.Count)
|
||||||
|
//{
|
||||||
|
// AddRowHolder();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//var holder = m_rowHolders[index];
|
||||||
|
|
||||||
|
var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent);
|
||||||
|
cacheKey.CreateIValue(key, this.m_typeOfKeys);
|
||||||
|
cacheKey.Disable();
|
||||||
|
|
||||||
|
var cacheValue = new CachePaired(index, this, this.RefIDictionary, PairTypes.Value, m_listContent);
|
||||||
|
cacheValue.CreateIValue(value, this.m_typeofValues);
|
||||||
|
cacheValue.Disable();
|
||||||
|
|
||||||
|
//holder.SetActive(false);
|
||||||
|
|
||||||
|
m_entries.Add(new KeyValuePair<CachePaired, CachePaired>(cacheKey, cacheValue));
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshDisplay()
|
||||||
|
{
|
||||||
|
var entries = m_entries;
|
||||||
|
m_pageHandler.ListCount = entries.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_displayedEntries.Length; i++)
|
||||||
|
{
|
||||||
|
var entry = m_displayedEntries[i];
|
||||||
|
if (entry.Key != null && entry.Value != null)
|
||||||
|
{
|
||||||
|
//m_rowHolders[i].SetActive(false);
|
||||||
|
entry.Key.Disable();
|
||||||
|
entry.Value.Disable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var itemIndex in m_pageHandler)
|
||||||
|
{
|
||||||
|
if (itemIndex >= entries.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var entry = entries[itemIndex];
|
||||||
|
m_displayedEntries[itemIndex - m_pageHandler.StartIndex] = entry;
|
||||||
|
|
||||||
|
//m_rowHolders[itemIndex].SetActive(true);
|
||||||
|
entry.Key.Enable();
|
||||||
|
entry.Value.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
//UpdateSubcontentHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void OnToggleSubcontent(bool active)
|
||||||
|
{
|
||||||
|
base.OnToggleSubcontent(active);
|
||||||
|
|
||||||
|
if (active && m_recacheWanted)
|
||||||
|
{
|
||||||
|
m_recacheWanted = false;
|
||||||
|
GetCacheEntries();
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region CPP fixes
|
||||||
|
#if CPP
|
||||||
|
// temp fix for Il2Cpp IDictionary until interfaces are fixed
|
||||||
|
private IDictionary EnumerateWithReflection()
|
||||||
|
{
|
||||||
|
var valueType = Value?.GetType() ?? FallbackType;
|
||||||
|
|
||||||
|
// get keys and values
|
||||||
|
var keys = valueType.GetProperty("Keys").GetValue(Value, null);
|
||||||
|
var values = valueType.GetProperty("Values").GetValue(Value, null);
|
||||||
|
|
||||||
|
// create lists to hold them
|
||||||
|
var keyList = new List<object>();
|
||||||
|
var valueList = new List<object>();
|
||||||
|
|
||||||
|
// store entries with reflection
|
||||||
|
EnumerateWithReflection(keys, keyList);
|
||||||
|
EnumerateWithReflection(values, valueList);
|
||||||
|
|
||||||
|
// make actual mono dictionary
|
||||||
|
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
|
||||||
|
.MakeGenericType(m_typeOfKeys, m_typeofValues));
|
||||||
|
|
||||||
|
// finally iterate into mono dictionary
|
||||||
|
for (int i = 0; i < keyList.Count; i++)
|
||||||
|
dict.Add(keyList[i], valueList[i]);
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnumerateWithReflection(object collection, List<object> list)
|
||||||
|
{
|
||||||
|
// invoke GetEnumerator
|
||||||
|
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
|
||||||
|
// get the type of it
|
||||||
|
var enumeratorType = enumerator.GetType();
|
||||||
|
// reflect MoveNext and Current
|
||||||
|
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||||
|
var current = enumeratorType.GetProperty("Current");
|
||||||
|
// iterate
|
||||||
|
while ((bool)moveNext.Invoke(enumerator, null))
|
||||||
|
{
|
||||||
|
list.Add(current.GetValue(enumerator, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal GameObject m_listContent;
|
||||||
|
internal LayoutElement m_listLayout;
|
||||||
|
|
||||||
|
internal PageHandler m_pageHandler;
|
||||||
|
|
||||||
|
//internal List<GameObject> m_rowHolders = new List<GameObject>();
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructSubcontent()
|
||||||
|
{
|
||||||
|
base.ConstructSubcontent();
|
||||||
|
|
||||||
|
m_pageHandler = new PageHandler(null);
|
||||||
|
m_pageHandler.ConstructUI(m_subContentParent);
|
||||||
|
m_pageHandler.OnPageChanged += OnPageTurned;
|
||||||
|
|
||||||
|
var scrollObj = UIFactory.CreateVerticalGroup(this.m_subContentParent, new Color(0.08f, 0.08f, 0.08f));
|
||||||
|
m_listContent = scrollObj;
|
||||||
|
|
||||||
|
var scrollRect = scrollObj.GetComponent<RectTransform>();
|
||||||
|
scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
|
||||||
|
|
||||||
|
m_listLayout = Owner.m_mainContent.GetComponent<LayoutElement>();
|
||||||
|
m_listLayout.minHeight = 25;
|
||||||
|
m_listLayout.flexibleHeight = 0;
|
||||||
|
Owner.m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||||
|
|
||||||
|
var scrollGroup = m_listContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
scrollGroup.childForceExpandHeight = true;
|
||||||
|
scrollGroup.childControlHeight = true;
|
||||||
|
scrollGroup.spacing = 2;
|
||||||
|
scrollGroup.padding.top = 5;
|
||||||
|
scrollGroup.padding.left = 5;
|
||||||
|
scrollGroup.padding.right = 5;
|
||||||
|
scrollGroup.padding.bottom = 5;
|
||||||
|
|
||||||
|
var contentFitter = scrollObj.AddComponent<ContentSizeFitter>();
|
||||||
|
contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||||
|
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
//internal void AddRowHolder()
|
||||||
|
//{
|
||||||
|
// var obj = UIFactory.CreateHorizontalGroup(m_listContent, new Color(0.15f, 0.15f, 0.15f));
|
||||||
|
|
||||||
|
// m_rowHolders.Add(obj);
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
161
src/Inspectors/Reflection/InteractiveValue/InteractiveEnum.cs
Normal file
161
src/Inspectors/Reflection/InteractiveValue/InteractiveEnum.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveEnum : InteractiveValue
|
||||||
|
{
|
||||||
|
internal static Dictionary<Type, KeyValuePair<int,string>[]> s_enumNamesCache = new Dictionary<Type, KeyValuePair<int, string>[]>();
|
||||||
|
|
||||||
|
public InteractiveEnum(object value, Type valueType) : base(value, valueType)
|
||||||
|
{
|
||||||
|
GetNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HasSubContent => true;
|
||||||
|
public override bool SubContentWanted => Owner.CanWrite;
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
|
||||||
|
internal KeyValuePair<int,string>[] m_values = new KeyValuePair<int, string>[0];
|
||||||
|
|
||||||
|
internal Type m_lastEnumType;
|
||||||
|
|
||||||
|
internal void GetNames()
|
||||||
|
{
|
||||||
|
var type = Value?.GetType() ?? FallbackType;
|
||||||
|
|
||||||
|
if (m_lastEnumType == type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_lastEnumType = type;
|
||||||
|
|
||||||
|
if (m_subContentConstructed)
|
||||||
|
{
|
||||||
|
// changing types, destroy subcontent
|
||||||
|
for (int i = 0; i < m_subContentParent.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
var child = m_subContentParent.transform.GetChild(i);
|
||||||
|
GameObject.Destroy(child.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_subContentConstructed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s_enumNamesCache.ContainsKey(type))
|
||||||
|
{
|
||||||
|
// using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags)
|
||||||
|
var values = Enum.GetValues(type);
|
||||||
|
|
||||||
|
var list = new List<KeyValuePair<int, string>>();
|
||||||
|
var set = new HashSet<string>();
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
var name = value.ToString();
|
||||||
|
if (set.Contains(name))
|
||||||
|
continue;
|
||||||
|
set.Add(name);
|
||||||
|
list.Add(new KeyValuePair<int, string>((int)value, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
s_enumNamesCache.Add(type, list.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_values = s_enumNamesCache[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
GetNames();
|
||||||
|
|
||||||
|
base.OnValueUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
base.RefreshUIForValue();
|
||||||
|
|
||||||
|
if (m_subContentConstructed)
|
||||||
|
{
|
||||||
|
m_dropdownText.text = Value?.ToString() ?? "<no value set>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void OnToggleSubcontent(bool toggle)
|
||||||
|
{
|
||||||
|
base.OnToggleSubcontent(toggle);
|
||||||
|
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetValueFromDropdown()
|
||||||
|
{
|
||||||
|
var type = Value?.GetType() ?? FallbackType;
|
||||||
|
var value = Enum.Parse(type, m_dropdownText.text);
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Owner.SetValue();
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Dropdown m_dropdown;
|
||||||
|
internal Text m_dropdownText;
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructSubcontent()
|
||||||
|
{
|
||||||
|
base.ConstructSubcontent();
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var groupObj = UIFactory.CreateHorizontalGroup(m_subContentParent, new Color(1, 1, 1, 0));
|
||||||
|
var group = groupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
group.padding.top = 3;
|
||||||
|
group.padding.left = 3;
|
||||||
|
group.padding.right = 3;
|
||||||
|
group.padding.bottom = 3;
|
||||||
|
group.spacing = 5;
|
||||||
|
|
||||||
|
// apply button
|
||||||
|
|
||||||
|
var applyObj = UIFactory.CreateButton(groupObj, new Color(0.3f, 0.3f, 0.3f));
|
||||||
|
var applyLayout = applyObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.minWidth = 50;
|
||||||
|
var applyText = applyObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
var applyBtn = applyObj.GetComponent<Button>();
|
||||||
|
applyBtn.onClick.AddListener(SetValueFromDropdown);
|
||||||
|
|
||||||
|
// dropdown
|
||||||
|
|
||||||
|
var dropdownObj = UIFactory.CreateDropdown(groupObj, out m_dropdown);
|
||||||
|
var dropLayout = dropdownObj.AddComponent<LayoutElement>();
|
||||||
|
dropLayout.minWidth = 150;
|
||||||
|
dropLayout.minHeight = 25;
|
||||||
|
dropLayout.flexibleWidth = 120;
|
||||||
|
|
||||||
|
foreach (var kvp in m_values)
|
||||||
|
{
|
||||||
|
m_dropdown.options.Add(new Dropdown.OptionData
|
||||||
|
{
|
||||||
|
text = $"{kvp.Key}: {kvp.Value}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dropdownText = m_dropdown.transform.Find("Label").GetComponent<Text>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,301 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveEnumerable : InteractiveValue
|
||||||
|
{
|
||||||
|
public InteractiveEnumerable(object value, Type valueType) : base(value, valueType)
|
||||||
|
{
|
||||||
|
if (valueType.IsGenericType)
|
||||||
|
m_baseEntryType = valueType.GetGenericArguments()[0];
|
||||||
|
else
|
||||||
|
m_baseEntryType = typeof(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
public override bool HasSubContent => true;
|
||||||
|
public override bool SubContentWanted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_recacheWanted)
|
||||||
|
return true;
|
||||||
|
else return m_entries.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IEnumerable RefIEnumerable;
|
||||||
|
internal IList RefIList;
|
||||||
|
|
||||||
|
internal readonly Type m_baseEntryType;
|
||||||
|
|
||||||
|
internal readonly List<CacheEnumerated> m_entries = new List<CacheEnumerated>();
|
||||||
|
internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ModConfig.Instance.Default_Page_Limit];
|
||||||
|
internal bool m_recacheWanted = true;
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
RefIEnumerable = Value as IEnumerable;
|
||||||
|
RefIList = Value as IList;
|
||||||
|
|
||||||
|
if (m_subContentParent.activeSelf)
|
||||||
|
{
|
||||||
|
GetCacheEntries();
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_recacheWanted = true;
|
||||||
|
|
||||||
|
base.OnValueUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnException(CacheMember member)
|
||||||
|
{
|
||||||
|
base.OnException(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageTurned()
|
||||||
|
{
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
GetDefaultLabel();
|
||||||
|
|
||||||
|
if (Value != null)
|
||||||
|
{
|
||||||
|
string count = "?";
|
||||||
|
if (m_recacheWanted && RefIList != null)
|
||||||
|
count = RefIList.Count.ToString();
|
||||||
|
else if (!m_recacheWanted)
|
||||||
|
count = m_entries.Count.ToString();
|
||||||
|
|
||||||
|
m_baseLabel.text = $"[{count}] {m_richValueType}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetCacheEntries()
|
||||||
|
{
|
||||||
|
if (m_entries.Any())
|
||||||
|
{
|
||||||
|
// maybe improve this, probably could be more efficient i guess
|
||||||
|
|
||||||
|
foreach (var entry in m_entries)
|
||||||
|
entry.Destroy();
|
||||||
|
|
||||||
|
m_entries.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
if (RefIEnumerable == null && Value != null)
|
||||||
|
RefIEnumerable = EnumerateWithReflection();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (RefIEnumerable != null)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
foreach (var entry in RefIEnumerable)
|
||||||
|
{
|
||||||
|
var cache = new CacheEnumerated(index, this, RefIList, this.m_listContent);
|
||||||
|
cache.CreateIValue(entry, m_baseEntryType);
|
||||||
|
m_entries.Add(cache);
|
||||||
|
|
||||||
|
cache.Disable();
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshDisplay()
|
||||||
|
{
|
||||||
|
var entries = m_entries;
|
||||||
|
m_pageHandler.ListCount = entries.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_displayedEntries.Length; i++)
|
||||||
|
{
|
||||||
|
var entry = m_displayedEntries[i];
|
||||||
|
if (entry != null)
|
||||||
|
entry.Disable();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var itemIndex in m_pageHandler)
|
||||||
|
{
|
||||||
|
if (itemIndex >= entries.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
CacheEnumerated entry = entries[itemIndex];
|
||||||
|
m_displayedEntries[itemIndex - m_pageHandler.StartIndex] = entry;
|
||||||
|
entry.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
//UpdateSubcontentHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void OnToggleSubcontent(bool active)
|
||||||
|
{
|
||||||
|
base.OnToggleSubcontent(active);
|
||||||
|
|
||||||
|
if (active && m_recacheWanted)
|
||||||
|
{
|
||||||
|
m_recacheWanted = false;
|
||||||
|
GetCacheEntries();
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region CPP Helpers
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
// some temp fixes for Il2Cpp IEnumerables until interfaces are fixed
|
||||||
|
|
||||||
|
private IEnumerable EnumerateWithReflection()
|
||||||
|
{
|
||||||
|
if (Value.IsNullOrDestroyed())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var genericDef = Value.GetType().GetGenericTypeDefinition();
|
||||||
|
|
||||||
|
if (genericDef == typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||||
|
return CppListToMono(genericDef);
|
||||||
|
else if (genericDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
|
||||||
|
return CppHashSetToMono();
|
||||||
|
else
|
||||||
|
return CppIListToMono();
|
||||||
|
}
|
||||||
|
|
||||||
|
// List<T>.ToArray()
|
||||||
|
private IEnumerable CppListToMono(Type genericTypeDef)
|
||||||
|
{
|
||||||
|
if (genericTypeDef == null) return null;
|
||||||
|
|
||||||
|
return genericTypeDef
|
||||||
|
.MakeGenericType(new Type[] { this.m_baseEntryType })
|
||||||
|
.GetMethod("ToArray")
|
||||||
|
.Invoke(Value, new object[0]) as IEnumerable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashSet.GetEnumerator
|
||||||
|
private IEnumerable CppHashSetToMono()
|
||||||
|
{
|
||||||
|
var set = new HashSet<object>();
|
||||||
|
|
||||||
|
// invoke GetEnumerator
|
||||||
|
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
|
||||||
|
// get the type of it
|
||||||
|
var enumeratorType = enumerator.GetType();
|
||||||
|
// reflect MoveNext and Current
|
||||||
|
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||||
|
var current = enumeratorType.GetProperty("Current");
|
||||||
|
// iterate
|
||||||
|
while ((bool)moveNext.Invoke(enumerator, null))
|
||||||
|
set.Add(current.GetValue(enumerator));
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IList.Item
|
||||||
|
private IList CppIListToMono()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.m_baseEntryType });
|
||||||
|
var list = (IList)Activator.CreateInstance(genericType);
|
||||||
|
|
||||||
|
for (int i = 0; ; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var itm = Value?.GetType()
|
||||||
|
.GetProperty("Item")
|
||||||
|
.GetValue(Value, new object[] { i });
|
||||||
|
list.Add(itm);
|
||||||
|
}
|
||||||
|
catch { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal GameObject m_listContent;
|
||||||
|
internal LayoutElement m_listLayout;
|
||||||
|
|
||||||
|
internal PageHandler m_pageHandler;
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructSubcontent()
|
||||||
|
{
|
||||||
|
base.ConstructSubcontent();
|
||||||
|
|
||||||
|
m_pageHandler = new PageHandler(null);
|
||||||
|
m_pageHandler.ConstructUI(m_subContentParent);
|
||||||
|
m_pageHandler.OnPageChanged += OnPageTurned;
|
||||||
|
|
||||||
|
var scrollObj = UIFactory.CreateVerticalGroup(this.m_subContentParent, new Color(0.08f, 0.08f, 0.08f));
|
||||||
|
m_listContent = scrollObj;
|
||||||
|
|
||||||
|
var scrollRect = scrollObj.GetComponent<RectTransform>();
|
||||||
|
scrollRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
|
||||||
|
|
||||||
|
m_listLayout = Owner.m_mainContent.GetComponent<LayoutElement>();
|
||||||
|
m_listLayout.minHeight = 25;
|
||||||
|
m_listLayout.flexibleHeight = 0;
|
||||||
|
Owner.m_mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||||
|
|
||||||
|
var scrollGroup = m_listContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
scrollGroup.childForceExpandHeight = true;
|
||||||
|
scrollGroup.childControlHeight = true;
|
||||||
|
scrollGroup.spacing = 2;
|
||||||
|
scrollGroup.padding.top = 5;
|
||||||
|
scrollGroup.padding.left = 5;
|
||||||
|
scrollGroup.padding.right = 5;
|
||||||
|
scrollGroup.padding.bottom = 5;
|
||||||
|
|
||||||
|
var contentFitter = scrollObj.AddComponent<ContentSizeFitter>();
|
||||||
|
contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||||
|
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
146
src/Inspectors/Reflection/InteractiveValue/InteractiveFlags.cs
Normal file
146
src/Inspectors/Reflection/InteractiveValue/InteractiveFlags.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveFlags : InteractiveEnum
|
||||||
|
{
|
||||||
|
public InteractiveFlags(object value, Type valueType) : base(value, valueType)
|
||||||
|
{
|
||||||
|
m_toggles = new Toggle[m_values.Length];
|
||||||
|
m_enabledFlags = new bool[m_values.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HasSubContent => true;
|
||||||
|
public override bool SubContentWanted => Owner.CanWrite;
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
|
||||||
|
internal bool[] m_enabledFlags;
|
||||||
|
internal Toggle[] m_toggles;
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
base.OnValueUpdated();
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var enabledNames = new List<string>();
|
||||||
|
|
||||||
|
var enabled = Value?.ToString().Split(',').Select(it => it.Trim());
|
||||||
|
if (enabled != null)
|
||||||
|
enabledNames.AddRange(enabled);
|
||||||
|
|
||||||
|
for (int i = 0; i < m_values.Length; i++)
|
||||||
|
{
|
||||||
|
m_enabledFlags[i] = enabledNames.Contains(m_values[i].Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
GetDefaultLabel();
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
|
||||||
|
if (m_subContentConstructed)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_values.Length; i++)
|
||||||
|
{
|
||||||
|
var toggle = m_toggles[i];
|
||||||
|
if (toggle.isOn != m_enabledFlags[i])
|
||||||
|
toggle.isOn = m_enabledFlags[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetValueFromToggles()
|
||||||
|
{
|
||||||
|
string val = "";
|
||||||
|
for (int i = 0; i < m_values.Length; i++)
|
||||||
|
{
|
||||||
|
if (m_enabledFlags[i])
|
||||||
|
{
|
||||||
|
if (val != "") val += ", ";
|
||||||
|
val += m_values[i].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var type = Value?.GetType() ?? FallbackType;
|
||||||
|
Value = Enum.Parse(type, val);
|
||||||
|
RefreshUIForValue();
|
||||||
|
Owner.SetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void OnToggleSubcontent(bool toggle)
|
||||||
|
{
|
||||||
|
base.OnToggleSubcontent(toggle);
|
||||||
|
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructSubcontent()
|
||||||
|
{
|
||||||
|
m_subContentConstructed = true;
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, new Color(1, 1, 1, 0));
|
||||||
|
var group = groupObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
group.childForceExpandHeight = true;
|
||||||
|
group.childForceExpandWidth = false;
|
||||||
|
group.childControlHeight = true;
|
||||||
|
group.childControlWidth = true;
|
||||||
|
group.padding.top = 3;
|
||||||
|
group.padding.left = 3;
|
||||||
|
group.padding.right = 3;
|
||||||
|
group.padding.bottom = 3;
|
||||||
|
group.spacing = 5;
|
||||||
|
|
||||||
|
// apply button
|
||||||
|
|
||||||
|
var applyObj = UIFactory.CreateButton(groupObj, new Color(0.3f, 0.3f, 0.3f));
|
||||||
|
var applyLayout = applyObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.minWidth = 50;
|
||||||
|
var applyText = applyObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
var applyBtn = applyObj.GetComponent<Button>();
|
||||||
|
applyBtn.onClick.AddListener(SetValueFromToggles);
|
||||||
|
|
||||||
|
// toggles
|
||||||
|
|
||||||
|
for (int i = 0; i < m_values.Length; i++)
|
||||||
|
{
|
||||||
|
AddToggle(i, groupObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddToggle(int index, GameObject groupObj)
|
||||||
|
{
|
||||||
|
var value = m_values[index];
|
||||||
|
|
||||||
|
var toggleObj = UIFactory.CreateToggle(groupObj, out Toggle toggle, out Text text, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||||
|
toggleLayout.minWidth = 100;
|
||||||
|
toggleLayout.flexibleWidth = 2000;
|
||||||
|
toggleLayout.minHeight = 25;
|
||||||
|
|
||||||
|
m_toggles[index] = toggle;
|
||||||
|
|
||||||
|
toggle.onValueChanged.AddListener((bool val) => { m_enabledFlags[index] = val; });
|
||||||
|
|
||||||
|
text.text = $"{value.Key}: {value.Value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
src/Inspectors/Reflection/InteractiveValue/InteractiveNumber.cs
Normal file
126
src/Inspectors/Reflection/InteractiveValue/InteractiveNumber.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveNumber : InteractiveValue
|
||||||
|
{
|
||||||
|
public InteractiveNumber(object value, Type valueType) : base(value, valueType) { }
|
||||||
|
|
||||||
|
public override bool HasSubContent => false;
|
||||||
|
public override bool SubContentWanted => false;
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
base.OnValueUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnException(CacheMember member)
|
||||||
|
{
|
||||||
|
base.OnException(member);
|
||||||
|
|
||||||
|
if (m_valueInput.gameObject.activeSelf)
|
||||||
|
m_valueInput.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
if (m_applyBtn.gameObject.activeSelf)
|
||||||
|
m_applyBtn.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
if (!Owner.HasEvaluated)
|
||||||
|
{
|
||||||
|
GetDefaultLabel();
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_baseLabel.text = UISyntaxHighlight.ParseFullSyntax(FallbackType, false);
|
||||||
|
m_valueInput.text = Value.ToString();
|
||||||
|
|
||||||
|
var type = Value.GetType();
|
||||||
|
if (type == typeof(float)
|
||||||
|
|| type == typeof(double)
|
||||||
|
|| type == typeof(decimal))
|
||||||
|
{
|
||||||
|
m_valueInput.characterValidation = InputField.CharacterValidation.Decimal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_valueInput.characterValidation = InputField.CharacterValidation.Integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
if (!m_applyBtn.gameObject.activeSelf)
|
||||||
|
m_applyBtn.gameObject.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_valueInput.gameObject.activeSelf)
|
||||||
|
m_valueInput.gameObject.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
|
||||||
|
private MethodInfo m_parseMethod;
|
||||||
|
|
||||||
|
internal void OnApplyClicked()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Value = ParseMethod.Invoke(null, new object[] { m_valueInput.text });
|
||||||
|
Owner.SetValue();
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Could not parse input! " + ReflectionHelpers.ExceptionToString(e, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal InputField m_valueInput;
|
||||||
|
internal Button m_applyBtn;
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
|
||||||
|
var labelLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 50;
|
||||||
|
labelLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateInputField(m_valueContent);
|
||||||
|
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||||
|
inputLayout.minWidth = 120;
|
||||||
|
inputLayout.minHeight = 25;
|
||||||
|
inputLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
m_valueInput = inputObj.GetComponent<InputField>();
|
||||||
|
m_valueInput.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minWidth = 50;
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.flexibleWidth = 0;
|
||||||
|
m_applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
m_applyBtn.onClick.AddListener(OnApplyClicked);
|
||||||
|
|
||||||
|
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
src/Inspectors/Reflection/InteractiveValue/InteractiveString.cs
Normal file
149
src/Inspectors/Reflection/InteractiveValue/InteractiveString.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveString : InteractiveValue
|
||||||
|
{
|
||||||
|
public InteractiveString(object value, Type valueType) : base(value, valueType) { }
|
||||||
|
|
||||||
|
public override bool HasSubContent => false;
|
||||||
|
public override bool SubContentWanted => false;
|
||||||
|
public override bool WantInspectBtn => false;
|
||||||
|
|
||||||
|
public override void OnValueUpdated()
|
||||||
|
{
|
||||||
|
base.OnValueUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnException(CacheMember member)
|
||||||
|
{
|
||||||
|
base.OnException(member);
|
||||||
|
|
||||||
|
if (m_hiddenObj.gameObject.activeSelf)
|
||||||
|
m_hiddenObj.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
// m_baseLabel.text = DefaultLabel;
|
||||||
|
m_labelLayout.minWidth = 200;
|
||||||
|
m_labelLayout.flexibleWidth = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
GetDefaultLabel(false);
|
||||||
|
|
||||||
|
if (!Owner.HasEvaluated)
|
||||||
|
{
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_hiddenObj.gameObject.activeSelf)
|
||||||
|
m_hiddenObj.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
m_baseLabel.text = m_richValueType;
|
||||||
|
|
||||||
|
if (Value != null)
|
||||||
|
{
|
||||||
|
var toString = Value.ToString();
|
||||||
|
if (toString.Length > 15000)
|
||||||
|
toString = toString.Substring(0, 15000);
|
||||||
|
|
||||||
|
m_valueInput.text = toString;
|
||||||
|
m_placeholderText.text = toString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_valueInput.text = "";
|
||||||
|
m_placeholderText.text = "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_labelLayout.minWidth = 50;
|
||||||
|
m_labelLayout.flexibleWidth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal void OnApplyClicked()
|
||||||
|
{
|
||||||
|
Value = m_valueInput.text;
|
||||||
|
Owner.SetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal InputField m_valueInput;
|
||||||
|
internal LayoutElement m_labelLayout;
|
||||||
|
internal GameObject m_hiddenObj;
|
||||||
|
internal Text m_placeholderText;
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
|
||||||
|
GetDefaultLabel(false);
|
||||||
|
m_richValueType = UISyntaxHighlight.ParseFullSyntax(FallbackType, false);
|
||||||
|
|
||||||
|
m_labelLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
|
||||||
|
|
||||||
|
m_hiddenObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
|
||||||
|
m_hiddenObj.SetActive(false);
|
||||||
|
var hiddenText = m_hiddenObj.GetComponent<Text>();
|
||||||
|
hiddenText.color = Color.clear;
|
||||||
|
hiddenText.fontSize = 14;
|
||||||
|
hiddenText.raycastTarget = false;
|
||||||
|
var hiddenFitter = m_hiddenObj.AddComponent<ContentSizeFitter>();
|
||||||
|
hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
var hiddenLayout = m_hiddenObj.AddComponent<LayoutElement>();
|
||||||
|
hiddenLayout.minHeight = 25;
|
||||||
|
hiddenLayout.flexibleHeight = 500;
|
||||||
|
hiddenLayout.minWidth = 250;
|
||||||
|
hiddenLayout.flexibleWidth = 9000;
|
||||||
|
var hiddenGroup = m_hiddenObj.AddComponent<HorizontalLayoutGroup>();
|
||||||
|
hiddenGroup.childForceExpandWidth = true;
|
||||||
|
hiddenGroup.childControlWidth = true;
|
||||||
|
hiddenGroup.childForceExpandHeight = true;
|
||||||
|
hiddenGroup.childControlHeight = true;
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateInputField(m_hiddenObj, 14, 3);
|
||||||
|
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||||
|
inputLayout.minWidth = 120;
|
||||||
|
inputLayout.minHeight = 25;
|
||||||
|
inputLayout.flexibleWidth = 5000;
|
||||||
|
inputLayout.flexibleHeight = 5000;
|
||||||
|
|
||||||
|
m_valueInput = inputObj.GetComponent<InputField>();
|
||||||
|
m_valueInput.lineType = InputField.LineType.MultiLineNewline;
|
||||||
|
|
||||||
|
m_placeholderText = m_valueInput.placeholder.GetComponent<Text>();
|
||||||
|
|
||||||
|
m_valueInput.onValueChanged.AddListener((string val) =>
|
||||||
|
{
|
||||||
|
hiddenText.text = val;
|
||||||
|
LayoutRebuilder.ForceRebuildLayoutImmediate(Owner.m_mainRect);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minWidth = 50;
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.flexibleWidth = 0;
|
||||||
|
var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
applyBtn.onClick.AddListener(OnApplyClicked);
|
||||||
|
|
||||||
|
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_valueInput.readOnly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,337 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
#region IStructInfo helper
|
||||||
|
|
||||||
|
public interface IStructInfo
|
||||||
|
{
|
||||||
|
string[] FieldNames { get; }
|
||||||
|
object SetValue(ref object value, int fieldIndex, float val);
|
||||||
|
void RefreshUI(InputField[] inputs, object value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StructInfo<T> : IStructInfo where T : struct
|
||||||
|
{
|
||||||
|
public string[] FieldNames { get; set; }
|
||||||
|
|
||||||
|
public delegate void SetMethod(ref T value, int fieldIndex, float val);
|
||||||
|
public SetMethod SetValueMethod;
|
||||||
|
|
||||||
|
public delegate void UpdateMethod(InputField[] inputs, object value);
|
||||||
|
public UpdateMethod UpdateUIMethod;
|
||||||
|
|
||||||
|
public object SetValue(ref object value, int fieldIndex, float val)
|
||||||
|
{
|
||||||
|
var box = (T)value;
|
||||||
|
SetValueMethod.Invoke(ref box, fieldIndex, val);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshUI(InputField[] inputs, object value)
|
||||||
|
{
|
||||||
|
UpdateUIMethod.Invoke(inputs, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This part is a bit ugly, but everything else is generalized above.
|
||||||
|
// I could generalize it more with reflection, but it would be different for
|
||||||
|
// mono/il2cpp and also slower.
|
||||||
|
public static class StructInfoFactory
|
||||||
|
{
|
||||||
|
public static IStructInfo Create(Type type)
|
||||||
|
{
|
||||||
|
if (type == typeof(Vector2))
|
||||||
|
{
|
||||||
|
return new StructInfo<Vector2>()
|
||||||
|
{
|
||||||
|
FieldNames = new[] { "x", "y", },
|
||||||
|
SetValueMethod = (ref Vector2 vec, int fieldIndex, float val) =>
|
||||||
|
{
|
||||||
|
switch (fieldIndex)
|
||||||
|
{
|
||||||
|
case 0: vec.x = val; break;
|
||||||
|
case 1: vec.y = val; break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||||
|
{
|
||||||
|
Vector2 vec = (Vector2)value;
|
||||||
|
inputs[0].text = vec.x.ToString();
|
||||||
|
inputs[1].text = vec.y.ToString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector3))
|
||||||
|
{
|
||||||
|
return new StructInfo<Vector3>()
|
||||||
|
{
|
||||||
|
FieldNames = new[] { "x", "y", "z" },
|
||||||
|
SetValueMethod = (ref Vector3 vec, int fieldIndex, float val) =>
|
||||||
|
{
|
||||||
|
switch (fieldIndex)
|
||||||
|
{
|
||||||
|
case 0: vec.x = val; break;
|
||||||
|
case 1: vec.y = val; break;
|
||||||
|
case 2: vec.z = val; break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||||
|
{
|
||||||
|
Vector3 vec = (Vector3)value;
|
||||||
|
inputs[0].text = vec.x.ToString();
|
||||||
|
inputs[1].text = vec.y.ToString();
|
||||||
|
inputs[2].text = vec.z.ToString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector4))
|
||||||
|
{
|
||||||
|
return new StructInfo<Vector4>()
|
||||||
|
{
|
||||||
|
FieldNames = new[] { "x", "y", "z", "w" },
|
||||||
|
SetValueMethod = (ref Vector4 vec, int fieldIndex, float val) =>
|
||||||
|
{
|
||||||
|
switch (fieldIndex)
|
||||||
|
{
|
||||||
|
case 0: vec.x = val; break;
|
||||||
|
case 1: vec.y = val; break;
|
||||||
|
case 2: vec.z = val; break;
|
||||||
|
case 3: vec.w = val; break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||||
|
{
|
||||||
|
Vector4 vec = (Vector4)value;
|
||||||
|
inputs[0].text = vec.x.ToString();
|
||||||
|
inputs[1].text = vec.y.ToString();
|
||||||
|
inputs[2].text = vec.z.ToString();
|
||||||
|
inputs[3].text = vec.w.ToString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (type == typeof(Rect))
|
||||||
|
{
|
||||||
|
return new StructInfo<Rect>()
|
||||||
|
{
|
||||||
|
FieldNames = new[] { "x", "y", "width", "height" },
|
||||||
|
SetValueMethod = (ref Rect vec, int fieldIndex, float val) =>
|
||||||
|
{
|
||||||
|
switch (fieldIndex)
|
||||||
|
{
|
||||||
|
case 0: vec.x = val; break;
|
||||||
|
case 1: vec.y = val; break;
|
||||||
|
case 2: vec.width = val; break;
|
||||||
|
case 3: vec.height = val; break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||||
|
{
|
||||||
|
Rect vec = (Rect)value;
|
||||||
|
inputs[0].text = vec.x.ToString();
|
||||||
|
inputs[1].text = vec.y.ToString();
|
||||||
|
inputs[2].text = vec.width.ToString();
|
||||||
|
inputs[3].text = vec.height.ToString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (type == typeof(Color))
|
||||||
|
{
|
||||||
|
return new StructInfo<Color>()
|
||||||
|
{
|
||||||
|
FieldNames = new[] { "r", "g", "b", "a" },
|
||||||
|
SetValueMethod = (ref Color vec, int fieldIndex, float val) =>
|
||||||
|
{
|
||||||
|
switch (fieldIndex)
|
||||||
|
{
|
||||||
|
case 0: vec.r = val; break;
|
||||||
|
case 1: vec.g = val; break;
|
||||||
|
case 2: vec.b = val; break;
|
||||||
|
case 3: vec.a = val; break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateUIMethod = (InputField[] inputs, object value) =>
|
||||||
|
{
|
||||||
|
Color vec = (Color)value;
|
||||||
|
inputs[0].text = vec.r.ToString();
|
||||||
|
inputs[1].text = vec.g.ToString();
|
||||||
|
inputs[2].text = vec.b.ToString();
|
||||||
|
inputs[3].text = vec.a.ToString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public class InteractiveUnityStruct : InteractiveValue
|
||||||
|
{
|
||||||
|
public static bool SupportsType(Type type) => s_supportedTypes.Contains(type);
|
||||||
|
private static readonly HashSet<Type> s_supportedTypes = new HashSet<Type>
|
||||||
|
{
|
||||||
|
typeof(Vector2),
|
||||||
|
typeof(Vector3),
|
||||||
|
typeof(Vector4),
|
||||||
|
typeof(Rect),
|
||||||
|
typeof(Color) // todo might make a special editor for colors
|
||||||
|
};
|
||||||
|
|
||||||
|
//~~~~~~~~~ Instance ~~~~~~~~~~
|
||||||
|
|
||||||
|
public InteractiveUnityStruct(object value, Type valueType) : base(value, valueType) { }
|
||||||
|
|
||||||
|
public override bool HasSubContent => true;
|
||||||
|
public override bool SubContentWanted => true;
|
||||||
|
public override bool WantInspectBtn => true;
|
||||||
|
|
||||||
|
public IStructInfo StructInfo;
|
||||||
|
|
||||||
|
public override void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
InitializeStructInfo();
|
||||||
|
|
||||||
|
base.RefreshUIForValue();
|
||||||
|
|
||||||
|
if (m_subContentConstructed)
|
||||||
|
StructInfo.RefreshUI(m_inputs, this.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void OnToggleSubcontent(bool toggle)
|
||||||
|
{
|
||||||
|
InitializeStructInfo();
|
||||||
|
|
||||||
|
base.OnToggleSubcontent(toggle);
|
||||||
|
|
||||||
|
StructInfo.RefreshUI(m_inputs, this.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Type m_lastStructType;
|
||||||
|
|
||||||
|
internal void InitializeStructInfo()
|
||||||
|
{
|
||||||
|
var type = Value?.GetType() ?? FallbackType;
|
||||||
|
|
||||||
|
if (StructInfo != null && type == m_lastStructType)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (StructInfo != null)
|
||||||
|
{
|
||||||
|
// changing types, destroy subcontent
|
||||||
|
for (int i = 0; i < m_subContentParent.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
var child = m_subContentParent.transform.GetChild(i);
|
||||||
|
GameObject.Destroy(child.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_UIConstructed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastStructType = type;
|
||||||
|
|
||||||
|
StructInfo = StructInfoFactory.Create(type);
|
||||||
|
|
||||||
|
if (m_subContentParent.activeSelf)
|
||||||
|
{
|
||||||
|
ConstructSubcontent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal InputField[] m_inputs;
|
||||||
|
|
||||||
|
public override void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
base.ConstructUI(parent, subGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConstructSubcontent()
|
||||||
|
{
|
||||||
|
base.ConstructSubcontent();
|
||||||
|
|
||||||
|
if (StructInfo == null)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Setting up subcontent but structinfo is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var editorContainer = UIFactory.CreateVerticalGroup(m_subContentParent, new Color(0.08f, 0.08f, 0.08f));
|
||||||
|
var editorGroup = editorContainer.GetComponent<VerticalLayoutGroup>();
|
||||||
|
editorGroup.childForceExpandWidth = false;
|
||||||
|
editorGroup.padding.top = 4;
|
||||||
|
editorGroup.padding.right = 4;
|
||||||
|
editorGroup.padding.left = 4;
|
||||||
|
editorGroup.padding.bottom = 4;
|
||||||
|
editorGroup.spacing = 2;
|
||||||
|
|
||||||
|
m_inputs = new InputField[StructInfo.FieldNames.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < StructInfo.FieldNames.Length; i++)
|
||||||
|
{
|
||||||
|
AddEditorRow(i, editorContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.CanWrite)
|
||||||
|
{
|
||||||
|
var applyBtnObj = UIFactory.CreateButton(editorContainer, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minWidth = 175;
|
||||||
|
applyLayout.minHeight = 25;
|
||||||
|
applyLayout.flexibleWidth = 0;
|
||||||
|
var m_applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
m_applyBtn.onClick.AddListener(OnSetValue);
|
||||||
|
|
||||||
|
void OnSetValue()
|
||||||
|
{
|
||||||
|
Owner.SetValue();
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddEditorRow(int index, GameObject groupObj)
|
||||||
|
{
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(groupObj, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childForceExpandHeight = true;
|
||||||
|
rowGroup.childForceExpandWidth = false;
|
||||||
|
rowGroup.spacing = 5;
|
||||||
|
|
||||||
|
var label = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleRight);
|
||||||
|
var labelLayout = label.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 50;
|
||||||
|
labelLayout.flexibleWidth = 0;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
var labelText = label.GetComponent<Text>();
|
||||||
|
labelText.text = $"{StructInfo.FieldNames[index]}:";
|
||||||
|
labelText.color = Color.cyan;
|
||||||
|
|
||||||
|
var inputFieldObj = UIFactory.CreateInputField(rowObj, 14, 3, 1);
|
||||||
|
var inputField = inputFieldObj.GetComponent<InputField>();
|
||||||
|
inputField.characterValidation = InputField.CharacterValidation.Decimal;
|
||||||
|
var inputLayout = inputFieldObj.AddComponent<LayoutElement>();
|
||||||
|
inputLayout.flexibleWidth = 0;
|
||||||
|
inputLayout.minWidth = 120;
|
||||||
|
inputLayout.minHeight = 25;
|
||||||
|
|
||||||
|
m_inputs[index] = inputField;
|
||||||
|
|
||||||
|
inputField.onValueChanged.AddListener((string val) => { Value = StructInfo.SetValue(ref this.Value, index, float.Parse(val)); });
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
318
src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs
Normal file
318
src/Inspectors/Reflection/InteractiveValue/InteractiveValue.cs
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class InteractiveValue
|
||||||
|
{
|
||||||
|
public static Type GetIValueForType(Type type)
|
||||||
|
{
|
||||||
|
if (type == typeof(bool))
|
||||||
|
return typeof(InteractiveBool);
|
||||||
|
else if (type == typeof(string))
|
||||||
|
return typeof(InteractiveString);
|
||||||
|
else if (type.IsPrimitive)
|
||||||
|
return typeof(InteractiveNumber);
|
||||||
|
else if (typeof(Enum).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
if (type.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
|
||||||
|
return typeof(InteractiveFlags);
|
||||||
|
else
|
||||||
|
return typeof(InteractiveEnum);
|
||||||
|
}
|
||||||
|
else if (InteractiveUnityStruct.SupportsType(type))
|
||||||
|
return typeof(InteractiveUnityStruct);
|
||||||
|
else if (typeof(Transform).IsAssignableFrom(type))
|
||||||
|
return typeof(InteractiveValue);
|
||||||
|
else if (ReflectionHelpers.IsDictionary(type))
|
||||||
|
return typeof(InteractiveDictionary);
|
||||||
|
else if (ReflectionHelpers.IsEnumerable(type))
|
||||||
|
return typeof(InteractiveEnumerable);
|
||||||
|
else
|
||||||
|
return typeof(InteractiveValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InteractiveValue Create(object value, Type fallbackType)
|
||||||
|
{
|
||||||
|
var type = ReflectionHelpers.GetActualType(value) ?? fallbackType;
|
||||||
|
var iType = GetIValueForType(type);
|
||||||
|
|
||||||
|
return (InteractiveValue)Activator.CreateInstance(iType, new object[] { value, type });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~~~ Instance ~~~~~~~~~
|
||||||
|
|
||||||
|
public InteractiveValue(object value, Type valueType)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
this.FallbackType = valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CacheObjectBase Owner;
|
||||||
|
|
||||||
|
public object Value;
|
||||||
|
public readonly Type FallbackType;
|
||||||
|
|
||||||
|
public virtual bool HasSubContent => false;
|
||||||
|
public virtual bool SubContentWanted => false;
|
||||||
|
public virtual bool WantInspectBtn => true;
|
||||||
|
|
||||||
|
public string DefaultLabel => m_defaultLabel ?? GetDefaultLabel();
|
||||||
|
internal string m_defaultLabel;
|
||||||
|
internal string m_richValueType;
|
||||||
|
|
||||||
|
public bool m_UIConstructed;
|
||||||
|
|
||||||
|
public virtual void OnDestroy()
|
||||||
|
{
|
||||||
|
if (this.m_valueContent)
|
||||||
|
{
|
||||||
|
m_valueContent.transform.SetParent(null, false);
|
||||||
|
m_valueContent.SetActive(false);
|
||||||
|
GameObject.Destroy(this.m_valueContent.gameObject);
|
||||||
|
}
|
||||||
|
if (this.m_subContentParent && SubContentWanted)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < this.m_subContentParent.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
var child = m_subContentParent.transform.GetChild(i);
|
||||||
|
if (child)
|
||||||
|
GameObject.Destroy(child.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnValueUpdated()
|
||||||
|
{
|
||||||
|
if (!m_UIConstructed)
|
||||||
|
ConstructUI(m_mainContentParent, m_subContentParent);
|
||||||
|
|
||||||
|
if (Owner is CacheMember ownerMember && !string.IsNullOrEmpty(ownerMember.ReflectionException))
|
||||||
|
{
|
||||||
|
OnException(ownerMember);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RefreshUIForValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnException(CacheMember member)
|
||||||
|
{
|
||||||
|
m_baseLabel.text = "<color=red>" + member.ReflectionException + "</color>";
|
||||||
|
Value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RefreshUIForValue()
|
||||||
|
{
|
||||||
|
GetDefaultLabel();
|
||||||
|
m_baseLabel.text = DefaultLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshElementsAfterUpdate()
|
||||||
|
{
|
||||||
|
if (WantInspectBtn)
|
||||||
|
{
|
||||||
|
bool shouldShowInspect = !Value.IsNullOrDestroyed();
|
||||||
|
|
||||||
|
if (m_inspectButton.activeSelf != shouldShowInspect)
|
||||||
|
m_inspectButton.SetActive(shouldShowInspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool subContentWanted = SubContentWanted;
|
||||||
|
if (Owner is CacheMember cm && (!cm.HasEvaluated || !string.IsNullOrEmpty(cm.ReflectionException)))
|
||||||
|
subContentWanted = false;
|
||||||
|
|
||||||
|
if (HasSubContent)
|
||||||
|
{
|
||||||
|
if (m_subExpandBtn.gameObject.activeSelf != subContentWanted)
|
||||||
|
m_subExpandBtn.gameObject.SetActive(subContentWanted);
|
||||||
|
|
||||||
|
if (!subContentWanted && m_subContentParent.activeSelf)
|
||||||
|
ToggleSubcontent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ConstructSubcontent()
|
||||||
|
{
|
||||||
|
m_subContentConstructed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleSubcontent()
|
||||||
|
{
|
||||||
|
if (!this.m_subContentParent.activeSelf)
|
||||||
|
{
|
||||||
|
this.m_subContentParent.SetActive(true);
|
||||||
|
this.m_subContentParent.transform.SetAsLastSibling();
|
||||||
|
m_subExpandBtn.GetComponentInChildren<Text>().text = "▼";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.m_subContentParent.SetActive(false);
|
||||||
|
m_subExpandBtn.GetComponentInChildren<Text>().text = "▲";
|
||||||
|
}
|
||||||
|
|
||||||
|
OnToggleSubcontent(m_subContentParent.activeSelf);
|
||||||
|
|
||||||
|
RefreshElementsAfterUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual void OnToggleSubcontent(bool toggle)
|
||||||
|
{
|
||||||
|
if (!m_subContentConstructed)
|
||||||
|
ConstructSubcontent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDefaultLabel(bool updateType = true)
|
||||||
|
{
|
||||||
|
var valueType = Value?.GetType() ?? this.FallbackType;
|
||||||
|
if (updateType)
|
||||||
|
m_richValueType = UISyntaxHighlight.ParseFullSyntax(valueType, true);
|
||||||
|
|
||||||
|
if (!Owner.HasEvaluated)
|
||||||
|
return m_defaultLabel = $"<i><color=grey>Not yet evaluated</color> ({m_richValueType})</i>";
|
||||||
|
|
||||||
|
if (Value.IsNullOrDestroyed())
|
||||||
|
return m_defaultLabel = $"<color=grey>null</color> ({m_richValueType})";
|
||||||
|
|
||||||
|
string label;
|
||||||
|
|
||||||
|
if (Value is TextAsset textAsset)
|
||||||
|
{
|
||||||
|
label = textAsset.text;
|
||||||
|
|
||||||
|
if (label.Length > 10)
|
||||||
|
label = $"{label.Substring(0, 10)}...";
|
||||||
|
|
||||||
|
label = $"\"{label}\" {textAsset.name} ({m_richValueType})";
|
||||||
|
}
|
||||||
|
else if (Value is EventSystem)
|
||||||
|
{
|
||||||
|
label = m_richValueType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var toString = (string)valueType.GetMethod("ToString", new Type[0])?.Invoke(Value, null)
|
||||||
|
?? Value.ToString();
|
||||||
|
|
||||||
|
var fullnametemp = valueType.ToString();
|
||||||
|
if (fullnametemp.StartsWith("Il2CppSystem"))
|
||||||
|
fullnametemp = fullnametemp.Substring(6, fullnametemp.Length - 6);
|
||||||
|
|
||||||
|
var temp = toString.Replace(fullnametemp, "").Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(temp))
|
||||||
|
{
|
||||||
|
label = m_richValueType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (toString.Length > 200)
|
||||||
|
toString = toString.Substring(0, 200) + "...";
|
||||||
|
|
||||||
|
label = toString;
|
||||||
|
|
||||||
|
var unityType = $"({valueType.FullName})";
|
||||||
|
if (Value is UnityEngine.Object && label.Contains(unityType))
|
||||||
|
label = label.Replace(unityType, $"({m_richValueType})");
|
||||||
|
else
|
||||||
|
label += $" ({m_richValueType})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_defaultLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal GameObject m_mainContentParent;
|
||||||
|
internal GameObject m_subContentParent;
|
||||||
|
|
||||||
|
internal GameObject m_valueContent;
|
||||||
|
internal GameObject m_inspectButton;
|
||||||
|
internal Text m_baseLabel;
|
||||||
|
|
||||||
|
internal Button m_subExpandBtn;
|
||||||
|
internal bool m_subContentConstructed;
|
||||||
|
|
||||||
|
public virtual void ConstructUI(GameObject parent, GameObject subGroup)
|
||||||
|
{
|
||||||
|
m_UIConstructed = true;
|
||||||
|
|
||||||
|
m_valueContent = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
m_valueContent.name = "InteractiveValue.ValueContent";
|
||||||
|
var mainRect = m_valueContent.GetComponent<RectTransform>();
|
||||||
|
mainRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25);
|
||||||
|
var mainGroup = m_valueContent.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
mainGroup.childForceExpandWidth = false;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = false;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.spacing = 4;
|
||||||
|
mainGroup.childAlignment = TextAnchor.UpperLeft;
|
||||||
|
var mainLayout = m_valueContent.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.flexibleWidth = 9000;
|
||||||
|
mainLayout.minWidth = 175;
|
||||||
|
mainLayout.minHeight = 25;
|
||||||
|
mainLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// subcontent expand button TODO
|
||||||
|
if (HasSubContent)
|
||||||
|
{
|
||||||
|
var subBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.3f, 0.3f, 0.3f));
|
||||||
|
var btnLayout = subBtnObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.minWidth = 25;
|
||||||
|
btnLayout.flexibleWidth = 0;
|
||||||
|
btnLayout.flexibleHeight = 0;
|
||||||
|
var btnText = subBtnObj.GetComponentInChildren<Text>();
|
||||||
|
btnText.text = "▲";
|
||||||
|
m_subExpandBtn = subBtnObj.GetComponent<Button>();
|
||||||
|
m_subExpandBtn.onClick.AddListener(() =>
|
||||||
|
{
|
||||||
|
ToggleSubcontent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspect button
|
||||||
|
|
||||||
|
m_inspectButton = UIFactory.CreateButton(m_valueContent, new Color(0.3f, 0.3f, 0.3f, 0.2f));
|
||||||
|
var inspectLayout = m_inspectButton.AddComponent<LayoutElement>();
|
||||||
|
inspectLayout.minWidth = 60;
|
||||||
|
inspectLayout.minHeight = 25;
|
||||||
|
inspectLayout.flexibleHeight = 0;
|
||||||
|
inspectLayout.flexibleWidth = 0;
|
||||||
|
var inspectText = m_inspectButton.GetComponentInChildren<Text>();
|
||||||
|
inspectText.text = "Inspect";
|
||||||
|
var inspectBtn = m_inspectButton.GetComponent<Button>();
|
||||||
|
|
||||||
|
inspectBtn.onClick.AddListener(OnInspectClicked);
|
||||||
|
void OnInspectClicked()
|
||||||
|
{
|
||||||
|
if (!Value.IsNullOrDestroyed(false))
|
||||||
|
InspectorManager.Instance.Inspect(this.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_inspectButton.SetActive(false);
|
||||||
|
|
||||||
|
// value label
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
|
||||||
|
m_baseLabel = labelObj.GetComponent<Text>();
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.flexibleWidth = 9000;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
m_subContentParent = subGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
603
src/Inspectors/Reflection/ReflectionInspector.cs
Normal file
603
src/Inspectors/Reflection/ReflectionInspector.cs
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Inspectors.Reflection;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors
|
||||||
|
{
|
||||||
|
public class ReflectionInspector : InspectorBase
|
||||||
|
{
|
||||||
|
#region STATIC
|
||||||
|
|
||||||
|
public static ReflectionInspector ActiveInstance { get; private set; }
|
||||||
|
|
||||||
|
static ReflectionInspector()
|
||||||
|
{
|
||||||
|
PanelDragger.OnFinishResize += OnContainerResized;
|
||||||
|
SceneExplorer.OnToggleShow += OnContainerResized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnContainerResized()
|
||||||
|
{
|
||||||
|
if (ActiveInstance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActiveInstance.m_widthUpdateWanted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blacklists
|
||||||
|
private static readonly HashSet<string> bl_typeAndMember = new HashSet<string>
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
// these cause a crash in IL2CPP
|
||||||
|
"Type.DeclaringMethod",
|
||||||
|
"Rigidbody2D.Cast",
|
||||||
|
"Collider2D.Cast",
|
||||||
|
"Collider2D.Raycast",
|
||||||
|
"Texture2D.SetPixelDataImpl",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
private static readonly HashSet<string> bl_memberNameStartsWith = new HashSet<string>
|
||||||
|
{
|
||||||
|
// these are redundant
|
||||||
|
"get_",
|
||||||
|
"set_",
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region INSTANCE
|
||||||
|
|
||||||
|
public override string TabLabel => m_targetTypeShortName;
|
||||||
|
|
||||||
|
internal readonly Type m_targetType;
|
||||||
|
internal readonly string m_targetTypeShortName;
|
||||||
|
|
||||||
|
// all cached members of the target
|
||||||
|
internal CacheMember[] m_allMembers;
|
||||||
|
// filtered members based on current filters
|
||||||
|
internal readonly List<CacheMember> m_membersFiltered = new List<CacheMember>();
|
||||||
|
// actual shortlist of displayed members
|
||||||
|
internal readonly CacheMember[] m_displayedMembers = new CacheMember[ModConfig.Instance.Default_Page_Limit];
|
||||||
|
|
||||||
|
internal bool m_autoUpdate;
|
||||||
|
|
||||||
|
// UI members
|
||||||
|
|
||||||
|
private GameObject m_content;
|
||||||
|
public override GameObject Content
|
||||||
|
{
|
||||||
|
get => m_content;
|
||||||
|
set => m_content = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Text m_nameFilterText;
|
||||||
|
internal MemberTypes m_memberFilter;
|
||||||
|
internal Button m_lastActiveMemButton;
|
||||||
|
|
||||||
|
internal PageHandler m_pageHandler;
|
||||||
|
internal SliderScrollbar m_sliderScroller;
|
||||||
|
internal GameObject m_scrollContent;
|
||||||
|
internal RectTransform m_scrollContentRect;
|
||||||
|
|
||||||
|
internal bool m_widthUpdateWanted;
|
||||||
|
internal bool m_widthUpdateWaiting;
|
||||||
|
|
||||||
|
public ReflectionInspector(object target) : base(target)
|
||||||
|
{
|
||||||
|
if (this is StaticInspector)
|
||||||
|
m_targetType = target as Type;
|
||||||
|
else
|
||||||
|
m_targetType = ReflectionHelpers.GetActualType(target);
|
||||||
|
|
||||||
|
m_targetTypeShortName = UISyntaxHighlight.ParseFullSyntax(m_targetType, false);
|
||||||
|
|
||||||
|
ConstructUI();
|
||||||
|
|
||||||
|
CacheMembers(m_targetType);
|
||||||
|
|
||||||
|
FilterMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetActive()
|
||||||
|
{
|
||||||
|
base.SetActive();
|
||||||
|
ActiveInstance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetInactive()
|
||||||
|
{
|
||||||
|
base.SetInactive();
|
||||||
|
ActiveInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Destroy()
|
||||||
|
{
|
||||||
|
base.Destroy();
|
||||||
|
|
||||||
|
if (this.Content)
|
||||||
|
GameObject.Destroy(this.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageTurned()
|
||||||
|
{
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsBlacklisted(string sig) => bl_typeAndMember.Any(it => sig.Contains(it));
|
||||||
|
internal bool IsBlacklisted(MethodInfo method) => bl_memberNameStartsWith.Any(it => method.Name.StartsWith(it));
|
||||||
|
|
||||||
|
internal string GetSig(MemberInfo member) => $"{member.DeclaringType.Name}.{member.Name}";
|
||||||
|
internal string AppendArgsToSig(ParameterInfo[] args)
|
||||||
|
{
|
||||||
|
string ret = " (";
|
||||||
|
foreach (var param in args)
|
||||||
|
ret += $"{param.ParameterType.Name} {param.Name}, ";
|
||||||
|
ret += ")";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CacheMembers(Type type)
|
||||||
|
{
|
||||||
|
var list = new List<CacheMember>();
|
||||||
|
var cachedSigs = new HashSet<string>();
|
||||||
|
|
||||||
|
var types = ReflectionHelpers.GetAllBaseTypes(type);
|
||||||
|
|
||||||
|
var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
|
||||||
|
if (this is InstanceInspector)
|
||||||
|
flags |= BindingFlags.Instance;
|
||||||
|
|
||||||
|
foreach (var declaringType in types)
|
||||||
|
{
|
||||||
|
var target = Target;
|
||||||
|
#if CPP
|
||||||
|
target = target.Il2CppCast(declaringType);
|
||||||
|
#endif
|
||||||
|
IEnumerable<MemberInfo> infos = declaringType.GetMethods(flags);
|
||||||
|
infos = infos.Concat(declaringType.GetProperties(flags));
|
||||||
|
infos = infos.Concat(declaringType.GetFields(flags));
|
||||||
|
|
||||||
|
foreach (var member in infos)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//ExplorerCore.Log($"Trying to cache member {sig}...");
|
||||||
|
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
|
||||||
|
|
||||||
|
var sig = GetSig(member);
|
||||||
|
|
||||||
|
var mi = member as MethodInfo;
|
||||||
|
var pi = member as PropertyInfo;
|
||||||
|
var fi = member as FieldInfo;
|
||||||
|
|
||||||
|
if (IsBlacklisted(sig) || (mi != null && IsBlacklisted(mi)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var args = mi?.GetParameters() ?? pi?.GetIndexParameters();
|
||||||
|
if (args != null)
|
||||||
|
{
|
||||||
|
if (!CacheMember.CanProcessArgs(args))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sig += AppendArgsToSig(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedSigs.Contains(sig))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cachedSigs.Add(sig);
|
||||||
|
|
||||||
|
if (mi != null)
|
||||||
|
list.Add(new CacheMethod(mi, target, m_scrollContent));
|
||||||
|
else if (pi != null)
|
||||||
|
list.Add(new CacheProperty(pi, target, m_scrollContent));
|
||||||
|
else
|
||||||
|
list.Add(new CacheField(fi, target, m_scrollContent));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning($"Exception caching member {member.DeclaringType.FullName}.{member.Name}!");
|
||||||
|
ExplorerCore.Log(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeList = types.ToList();
|
||||||
|
|
||||||
|
var sorted = new List<CacheMember>();
|
||||||
|
sorted.AddRange(list.Where(it => it is CacheMethod)
|
||||||
|
.OrderBy(it => typeList.IndexOf(it.DeclaringType))
|
||||||
|
.ThenBy(it => it.NameForFiltering));
|
||||||
|
sorted.AddRange(list.Where(it => it is CacheProperty)
|
||||||
|
.OrderBy(it => typeList.IndexOf(it.DeclaringType))
|
||||||
|
.ThenBy(it => it.NameForFiltering));
|
||||||
|
sorted.AddRange(list.Where(it => it is CacheField)
|
||||||
|
.OrderBy(it => typeList.IndexOf(it.DeclaringType))
|
||||||
|
.ThenBy(it => it.NameForFiltering));
|
||||||
|
|
||||||
|
m_allMembers = sorted.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (m_autoUpdate)
|
||||||
|
{
|
||||||
|
foreach (var member in m_displayedMembers)
|
||||||
|
{
|
||||||
|
if (member == null) break;
|
||||||
|
member.UpdateValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_widthUpdateWanted)
|
||||||
|
{
|
||||||
|
if (!m_widthUpdateWaiting)
|
||||||
|
m_widthUpdateWaiting = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateWidths();
|
||||||
|
m_widthUpdateWaiting = false;
|
||||||
|
m_widthUpdateWanted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMemberFilterClicked(MemberTypes type, Button button)
|
||||||
|
{
|
||||||
|
if (m_lastActiveMemButton)
|
||||||
|
{
|
||||||
|
var lastColors = m_lastActiveMemButton.colors;
|
||||||
|
lastColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
|
||||||
|
m_lastActiveMemButton.colors = lastColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_memberFilter = type;
|
||||||
|
m_lastActiveMemButton = button;
|
||||||
|
|
||||||
|
var colors = m_lastActiveMemButton.colors;
|
||||||
|
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||||
|
m_lastActiveMemButton.colors = colors;
|
||||||
|
|
||||||
|
FilterMembers(null, true);
|
||||||
|
m_sliderScroller.m_slider.value = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FilterMembers(string nameFilter = null, bool force = false)
|
||||||
|
{
|
||||||
|
int lastCount = m_membersFiltered.Count;
|
||||||
|
m_membersFiltered.Clear();
|
||||||
|
|
||||||
|
nameFilter = nameFilter?.ToLower() ?? m_nameFilterText.text.ToLower();
|
||||||
|
|
||||||
|
foreach (var mem in m_allMembers)
|
||||||
|
{
|
||||||
|
// membertype filter
|
||||||
|
if (m_memberFilter != MemberTypes.All && mem.MemInfo.MemberType != m_memberFilter)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (this is InstanceInspector ii && ii.m_scopeFilter != MemberScopes.All)
|
||||||
|
{
|
||||||
|
if (mem.IsStatic && ii.m_scopeFilter != MemberScopes.Static)
|
||||||
|
continue;
|
||||||
|
else if (!mem.IsStatic && ii.m_scopeFilter != MemberScopes.Instance)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// name filter
|
||||||
|
if (!string.IsNullOrEmpty(nameFilter) && !mem.NameForFiltering.Contains(nameFilter))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_membersFiltered.Add(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force || lastCount != m_membersFiltered.Count)
|
||||||
|
RefreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshDisplay()
|
||||||
|
{
|
||||||
|
var members = m_membersFiltered;
|
||||||
|
m_pageHandler.ListCount = members.Count;
|
||||||
|
|
||||||
|
// disable current members
|
||||||
|
for (int i = 0; i < m_displayedMembers.Length; i++)
|
||||||
|
{
|
||||||
|
var mem = m_displayedMembers[i];
|
||||||
|
if (mem != null)
|
||||||
|
mem.Disable();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (members.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var itemIndex in m_pageHandler)
|
||||||
|
{
|
||||||
|
if (itemIndex >= members.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
CacheMember member = members[itemIndex];
|
||||||
|
m_displayedMembers[itemIndex - m_pageHandler.StartIndex] = member;
|
||||||
|
member.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_widthUpdateWanted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void UpdateWidths()
|
||||||
|
{
|
||||||
|
float labelWidth = 125;
|
||||||
|
|
||||||
|
foreach (var cache in m_displayedMembers)
|
||||||
|
{
|
||||||
|
if (cache == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var width = cache.GetMemberLabelWidth(m_scrollContentRect);
|
||||||
|
|
||||||
|
if (width > labelWidth)
|
||||||
|
labelWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
float valueWidth = m_scrollContentRect.rect.width - labelWidth - 20;
|
||||||
|
|
||||||
|
foreach (var cache in m_displayedMembers)
|
||||||
|
{
|
||||||
|
if (cache == null)
|
||||||
|
break;
|
||||||
|
cache.SetWidths(labelWidth, valueWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructUI()
|
||||||
|
{
|
||||||
|
var parent = InspectorManager.Instance.m_inspectorContent;
|
||||||
|
this.Content = UIFactory.CreateVerticalGroup(parent, new Color(0.15f, 0.15f, 0.15f));
|
||||||
|
var mainGroup = Content.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childForceExpandHeight = false;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.spacing = 5;
|
||||||
|
mainGroup.padding.top = 4;
|
||||||
|
mainGroup.padding.left = 4;
|
||||||
|
mainGroup.padding.right = 4;
|
||||||
|
mainGroup.padding.bottom = 4;
|
||||||
|
|
||||||
|
ConstructTopArea();
|
||||||
|
|
||||||
|
ConstructMemberList();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructTopArea()
|
||||||
|
{
|
||||||
|
var nameRowObj = UIFactory.CreateHorizontalGroup(Content, new Color(1, 1, 1, 0));
|
||||||
|
var nameRow = nameRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
nameRow.childForceExpandWidth = true;
|
||||||
|
nameRow.childForceExpandHeight = true;
|
||||||
|
nameRow.childControlHeight = true;
|
||||||
|
nameRow.childControlWidth = true;
|
||||||
|
nameRow.padding.top = 2;
|
||||||
|
var nameRowLayout = nameRowObj.AddComponent<LayoutElement>();
|
||||||
|
nameRowLayout.minHeight = 25;
|
||||||
|
nameRowLayout.flexibleHeight = 0;
|
||||||
|
nameRowLayout.minWidth = 200;
|
||||||
|
nameRowLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
var typeLabel = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var typeLabelText = typeLabel.GetComponent<Text>();
|
||||||
|
typeLabelText.text = "Type:";
|
||||||
|
typeLabelText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
var typeLabelTextLayout = typeLabel.AddComponent<LayoutElement>();
|
||||||
|
typeLabelTextLayout.minWidth = 40;
|
||||||
|
typeLabelTextLayout.flexibleWidth = 0;
|
||||||
|
typeLabelTextLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var typeDisplayObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var typeDisplayText = typeDisplayObj.GetComponent<Text>();
|
||||||
|
typeDisplayText.text = UISyntaxHighlight.ParseFullSyntax(m_targetType, true);
|
||||||
|
var typeDisplayLayout = typeDisplayObj.AddComponent<LayoutElement>();
|
||||||
|
typeDisplayLayout.minHeight = 25;
|
||||||
|
typeDisplayLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
// Helper tools
|
||||||
|
|
||||||
|
if (this is InstanceInspector)
|
||||||
|
{
|
||||||
|
(this as InstanceInspector).ConstructInstanceHelpers();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstructFilterArea();
|
||||||
|
|
||||||
|
ConstructOptionsArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructFilterArea()
|
||||||
|
{
|
||||||
|
// Filters
|
||||||
|
|
||||||
|
var filterAreaObj = UIFactory.CreateVerticalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var filterLayout = filterAreaObj.AddComponent<LayoutElement>();
|
||||||
|
filterLayout.minHeight = 60;
|
||||||
|
var filterGroup = filterAreaObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
filterGroup.childForceExpandWidth = true;
|
||||||
|
filterGroup.childForceExpandHeight = true;
|
||||||
|
filterGroup.childControlWidth = true;
|
||||||
|
filterGroup.childControlHeight = true;
|
||||||
|
filterGroup.spacing = 4;
|
||||||
|
filterGroup.padding.left = 4;
|
||||||
|
filterGroup.padding.right = 4;
|
||||||
|
filterGroup.padding.top = 4;
|
||||||
|
filterGroup.padding.bottom = 4;
|
||||||
|
|
||||||
|
// name filter
|
||||||
|
|
||||||
|
var nameFilterRowObj = UIFactory.CreateHorizontalGroup(filterAreaObj, new Color(1, 1, 1, 0));
|
||||||
|
var nameFilterGroup = nameFilterRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
nameFilterGroup.childForceExpandHeight = false;
|
||||||
|
nameFilterGroup.childForceExpandWidth = false;
|
||||||
|
nameFilterGroup.childControlWidth = true;
|
||||||
|
nameFilterGroup.childControlHeight = true;
|
||||||
|
nameFilterGroup.spacing = 5;
|
||||||
|
var nameFilterLayout = nameFilterRowObj.AddComponent<LayoutElement>();
|
||||||
|
nameFilterLayout.minHeight = 25;
|
||||||
|
nameFilterLayout.flexibleHeight = 0;
|
||||||
|
nameFilterLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
var nameLabelObj = UIFactory.CreateLabel(nameFilterRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var nameLabelLayout = nameLabelObj.AddComponent<LayoutElement>();
|
||||||
|
nameLabelLayout.minWidth = 100;
|
||||||
|
nameLabelLayout.minHeight = 25;
|
||||||
|
nameLabelLayout.flexibleWidth = 0;
|
||||||
|
var nameLabelText = nameLabelObj.GetComponent<Text>();
|
||||||
|
nameLabelText.text = "Filter names:";
|
||||||
|
nameLabelText.color = Color.grey;
|
||||||
|
|
||||||
|
var nameInputObj = UIFactory.CreateInputField(nameFilterRowObj, 14, (int)TextAnchor.MiddleLeft, (int)HorizontalWrapMode.Overflow);
|
||||||
|
var nameInputLayout = nameInputObj.AddComponent<LayoutElement>();
|
||||||
|
nameInputLayout.flexibleWidth = 5000;
|
||||||
|
nameInputLayout.minWidth = 100;
|
||||||
|
nameInputLayout.minHeight = 25;
|
||||||
|
var nameInput = nameInputObj.GetComponent<InputField>();
|
||||||
|
nameInput.onValueChanged.AddListener((string val) => { FilterMembers(val); });
|
||||||
|
m_nameFilterText = nameInput.textComponent;
|
||||||
|
|
||||||
|
// membertype filter
|
||||||
|
|
||||||
|
var memberFilterRowObj = UIFactory.CreateHorizontalGroup(filterAreaObj, new Color(1, 1, 1, 0));
|
||||||
|
var memFilterGroup = memberFilterRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
memFilterGroup.childForceExpandHeight = false;
|
||||||
|
memFilterGroup.childForceExpandWidth = false;
|
||||||
|
memFilterGroup.childControlWidth = true;
|
||||||
|
memFilterGroup.childControlHeight = true;
|
||||||
|
memFilterGroup.spacing = 5;
|
||||||
|
var memFilterLayout = memberFilterRowObj.AddComponent<LayoutElement>();
|
||||||
|
memFilterLayout.minHeight = 25;
|
||||||
|
memFilterLayout.flexibleHeight = 0;
|
||||||
|
memFilterLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
var memLabelObj = UIFactory.CreateLabel(memberFilterRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var memLabelLayout = memLabelObj.AddComponent<LayoutElement>();
|
||||||
|
memLabelLayout.minWidth = 100;
|
||||||
|
memLabelLayout.minHeight = 25;
|
||||||
|
memLabelLayout.flexibleWidth = 0;
|
||||||
|
var memLabelText = memLabelObj.GetComponent<Text>();
|
||||||
|
memLabelText.text = "Filter members:";
|
||||||
|
memLabelText.color = Color.grey;
|
||||||
|
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberTypes.All);
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberTypes.Method);
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberTypes.Property, true);
|
||||||
|
AddFilterButton(memberFilterRowObj, MemberTypes.Field);
|
||||||
|
|
||||||
|
// Instance filters
|
||||||
|
|
||||||
|
if (this is InstanceInspector)
|
||||||
|
{
|
||||||
|
(this as InstanceInspector).ConstructInstanceFilters(filterAreaObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFilterButton(GameObject parent, MemberTypes type, bool setEnabled = false)
|
||||||
|
{
|
||||||
|
var btnObj = UIFactory.CreateButton(parent, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
|
||||||
|
var btnLayout = btnObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.minWidth = 70;
|
||||||
|
|
||||||
|
var text = btnObj.GetComponentInChildren<Text>();
|
||||||
|
text.text = type.ToString();
|
||||||
|
|
||||||
|
var btn = btnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
btn.onClick.AddListener(() => { OnMemberFilterClicked(type, btn); });
|
||||||
|
|
||||||
|
var colors = btn.colors;
|
||||||
|
colors.highlightedColor = new Color(0.3f, 0.7f, 0.3f);
|
||||||
|
|
||||||
|
if (setEnabled)
|
||||||
|
{
|
||||||
|
colors.normalColor = new Color(0.2f, 0.6f, 0.2f);
|
||||||
|
m_memberFilter = type;
|
||||||
|
m_lastActiveMemButton = btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructOptionsArea()
|
||||||
|
{
|
||||||
|
var optionsRowObj = UIFactory.CreateHorizontalGroup(Content, new Color(1, 1, 1, 0));
|
||||||
|
var optionsLayout = optionsRowObj.AddComponent<LayoutElement>();
|
||||||
|
optionsLayout.minHeight = 25;
|
||||||
|
var optionsGroup = optionsRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
optionsGroup.childForceExpandHeight = true;
|
||||||
|
optionsGroup.childForceExpandWidth = false;
|
||||||
|
optionsGroup.childAlignment = TextAnchor.MiddleLeft;
|
||||||
|
optionsGroup.spacing = 10;
|
||||||
|
|
||||||
|
// update button
|
||||||
|
|
||||||
|
var updateButtonObj = UIFactory.CreateButton(optionsRowObj, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var updateBtnLayout = updateButtonObj.AddComponent<LayoutElement>();
|
||||||
|
updateBtnLayout.minWidth = 110;
|
||||||
|
updateBtnLayout.flexibleWidth = 0;
|
||||||
|
var updateText = updateButtonObj.GetComponentInChildren<Text>();
|
||||||
|
updateText.text = "Update Values";
|
||||||
|
var updateBtn = updateButtonObj.GetComponent<Button>();
|
||||||
|
updateBtn.onClick.AddListener(() =>
|
||||||
|
{
|
||||||
|
bool orig = m_autoUpdate;
|
||||||
|
m_autoUpdate = true;
|
||||||
|
Update();
|
||||||
|
if (!orig) m_autoUpdate = orig;
|
||||||
|
});
|
||||||
|
|
||||||
|
// auto update
|
||||||
|
|
||||||
|
var autoUpdateObj = UIFactory.CreateToggle(optionsRowObj, out Toggle autoUpdateToggle, out Text autoUpdateText);
|
||||||
|
var autoUpdateLayout = autoUpdateObj.AddComponent<LayoutElement>();
|
||||||
|
autoUpdateLayout.minWidth = 150;
|
||||||
|
autoUpdateLayout.minHeight = 25;
|
||||||
|
autoUpdateText.text = "Auto-update?";
|
||||||
|
autoUpdateToggle.isOn = false;
|
||||||
|
autoUpdateToggle.onValueChanged.AddListener((bool val) => { m_autoUpdate = val; });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructMemberList()
|
||||||
|
{
|
||||||
|
var scrollobj = UIFactory.CreateScrollView(Content, out m_scrollContent, out m_sliderScroller, new Color(0.05f, 0.05f, 0.05f));
|
||||||
|
|
||||||
|
m_scrollContentRect = m_scrollContent.GetComponent<RectTransform>();
|
||||||
|
|
||||||
|
var scrollGroup = m_scrollContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
scrollGroup.spacing = 3;
|
||||||
|
scrollGroup.padding.left = 0;
|
||||||
|
scrollGroup.padding.right = 0;
|
||||||
|
scrollGroup.childForceExpandHeight = true;
|
||||||
|
|
||||||
|
m_pageHandler = new PageHandler(m_sliderScroller);
|
||||||
|
m_pageHandler.ConstructUI(Content);
|
||||||
|
m_pageHandler.OnPageChanged += OnPageTurned;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // end UI
|
||||||
|
|
||||||
|
#endregion // end instance
|
||||||
|
}
|
||||||
|
}
|
11
src/Inspectors/Reflection/StaticInspector.cs
Normal file
11
src/Inspectors/Reflection/StaticInspector.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors.Reflection
|
||||||
|
{
|
||||||
|
public class StaticInspector : ReflectionInspector
|
||||||
|
{
|
||||||
|
public override string TabLabel => $" <color=cyan>[S]</color> {base.TabLabel}";
|
||||||
|
|
||||||
|
public StaticInspector(Type type) : base(type) { }
|
||||||
|
}
|
||||||
|
}
|
@ -1,552 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using MelonLoader;
|
|
||||||
using Mono.CSharp;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class ReflectionWindow : WindowManager.UIWindow
|
|
||||||
{
|
|
||||||
public override Il2CppSystem.String Name { get => "Object Reflection"; set => Name = value; }
|
|
||||||
|
|
||||||
public Type m_objectType;
|
|
||||||
public object m_object;
|
|
||||||
|
|
||||||
private List<FieldInfoHolder> m_FieldInfos;
|
|
||||||
private List<PropertyInfoHolder> m_PropertyInfos;
|
|
||||||
|
|
||||||
private bool m_autoUpdate = false;
|
|
||||||
private string m_search = "";
|
|
||||||
public MemberFilter m_filter = MemberFilter.Property;
|
|
||||||
|
|
||||||
public enum MemberFilter
|
|
||||||
{
|
|
||||||
Both,
|
|
||||||
Property,
|
|
||||||
Field
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type GetActualType(object m_object)
|
|
||||||
{
|
|
||||||
if (m_object is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
var iltype = ilObject.GetIl2CppType();
|
|
||||||
return Type.GetType(iltype.AssemblyQualifiedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return m_object.GetType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type[] GetAllBaseTypes(object m_object)
|
|
||||||
{
|
|
||||||
var list = new List<Type>();
|
|
||||||
|
|
||||||
if (m_object is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
var ilType = ilObject.GetIl2CppType();
|
|
||||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilTypeToManaged)
|
|
||||||
{
|
|
||||||
list.Add(ilTypeToManaged);
|
|
||||||
|
|
||||||
while (ilType.BaseType != null)
|
|
||||||
{
|
|
||||||
ilType = ilType.BaseType;
|
|
||||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilBaseTypeToManaged)
|
|
||||||
{
|
|
||||||
list.Add(ilBaseTypeToManaged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var type = m_object.GetType();
|
|
||||||
list.Add(type);
|
|
||||||
while (type.BaseType != null)
|
|
||||||
{
|
|
||||||
type = type.BaseType;
|
|
||||||
list.Add(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
m_object = Target;
|
|
||||||
|
|
||||||
m_FieldInfos = new List<FieldInfoHolder>();
|
|
||||||
m_PropertyInfos = new List<PropertyInfoHolder>();
|
|
||||||
|
|
||||||
var type = GetActualType(m_object);
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("could not get underlying type for object. ToString(): " + m_object.ToString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_objectType = type;
|
|
||||||
GetFields(m_object);
|
|
||||||
GetProperties(m_object);
|
|
||||||
|
|
||||||
UpdateValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update()
|
|
||||||
{
|
|
||||||
if (m_autoUpdate)
|
|
||||||
{
|
|
||||||
UpdateValues();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateValues()
|
|
||||||
{
|
|
||||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
|
||||||
{
|
|
||||||
foreach (var holder in this.m_FieldInfos)
|
|
||||||
{
|
|
||||||
if (m_search == "" || holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower()))
|
|
||||||
{
|
|
||||||
holder.UpdateValue(m_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
|
||||||
{
|
|
||||||
foreach (var holder in this.m_PropertyInfos)
|
|
||||||
{
|
|
||||||
if (m_search == "" || holder.propInfo.Name.ToLower().Contains(m_search.ToLower()))
|
|
||||||
{
|
|
||||||
holder.UpdateValue(m_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WindowFunction(int windowID)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Header();
|
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("<b>Type:</b> <color=cyan>" + m_objectType.Name + "</color>", null);
|
|
||||||
|
|
||||||
bool unityObj = m_object is UnityEngine.Object;
|
|
||||||
|
|
||||||
if (unityObj)
|
|
||||||
{
|
|
||||||
GUILayout.Label("Name: " + (m_object as UnityEngine.Object).name, null);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
if (unityObj)
|
|
||||||
{
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
|
|
||||||
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
|
||||||
|
|
||||||
UIStyles.InstantiateButton((UnityEngine.Object)m_object);
|
|
||||||
|
|
||||||
if (m_object is Component comp && comp.gameObject is GameObject obj)
|
|
||||||
{
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
||||||
GUILayout.Label("GameObject:", null);
|
|
||||||
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 350) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(obj, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
UIStyles.HorizontalLine(Color.grey);
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("<b>Search:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
|
||||||
m_search = GUILayout.TextField(m_search, null);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
|
||||||
FilterToggle(MemberFilter.Both, "Both");
|
|
||||||
FilterToggle(MemberFilter.Property, "Properties");
|
|
||||||
FilterToggle(MemberFilter.Field, "Fields");
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("<b>Values:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
|
||||||
if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) }))
|
|
||||||
{
|
|
||||||
UpdateValues();
|
|
||||||
}
|
|
||||||
GUI.color = m_autoUpdate ? Color.green : Color.red;
|
|
||||||
m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) });
|
|
||||||
GUI.color = Color.white;
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.Space(10);
|
|
||||||
|
|
||||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
|
||||||
|
|
||||||
GUILayout.Space(10);
|
|
||||||
|
|
||||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
|
||||||
{
|
|
||||||
UIStyles.HorizontalLine(Color.grey);
|
|
||||||
|
|
||||||
GUILayout.Label("<size=18><b><color=gold>Fields</color></b></size>", null);
|
|
||||||
|
|
||||||
foreach (var holder in this.m_FieldInfos)
|
|
||||||
{
|
|
||||||
if (m_search != "" && !holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
|
||||||
holder.Draw(this);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
|
||||||
{
|
|
||||||
UIStyles.HorizontalLine(Color.grey);
|
|
||||||
|
|
||||||
GUILayout.Label("<size=18><b><color=gold>Properties</color></b></size>", null);
|
|
||||||
|
|
||||||
foreach (var holder in this.m_PropertyInfos)
|
|
||||||
{
|
|
||||||
if (m_search != "" && !holder.propInfo.Name.ToLower().Contains(m_search.ToLower()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
|
||||||
holder.Draw(this);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
|
|
||||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
|
||||||
|
|
||||||
GUILayout.EndArea();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message);
|
|
||||||
DestroyWindow();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FilterToggle(MemberFilter mode, string label)
|
|
||||||
{
|
|
||||||
if (m_filter == mode)
|
|
||||||
{
|
|
||||||
GUI.color = Color.green;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
|
||||||
{
|
|
||||||
m_filter = mode;
|
|
||||||
}
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsList(Type t)
|
|
||||||
{
|
|
||||||
return t.IsGenericType
|
|
||||||
&& t.GetGenericTypeDefinition() is Type typeDef
|
|
||||||
&& (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetProperties(object m_object, List<string> names = null)
|
|
||||||
{
|
|
||||||
if (names == null)
|
|
||||||
{
|
|
||||||
names = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var types = GetAllBaseTypes(m_object);
|
|
||||||
|
|
||||||
foreach (var type in types)
|
|
||||||
{
|
|
||||||
foreach (var pi in type.GetProperties(At.flags))
|
|
||||||
{
|
|
||||||
if (pi.Name == "Il2CppType")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (names.Contains(pi.Name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
names.Add(pi.Name);
|
|
||||||
|
|
||||||
var piHolder = new PropertyInfoHolder(type, pi);
|
|
||||||
m_PropertyInfos.Add(piHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetFields(object m_object, List<string> names = null)
|
|
||||||
{
|
|
||||||
if (names == null)
|
|
||||||
{
|
|
||||||
names = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var types = GetAllBaseTypes(m_object);
|
|
||||||
|
|
||||||
foreach (var type in types)
|
|
||||||
{
|
|
||||||
foreach (var fi in type.GetFields(At.flags))
|
|
||||||
{
|
|
||||||
if (names.Contains(fi.Name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
names.Add(fi.Name);
|
|
||||||
|
|
||||||
var fiHolder = new FieldInfoHolder(type, fi);
|
|
||||||
m_FieldInfos.Add(fiHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* *********************
|
|
||||||
* PROPERTYINFO HOLDER
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PropertyInfoHolder
|
|
||||||
{
|
|
||||||
public Type classType;
|
|
||||||
public PropertyInfo propInfo;
|
|
||||||
public object m_value;
|
|
||||||
|
|
||||||
public PropertyInfoHolder(Type _type, PropertyInfo _propInfo)
|
|
||||||
{
|
|
||||||
classType = _type;
|
|
||||||
propInfo = _propInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Draw(ReflectionWindow window)
|
|
||||||
{
|
|
||||||
if (propInfo.CanWrite)
|
|
||||||
{
|
|
||||||
UIStyles.DrawMember(ref m_value, propInfo.PropertyType.Name, propInfo.Name, window.m_rect, window.m_object, SetValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UIStyles.DrawMember(ref m_value, propInfo.PropertyType.Name, propInfo.Name, window.m_rect, window.m_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (obj is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
var declaringType = this.propInfo.DeclaringType;
|
|
||||||
if (declaringType == typeof(Il2CppObjectBase))
|
|
||||||
{
|
|
||||||
m_value = ilObject.Pointer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var cast = CppExplorer.Il2CppCast(obj, declaringType);
|
|
||||||
m_value = this.propInfo.GetValue(cast, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_value = this.propInfo.GetValue(obj, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
//MelonLogger.Log("Exception on PropertyInfoHolder.UpdateValue, Name: " + this.propInfo.Name);
|
|
||||||
//MelonLogger.Log(e.GetType() + ", " + e.Message);
|
|
||||||
|
|
||||||
//var inner = e.InnerException;
|
|
||||||
//while (inner != null)
|
|
||||||
//{
|
|
||||||
// MelonLogger.Log("inner: " + inner.GetType() + ", " + inner.Message);
|
|
||||||
// inner = inner.InnerException;
|
|
||||||
//}
|
|
||||||
|
|
||||||
m_value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValue(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (propInfo.PropertyType.IsEnum)
|
|
||||||
{
|
|
||||||
if (System.Enum.Parse(propInfo.PropertyType, m_value.ToString()) is object enumValue && enumValue != null)
|
|
||||||
{
|
|
||||||
m_value = enumValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propInfo.PropertyType.IsPrimitive)
|
|
||||||
{
|
|
||||||
if (propInfo.PropertyType == typeof(float))
|
|
||||||
{
|
|
||||||
if (float.TryParse(m_value.ToString(), out float f))
|
|
||||||
{
|
|
||||||
m_value = f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propInfo.PropertyType == typeof(double))
|
|
||||||
{
|
|
||||||
if (double.TryParse(m_value.ToString(), out double d))
|
|
||||||
{
|
|
||||||
m_value = d;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propInfo.PropertyType != typeof(bool))
|
|
||||||
{
|
|
||||||
if (int.TryParse(m_value.ToString(), out int i))
|
|
||||||
{
|
|
||||||
m_value = i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + propInfo.PropertyType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var declaring = propInfo.DeclaringType;
|
|
||||||
var cast = CppExplorer.Il2CppCast(obj, declaring);
|
|
||||||
|
|
||||||
propInfo.SetValue(propInfo.GetAccessors()[0].IsStatic ? null : cast, m_value, null);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
//MelonLogger.Log("Exception trying to set property " + this.propInfo.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* *********************
|
|
||||||
* FIELDINFO HOLDER
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FieldInfoHolder
|
|
||||||
{
|
|
||||||
public Type classType;
|
|
||||||
public FieldInfo fieldInfo;
|
|
||||||
public object m_value;
|
|
||||||
|
|
||||||
public FieldInfoHolder(Type _type, FieldInfo _fieldInfo)
|
|
||||||
{
|
|
||||||
classType = _type;
|
|
||||||
fieldInfo = _fieldInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
m_value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Draw(ReflectionWindow window)
|
|
||||||
{
|
|
||||||
bool canSet = !(fieldInfo.IsLiteral && !fieldInfo.IsInitOnly);
|
|
||||||
|
|
||||||
if (canSet)
|
|
||||||
{
|
|
||||||
UIStyles.DrawMember(ref m_value, fieldInfo.FieldType.Name, fieldInfo.Name, window.m_rect, window.m_object, SetValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UIStyles.DrawMember(ref m_value, fieldInfo.FieldType.Name, fieldInfo.Name, window.m_rect, window.m_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValue(object obj)
|
|
||||||
{
|
|
||||||
if (fieldInfo.FieldType.IsEnum)
|
|
||||||
{
|
|
||||||
if (System.Enum.Parse(fieldInfo.FieldType, m_value.ToString()) is object enumValue && enumValue != null)
|
|
||||||
{
|
|
||||||
m_value = enumValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldInfo.FieldType.IsPrimitive)
|
|
||||||
{
|
|
||||||
if (fieldInfo.FieldType == typeof(float))
|
|
||||||
{
|
|
||||||
if (float.TryParse(m_value.ToString(), out float f))
|
|
||||||
{
|
|
||||||
m_value = f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldInfo.FieldType == typeof(double))
|
|
||||||
{
|
|
||||||
if (double.TryParse(m_value.ToString(), out double d))
|
|
||||||
{
|
|
||||||
m_value = d;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldInfo.FieldType != typeof(bool))
|
|
||||||
{
|
|
||||||
if (int.TryParse(m_value.ToString(), out int i))
|
|
||||||
{
|
|
||||||
m_value = i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + fieldInfo.FieldType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : obj, m_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
559
src/Inspectors/SceneExplorer.cs
Normal file
559
src/Inspectors/SceneExplorer.cs
Normal file
@ -0,0 +1,559 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.Inspectors
|
||||||
|
{
|
||||||
|
public class SceneExplorer
|
||||||
|
{
|
||||||
|
public static SceneExplorer Instance;
|
||||||
|
|
||||||
|
internal static Action OnToggleShow;
|
||||||
|
|
||||||
|
public SceneExplorer()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
ConstructScenePane();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Hiding;
|
||||||
|
|
||||||
|
private const float UPDATE_INTERVAL = 1f;
|
||||||
|
private float m_timeOfLastSceneUpdate;
|
||||||
|
|
||||||
|
// private int m_currentSceneHandle = -1;
|
||||||
|
public static Scene DontDestroyScene => DontDestroyObject.scene;
|
||||||
|
internal Scene m_currentScene;
|
||||||
|
internal Scene[] m_currentScenes = new Scene[0];
|
||||||
|
|
||||||
|
private GameObject m_selectedSceneObject;
|
||||||
|
private int m_lastCount;
|
||||||
|
|
||||||
|
private Dropdown m_sceneDropdown;
|
||||||
|
private Text m_sceneDropdownText;
|
||||||
|
private Text m_scenePathText;
|
||||||
|
private GameObject m_mainInspectBtn;
|
||||||
|
private GameObject m_backButtonObj;
|
||||||
|
|
||||||
|
public PageHandler m_pageHandler;
|
||||||
|
private GameObject m_pageContent;
|
||||||
|
private GameObject[] m_allObjects = new GameObject[0];
|
||||||
|
private readonly List<GameObject> m_shortList = new List<GameObject>();
|
||||||
|
private readonly List<Text> m_shortListTexts = new List<Text>();
|
||||||
|
private readonly List<Toggle> m_shortListToggles = new List<Toggle>();
|
||||||
|
|
||||||
|
|
||||||
|
internal static GameObject DontDestroyObject
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!m_dontDestroyObject)
|
||||||
|
{
|
||||||
|
m_dontDestroyObject = new GameObject("DontDestroyMe");
|
||||||
|
GameObject.DontDestroyOnLoad(m_dontDestroyObject);
|
||||||
|
}
|
||||||
|
return m_dontDestroyObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GameObject m_dontDestroyObject;
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
RefreshSceneSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (Hiding || Time.realtimeSinceStartup - m_timeOfLastSceneUpdate < UPDATE_INTERVAL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshSceneSelector();
|
||||||
|
|
||||||
|
if (!m_selectedSceneObject)
|
||||||
|
{
|
||||||
|
if (m_currentScene != default)
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
SetSceneObjectList(SceneUnstrip.GetRootGameObjects(m_currentScene.handle));
|
||||||
|
#else
|
||||||
|
SetSceneObjectList(m_currentScene.GetRootGameObjects());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RefreshSelectedSceneObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#if CPP
|
||||||
|
// public int GetSceneHandle(string sceneName)
|
||||||
|
// {
|
||||||
|
// if (sceneName == "DontDestroyOnLoad")
|
||||||
|
// return DontDestroyScene;
|
||||||
|
|
||||||
|
// for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
// {
|
||||||
|
// var scene = SceneManager.GetSceneAt(i);
|
||||||
|
// if (scene.name == sceneName)
|
||||||
|
// return scene.handle;
|
||||||
|
// }
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
internal void OnSceneChange()
|
||||||
|
{
|
||||||
|
m_sceneDropdown.OnCancel(null);
|
||||||
|
RefreshSceneSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSceneSelector()
|
||||||
|
{
|
||||||
|
var names = new List<string>();
|
||||||
|
var scenes = new List<Scene>();
|
||||||
|
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
Scene scene = SceneManager.GetSceneAt(i);
|
||||||
|
|
||||||
|
if (scene == default)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
scenes.Add(scene);
|
||||||
|
names.Add(scene.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
names.Add("DontDestroyOnLoad");
|
||||||
|
scenes.Add(DontDestroyScene);
|
||||||
|
|
||||||
|
m_sceneDropdown.options.Clear();
|
||||||
|
|
||||||
|
foreach (string scene in names)
|
||||||
|
{
|
||||||
|
m_sceneDropdown.options.Add(new Dropdown.OptionData { text = scene });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!names.Contains(m_sceneDropdownText.text))
|
||||||
|
{
|
||||||
|
m_sceneDropdownText.text = names[0];
|
||||||
|
SetTargetScene(scenes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentScenes = scenes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#if CPP
|
||||||
|
// public void SetTargetScene(string name) => SetTargetScene(scene.handle);
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
public void SetTargetScene(Scene scene)
|
||||||
|
{
|
||||||
|
if (scene == default)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentScene = scene;
|
||||||
|
#if CPP
|
||||||
|
GameObject[] rootObjs = SceneUnstrip.GetRootGameObjects(scene.handle);
|
||||||
|
#else
|
||||||
|
GameObject[] rootObjs = SceneUnstrip.GetRootGameObjects(scene);
|
||||||
|
#endif
|
||||||
|
SetSceneObjectList(rootObjs);
|
||||||
|
|
||||||
|
m_selectedSceneObject = null;
|
||||||
|
|
||||||
|
if (m_backButtonObj.activeSelf)
|
||||||
|
{
|
||||||
|
m_backButtonObj.SetActive(false);
|
||||||
|
m_mainInspectBtn.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_scenePathText.text = "Scene root:";
|
||||||
|
//m_scenePathText.ForceMeshUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTargetObject(GameObject obj)
|
||||||
|
{
|
||||||
|
if (!obj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_scenePathText.text = obj.name;
|
||||||
|
//m_scenePathText.ForceMeshUpdate();
|
||||||
|
|
||||||
|
m_selectedSceneObject = obj;
|
||||||
|
|
||||||
|
RefreshSelectedSceneObject();
|
||||||
|
|
||||||
|
if (!m_backButtonObj.activeSelf)
|
||||||
|
{
|
||||||
|
m_backButtonObj.SetActive(true);
|
||||||
|
m_mainInspectBtn.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSelectedSceneObject()
|
||||||
|
{
|
||||||
|
GameObject[] list = new GameObject[m_selectedSceneObject.transform.childCount];
|
||||||
|
for (int i = 0; i < m_selectedSceneObject.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
list[i] = m_selectedSceneObject.transform.GetChild(i).gameObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSceneObjectList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetSceneObjectList(GameObject[] objects)
|
||||||
|
{
|
||||||
|
m_allObjects = objects;
|
||||||
|
RefreshSceneObjectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SceneListObjectClicked(int index)
|
||||||
|
{
|
||||||
|
if (index >= m_shortList.Count || !m_shortList[index])
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = m_shortList[index];
|
||||||
|
if (obj.transform.childCount > 0)
|
||||||
|
SetTargetObject(obj);
|
||||||
|
else
|
||||||
|
InspectorManager.Instance.Inspect(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneListPageTurn()
|
||||||
|
{
|
||||||
|
RefreshSceneObjectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToggleClicked(int index, bool val)
|
||||||
|
{
|
||||||
|
if (index >= m_shortList.Count || !m_shortList[index])
|
||||||
|
return;
|
||||||
|
|
||||||
|
var obj = m_shortList[index];
|
||||||
|
obj.SetActive(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSceneObjectList()
|
||||||
|
{
|
||||||
|
m_timeOfLastSceneUpdate = Time.realtimeSinceStartup;
|
||||||
|
|
||||||
|
var objects = m_allObjects;
|
||||||
|
m_pageHandler.ListCount = objects.Length;
|
||||||
|
|
||||||
|
//int startIndex = m_sceneListPageHandler.StartIndex;
|
||||||
|
|
||||||
|
int newCount = 0;
|
||||||
|
|
||||||
|
foreach (var itemIndex in m_pageHandler)
|
||||||
|
{
|
||||||
|
newCount++;
|
||||||
|
|
||||||
|
// normalized index starting from 0
|
||||||
|
var i = itemIndex - m_pageHandler.StartIndex;
|
||||||
|
|
||||||
|
if (itemIndex >= objects.Length)
|
||||||
|
{
|
||||||
|
if (i > m_lastCount || i >= m_shortListTexts.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
GameObject label = m_shortListTexts[i].transform.parent.parent.gameObject;
|
||||||
|
if (label.activeSelf)
|
||||||
|
label.SetActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameObject obj = objects[itemIndex];
|
||||||
|
|
||||||
|
if (!obj)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i >= m_shortList.Count)
|
||||||
|
{
|
||||||
|
m_shortList.Add(obj);
|
||||||
|
AddObjectListButton();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_shortList[i] = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = m_shortListTexts[i];
|
||||||
|
|
||||||
|
var name = obj.name;
|
||||||
|
|
||||||
|
if (obj.transform.childCount > 0)
|
||||||
|
name = $"<color=grey>[{obj.transform.childCount}]</color> {name}";
|
||||||
|
|
||||||
|
text.text = name;
|
||||||
|
text.color = obj.activeSelf ? Color.green : Color.red;
|
||||||
|
|
||||||
|
var tog = m_shortListToggles[i];
|
||||||
|
tog.isOn = obj.activeSelf;
|
||||||
|
|
||||||
|
var label = text.transform.parent.parent.gameObject;
|
||||||
|
if (!label.activeSelf)
|
||||||
|
{
|
||||||
|
label.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastCount = newCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
public void ConstructScenePane()
|
||||||
|
{
|
||||||
|
GameObject leftPane = UIFactory.CreateVerticalGroup(HomePage.Instance.Content, new Color(72f / 255f, 72f / 255f, 72f / 255f));
|
||||||
|
LayoutElement leftLayout = leftPane.AddComponent<LayoutElement>();
|
||||||
|
leftLayout.minWidth = 350;
|
||||||
|
leftLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
VerticalLayoutGroup leftGroup = leftPane.GetComponent<VerticalLayoutGroup>();
|
||||||
|
leftGroup.padding.left = 4;
|
||||||
|
leftGroup.padding.right = 4;
|
||||||
|
leftGroup.padding.top = 8;
|
||||||
|
leftGroup.padding.bottom = 4;
|
||||||
|
leftGroup.spacing = 4;
|
||||||
|
leftGroup.childControlWidth = true;
|
||||||
|
leftGroup.childControlHeight = true;
|
||||||
|
leftGroup.childForceExpandWidth = true;
|
||||||
|
leftGroup.childForceExpandHeight = true;
|
||||||
|
|
||||||
|
GameObject titleObj = UIFactory.CreateLabel(leftPane, TextAnchor.UpperLeft);
|
||||||
|
Text titleLabel = titleObj.GetComponent<Text>();
|
||||||
|
titleLabel.text = "Scene Explorer";
|
||||||
|
titleLabel.fontSize = 20;
|
||||||
|
LayoutElement titleLayout = titleObj.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minHeight = 30;
|
||||||
|
titleLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
GameObject sceneDropdownObj = UIFactory.CreateDropdown(leftPane, out m_sceneDropdown);
|
||||||
|
LayoutElement dropdownLayout = sceneDropdownObj.AddComponent<LayoutElement>();
|
||||||
|
dropdownLayout.minHeight = 40;
|
||||||
|
dropdownLayout.flexibleHeight = 0;
|
||||||
|
dropdownLayout.minWidth = 320;
|
||||||
|
dropdownLayout.flexibleWidth = 2;
|
||||||
|
|
||||||
|
m_sceneDropdownText = m_sceneDropdown.transform.Find("Label").GetComponent<Text>();
|
||||||
|
m_sceneDropdown.onValueChanged.AddListener((int val) => { SetSceneFromDropdown(val); });
|
||||||
|
|
||||||
|
void SetSceneFromDropdown(int val)
|
||||||
|
{
|
||||||
|
//string scene = m_sceneDropdown.options[val].text;
|
||||||
|
SetTargetScene(m_currentScenes[val]);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject scenePathGroupObj = UIFactory.CreateHorizontalGroup(leftPane, new Color(1, 1, 1, 0f));
|
||||||
|
HorizontalLayoutGroup scenePathGroup = scenePathGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
scenePathGroup.childControlHeight = true;
|
||||||
|
scenePathGroup.childControlWidth = true;
|
||||||
|
scenePathGroup.childForceExpandHeight = true;
|
||||||
|
scenePathGroup.childForceExpandWidth = true;
|
||||||
|
scenePathGroup.spacing = 5;
|
||||||
|
LayoutElement scenePathLayout = scenePathGroupObj.AddComponent<LayoutElement>();
|
||||||
|
scenePathLayout.minHeight = 20;
|
||||||
|
scenePathLayout.minWidth = 335;
|
||||||
|
scenePathLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
m_backButtonObj = UIFactory.CreateButton(scenePathGroupObj);
|
||||||
|
Text backButtonText = m_backButtonObj.GetComponentInChildren<Text>();
|
||||||
|
backButtonText.text = "◄";
|
||||||
|
LayoutElement backButtonLayout = m_backButtonObj.AddComponent<LayoutElement>();
|
||||||
|
backButtonLayout.minWidth = 40;
|
||||||
|
backButtonLayout.flexibleWidth = 0;
|
||||||
|
Button backButton = m_backButtonObj.GetComponent<Button>();
|
||||||
|
var colors = backButton.colors;
|
||||||
|
colors.normalColor = new Color(0.12f, 0.12f, 0.12f);
|
||||||
|
backButton.colors = colors;
|
||||||
|
|
||||||
|
backButton.onClick.AddListener(() => { SetSceneObjectParent(); });
|
||||||
|
|
||||||
|
void SetSceneObjectParent()
|
||||||
|
{
|
||||||
|
if (!m_selectedSceneObject || !m_selectedSceneObject.transform.parent?.gameObject)
|
||||||
|
{
|
||||||
|
m_selectedSceneObject = null;
|
||||||
|
SetTargetScene(m_currentScene);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetTargetObject(m_selectedSceneObject.transform.parent.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject scenePathLabel = UIFactory.CreateHorizontalGroup(scenePathGroupObj);
|
||||||
|
Image image = scenePathLabel.GetComponent<Image>();
|
||||||
|
image.color = Color.white;
|
||||||
|
|
||||||
|
LayoutElement scenePathLabelLayout = scenePathLabel.AddComponent<LayoutElement>();
|
||||||
|
scenePathLabelLayout.minWidth = 210;
|
||||||
|
scenePathLabelLayout.minHeight = 20;
|
||||||
|
scenePathLabelLayout.flexibleHeight = 0;
|
||||||
|
scenePathLabelLayout.flexibleWidth = 120;
|
||||||
|
|
||||||
|
scenePathLabel.AddComponent<Mask>().showMaskGraphic = false;
|
||||||
|
|
||||||
|
GameObject scenePathLabelText = UIFactory.CreateLabel(scenePathLabel, TextAnchor.MiddleLeft);
|
||||||
|
m_scenePathText = scenePathLabelText.GetComponent<Text>();
|
||||||
|
m_scenePathText.text = "Scene root:";
|
||||||
|
m_scenePathText.fontSize = 15;
|
||||||
|
m_scenePathText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
|
||||||
|
LayoutElement textLayout = scenePathLabelText.gameObject.AddComponent<LayoutElement>();
|
||||||
|
textLayout.minWidth = 210;
|
||||||
|
textLayout.flexibleWidth = 120;
|
||||||
|
textLayout.minHeight = 20;
|
||||||
|
textLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
m_mainInspectBtn = UIFactory.CreateButton(scenePathGroupObj);
|
||||||
|
Text inspectButtonText = m_mainInspectBtn.GetComponentInChildren<Text>();
|
||||||
|
inspectButtonText.text = "Inspect";
|
||||||
|
LayoutElement inspectButtonLayout = m_mainInspectBtn.AddComponent<LayoutElement>();
|
||||||
|
inspectButtonLayout.minWidth = 65;
|
||||||
|
inspectButtonLayout.flexibleWidth = 0;
|
||||||
|
Button inspectButton = m_mainInspectBtn.GetComponent<Button>();
|
||||||
|
colors = inspectButton.colors;
|
||||||
|
colors.normalColor = new Color(0.12f, 0.12f, 0.12f);
|
||||||
|
inspectButton.colors = colors;
|
||||||
|
|
||||||
|
inspectButton.onClick.AddListener(() => { InspectorManager.Instance.Inspect(m_selectedSceneObject); });
|
||||||
|
|
||||||
|
GameObject scrollObj = UIFactory.CreateScrollView(leftPane, out m_pageContent, out SliderScrollbar scroller, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
|
||||||
|
m_pageHandler = new PageHandler(scroller);
|
||||||
|
m_pageHandler.ConstructUI(leftPane);
|
||||||
|
m_pageHandler.OnPageChanged += OnSceneListPageTurn;
|
||||||
|
|
||||||
|
// hide button
|
||||||
|
|
||||||
|
var hideButtonObj = UIFactory.CreateButton(leftPane);
|
||||||
|
var hideBtn = hideButtonObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
var hideColors = hideBtn.colors;
|
||||||
|
hideColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
|
||||||
|
hideBtn.colors = hideColors;
|
||||||
|
var hideText = hideButtonObj.GetComponentInChildren<Text>();
|
||||||
|
hideText.text = "Hide Scene Explorer";
|
||||||
|
hideText.fontSize = 13;
|
||||||
|
var hideLayout = hideButtonObj.AddComponent<LayoutElement>();
|
||||||
|
hideLayout.minWidth = 20;
|
||||||
|
hideLayout.minHeight = 20;
|
||||||
|
|
||||||
|
hideBtn.onClick.AddListener(OnHide);
|
||||||
|
|
||||||
|
void OnHide()
|
||||||
|
{
|
||||||
|
if (!Hiding)
|
||||||
|
{
|
||||||
|
Hiding = true;
|
||||||
|
|
||||||
|
hideText.text = "►";
|
||||||
|
titleObj.SetActive(false);
|
||||||
|
sceneDropdownObj.SetActive(false);
|
||||||
|
scenePathGroupObj.SetActive(false);
|
||||||
|
scrollObj.SetActive(false);
|
||||||
|
m_pageHandler.Hide();
|
||||||
|
|
||||||
|
leftLayout.minWidth = 15;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Hiding = false;
|
||||||
|
|
||||||
|
hideText.text = "Hide Scene Explorer";
|
||||||
|
titleObj.SetActive(true);
|
||||||
|
sceneDropdownObj.SetActive(true);
|
||||||
|
scenePathGroupObj.SetActive(true);
|
||||||
|
scrollObj.SetActive(true);
|
||||||
|
|
||||||
|
leftLayout.minWidth = 350;
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnToggleShow?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddObjectListButton()
|
||||||
|
{
|
||||||
|
int thisIndex = m_shortListTexts.Count();
|
||||||
|
|
||||||
|
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(m_pageContent, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
btnGroup.childForceExpandWidth = true;
|
||||||
|
btnGroup.childControlWidth = true;
|
||||||
|
btnGroup.childForceExpandHeight = false;
|
||||||
|
btnGroup.childControlHeight = true;
|
||||||
|
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.flexibleWidth = 320;
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.flexibleHeight = 0;
|
||||||
|
btnGroupObj.AddComponent<Mask>();
|
||||||
|
|
||||||
|
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
|
||||||
|
toggleLayout.minHeight = 25;
|
||||||
|
toggleLayout.minWidth = 25;
|
||||||
|
toggleText.text = "";
|
||||||
|
toggle.isOn = false;
|
||||||
|
m_shortListToggles.Add(toggle);
|
||||||
|
toggle.onValueChanged.AddListener((bool val) => { OnToggleClicked(thisIndex, val); });
|
||||||
|
|
||||||
|
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||||
|
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||||
|
mainBtnLayout.minHeight = 25;
|
||||||
|
mainBtnLayout.flexibleHeight = 0;
|
||||||
|
mainBtnLayout.minWidth = 230;
|
||||||
|
mainBtnLayout.flexibleWidth = 0;
|
||||||
|
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||||
|
ColorBlock mainColors = mainBtn.colors;
|
||||||
|
mainColors.normalColor = new Color(0.1f, 0.1f, 0.1f);
|
||||||
|
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
mainBtn.colors = mainColors;
|
||||||
|
|
||||||
|
mainBtn.onClick.AddListener(() => { SceneListObjectClicked(thisIndex); });
|
||||||
|
|
||||||
|
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||||
|
mainText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
m_shortListTexts.Add(mainText);
|
||||||
|
|
||||||
|
GameObject inspectBtnObj = UIFactory.CreateButton(btnGroupObj);
|
||||||
|
LayoutElement inspectBtnLayout = inspectBtnObj.AddComponent<LayoutElement>();
|
||||||
|
inspectBtnLayout.minWidth = 60;
|
||||||
|
inspectBtnLayout.flexibleWidth = 0;
|
||||||
|
inspectBtnLayout.minHeight = 25;
|
||||||
|
inspectBtnLayout.flexibleHeight = 0;
|
||||||
|
Text inspectText = inspectBtnObj.GetComponentInChildren<Text>();
|
||||||
|
inspectText.text = "Inspect";
|
||||||
|
inspectText.color = Color.white;
|
||||||
|
|
||||||
|
Button inspectBtn = inspectBtnObj.GetComponent<Button>();
|
||||||
|
ColorBlock inspectColors = inspectBtn.colors;
|
||||||
|
inspectColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
|
||||||
|
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 0.5f);
|
||||||
|
inspectBtn.colors = inspectColors;
|
||||||
|
|
||||||
|
inspectBtn.onClick.AddListener(() => { InspectorManager.Instance.Inspect(m_shortList[thisIndex]); });
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using MelonLoader;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class MainMenu
|
|
||||||
{
|
|
||||||
public static MainMenu Instance;
|
|
||||||
|
|
||||||
public MainMenu()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
|
|
||||||
Pages.Add(new ScenePage());
|
|
||||||
Pages.Add(new SearchPage());
|
|
||||||
Pages.Add(new ConsolePage());
|
|
||||||
|
|
||||||
foreach (var page in Pages)
|
|
||||||
{
|
|
||||||
page.Init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public const int MainWindowID = 10;
|
|
||||||
public static Rect MainRect = new Rect(5, 5, 550, 700);
|
|
||||||
private static readonly List<WindowPage> Pages = new List<WindowPage>();
|
|
||||||
private static int m_currentPage = 0;
|
|
||||||
|
|
||||||
public static void SetCurrentPage(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || Pages.Count <= index)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("cannot set page " + index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_currentPage = index;
|
|
||||||
GUI.BringWindowToFront(MainWindowID);
|
|
||||||
GUI.FocusWindow(MainWindowID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update()
|
|
||||||
{
|
|
||||||
Pages[m_currentPage].Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGUI()
|
|
||||||
{
|
|
||||||
if (CppExplorer.ShowMenu)
|
|
||||||
{
|
|
||||||
var origSkin = GUI.skin;
|
|
||||||
GUI.skin = UIStyles.WindowSkin;
|
|
||||||
|
|
||||||
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, "IL2CPP Runtime Explorer");
|
|
||||||
|
|
||||||
GUI.skin = origSkin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MainWindow(int id)
|
|
||||||
{
|
|
||||||
GUI.DragWindow(new Rect(0, 0, MainRect.width - 90, 20));
|
|
||||||
|
|
||||||
if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), "Hide (F7)"))
|
|
||||||
{
|
|
||||||
CppExplorer.ShowMenu = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(5, 25, MainRect.width - 10, MainRect.height - 35), GUI.skin.box);
|
|
||||||
|
|
||||||
MainHeader();
|
|
||||||
|
|
||||||
var page = Pages[m_currentPage];
|
|
||||||
page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView);
|
|
||||||
page.DrawWindow();
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
|
|
||||||
MainRect = WindowManager.ResizeWindow(MainRect, MainWindowID);
|
|
||||||
|
|
||||||
GUILayout.EndArea();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MainHeader()
|
|
||||||
{
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("<b>Options:</b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
||||||
GUILayout.Label("Array Limit:", new GUILayoutOption[] { GUILayout.Width(70) });
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
var _input = GUILayout.TextField(CppExplorer.ArrayLimit.ToString(), new GUILayoutOption[] { GUILayout.Width(60) });
|
|
||||||
if (int.TryParse(_input, out int _lim))
|
|
||||||
{
|
|
||||||
CppExplorer.ArrayLimit = _lim;
|
|
||||||
}
|
|
||||||
CppExplorer.Instance.MouseInspect = GUILayout.Toggle(CppExplorer.Instance.MouseInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
for (int i = 0; i < Pages.Count; i++)
|
|
||||||
{
|
|
||||||
if (m_currentPage == i)
|
|
||||||
GUI.color = Color.green;
|
|
||||||
else
|
|
||||||
GUI.color = Color.white;
|
|
||||||
|
|
||||||
if (GUILayout.Button(Pages[i].Name, null))
|
|
||||||
{
|
|
||||||
m_currentPage = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.Space(10);
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class WindowPage
|
|
||||||
{
|
|
||||||
public virtual string Name { get; set; }
|
|
||||||
|
|
||||||
public Vector2 scroll = Vector2.zero;
|
|
||||||
|
|
||||||
public abstract void Init();
|
|
||||||
|
|
||||||
public abstract void DrawWindow();
|
|
||||||
|
|
||||||
public abstract void Update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using Mono.CSharp;
|
|
||||||
using UnityEngine;
|
|
||||||
using Attribute = System.Attribute;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class REPL : InteractiveBase
|
|
||||||
{
|
|
||||||
static REPL()
|
|
||||||
{
|
|
||||||
var go = new GameObject("UnityREPL");
|
|
||||||
GameObject.DontDestroyOnLoad(go);
|
|
||||||
//go.transform.parent = HPExplorer.Instance.transform;
|
|
||||||
MB = go.AddComponent<ReplHelper>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Documentation("MB - A dummy MonoBehaviour for accessing Unity.")]
|
|
||||||
public static ReplHelper MB { get; }
|
|
||||||
|
|
||||||
[Documentation("find<T>() - find a UnityEngine.Object of type T.")]
|
|
||||||
public static T find<T>() where T : Object
|
|
||||||
{
|
|
||||||
return MB.Find<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Documentation("findAll<T>() - find all UnityEngine.Object of type T.")]
|
|
||||||
public static T[] findAll<T>() where T : Object
|
|
||||||
{
|
|
||||||
return MB.FindAll<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Documentation("runCoroutine(enumerator) - runs an IEnumerator as a Unity coroutine.")]
|
|
||||||
public static object runCoroutine(IEnumerator i)
|
|
||||||
{
|
|
||||||
return MB.RunCoroutine(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Documentation("endCoroutine(co) - ends a Unity coroutine.")]
|
|
||||||
public static void endCoroutine(Coroutine c)
|
|
||||||
{
|
|
||||||
MB.EndCoroutine(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
////[Documentation("type<T>() - obtain type info about a type T. Provides some Reflection helpers.")]
|
|
||||||
////public static TypeHelper type<T>()
|
|
||||||
////{
|
|
||||||
//// return new TypeHelper(typeof(T));
|
|
||||||
////}
|
|
||||||
|
|
||||||
////[Documentation("type(obj) - obtain type info about object obj. Provides some Reflection helpers.")]
|
|
||||||
////public static TypeHelper type(object instance)
|
|
||||||
////{
|
|
||||||
//// return new TypeHelper(instance);
|
|
||||||
////}
|
|
||||||
|
|
||||||
//[Documentation("dir(obj) - lists all available methods and fiels of a given obj.")]
|
|
||||||
//public static string dir(object instance)
|
|
||||||
//{
|
|
||||||
// return type(instance).info();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//[Documentation("dir<T>() - lists all available methods and fields of type T.")]
|
|
||||||
//public static string dir<T>()
|
|
||||||
//{
|
|
||||||
// return type<T>().info();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//[Documentation("findrefs(obj) - find references to the object in currently loaded components.")]
|
|
||||||
//public static Component[] findrefs(object obj)
|
|
||||||
//{
|
|
||||||
// if (obj == null) throw new ArgumentNullException(nameof(obj));
|
|
||||||
|
|
||||||
// var results = new List<Component>();
|
|
||||||
// foreach (var component in Object.FindObjectsOfType<Component>())
|
|
||||||
// {
|
|
||||||
// var type = component.GetType();
|
|
||||||
|
|
||||||
// var nameBlacklist = new[] { "parent", "parentInternal", "root", "transform", "gameObject" };
|
|
||||||
// var typeBlacklist = new[] { typeof(bool) };
|
|
||||||
|
|
||||||
// foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
|
||||||
// .Where(x => x.CanRead && !nameBlacklist.Contains(x.Name) && !typeBlacklist.Contains(x.PropertyType)))
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// if (Equals(prop.GetValue(component, null), obj))
|
|
||||||
// {
|
|
||||||
// results.Add(component);
|
|
||||||
// goto finish;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch { }
|
|
||||||
// }
|
|
||||||
// foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
|
||||||
// .Where(x => !nameBlacklist.Contains(x.Name) && !typeBlacklist.Contains(x.FieldType)))
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// if (Equals(field.GetValue(component), obj))
|
|
||||||
// {
|
|
||||||
// results.Add(component);
|
|
||||||
// goto finish;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch { }
|
|
||||||
// }
|
|
||||||
// finish:;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return results.ToArray();
|
|
||||||
//}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
|
|
||||||
private class DocumentationAttribute : Attribute
|
|
||||||
{
|
|
||||||
public DocumentationAttribute(string doc)
|
|
||||||
{
|
|
||||||
Docs = doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Docs { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
//using Il2CppSystem;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnityEngine;
|
|
||||||
using System;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class ReplHelper : MonoBehaviour
|
|
||||||
{
|
|
||||||
public ReplHelper(IntPtr intPtr) : base(intPtr) { }
|
|
||||||
|
|
||||||
public T Find<T>() where T : Object
|
|
||||||
{
|
|
||||||
return FindObjectOfType<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T[] FindAll<T>() where T : Object
|
|
||||||
{
|
|
||||||
return FindObjectsOfType<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public object RunCoroutine(IEnumerator enumerator)
|
|
||||||
{
|
|
||||||
return MelonCoroutines.Start(enumerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndCoroutine(Coroutine c)
|
|
||||||
{
|
|
||||||
StopCoroutine(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,227 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Reflection;
|
|
||||||
using Mono.CSharp;
|
|
||||||
using System.IO;
|
|
||||||
using MelonLoader;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class ConsolePage : MainMenu.WindowPage
|
|
||||||
{
|
|
||||||
public override string Name { get => "Console"; set => base.Name = value; }
|
|
||||||
|
|
||||||
private ScriptEvaluator _evaluator;
|
|
||||||
private readonly StringBuilder _sb = new StringBuilder();
|
|
||||||
|
|
||||||
private string MethodInput = "";
|
|
||||||
private string UsingInput = "";
|
|
||||||
|
|
||||||
public static List<string> UsingDirectives;
|
|
||||||
|
|
||||||
private static readonly string[] m_defaultUsing = new string[]
|
|
||||||
{
|
|
||||||
"System",
|
|
||||||
"UnityEngine",
|
|
||||||
"System.Linq",
|
|
||||||
"System.Collections",
|
|
||||||
"System.Collections.Generic",
|
|
||||||
"System.Reflection",
|
|
||||||
"MelonLoader"
|
|
||||||
};
|
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp<ReplHelper>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MethodInput = @"// This is a basic REPL console used to execute a method.
|
|
||||||
// Some common directives are added by default, you can add more below.
|
|
||||||
// If you want to return some output, MelonLogger.Log() it.
|
|
||||||
|
|
||||||
MelonLogger.Log(""hello world"");";
|
|
||||||
|
|
||||||
ResetConsole();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.Log($"Error setting up console!\r\nMessage: {e.Message}\r\nStack: {e.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetConsole()
|
|
||||||
{
|
|
||||||
if (_evaluator != null)
|
|
||||||
{
|
|
||||||
_evaluator.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_evaluator = new ScriptEvaluator(new StringWriter(_sb)) { InteractiveBaseClass = typeof(REPL) };
|
|
||||||
|
|
||||||
UsingDirectives = new List<string>();
|
|
||||||
UsingDirectives.AddRange(m_defaultUsing);
|
|
||||||
foreach (string asm in UsingDirectives)
|
|
||||||
{
|
|
||||||
Evaluate(AsmToUsing(asm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AsmToUsing(string asm, bool richtext = false)
|
|
||||||
{
|
|
||||||
if (richtext)
|
|
||||||
{
|
|
||||||
return $"<color=#569cd6>using</color> {asm};";
|
|
||||||
}
|
|
||||||
return $"using {asm};";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddUsing(string asm)
|
|
||||||
{
|
|
||||||
if (!UsingDirectives.Contains(asm))
|
|
||||||
{
|
|
||||||
UsingDirectives.Add(asm);
|
|
||||||
Evaluate(AsmToUsing(asm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Evaluate(string str)
|
|
||||||
{
|
|
||||||
object ret = VoidType.Value;
|
|
||||||
|
|
||||||
_evaluator.Compile(str, out var compiled);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
compiled?.Invoke(ref ret);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning(e.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override void DrawWindow()
|
|
||||||
{
|
|
||||||
GUILayout.Label("<b><size=15><color=cyan>REPL Console</color></size></b>", null);
|
|
||||||
|
|
||||||
GUILayout.Label("Method:", null);
|
|
||||||
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(300) });
|
|
||||||
|
|
||||||
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MethodInput = MethodInput.Trim();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(MethodInput))
|
|
||||||
{
|
|
||||||
var result = Evaluate(MethodInput);
|
|
||||||
|
|
||||||
if (result != null && !Equals(result, VoidType.Value))
|
|
||||||
{
|
|
||||||
MelonLogger.Log("[Console Output]\r\n" + result.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label("<b>Using directives:</b>", null);
|
|
||||||
foreach (var asm in UsingDirectives)
|
|
||||||
{
|
|
||||||
GUILayout.Label(AsmToUsing(asm, true), null);
|
|
||||||
}
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(110) });
|
|
||||||
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
|
|
||||||
if (GUILayout.Button("Add", new GUILayoutOption[] { GUILayout.Width(50) }))
|
|
||||||
{
|
|
||||||
AddUsing(UsingInput);
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("<color=red>Reset</color>", null))
|
|
||||||
{
|
|
||||||
ResetConsole();
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update() { }
|
|
||||||
|
|
||||||
private class VoidType
|
|
||||||
{
|
|
||||||
public static readonly VoidType Value = new VoidType();
|
|
||||||
private VoidType() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ScriptEvaluator : Evaluator, IDisposable
|
|
||||||
{
|
|
||||||
private static readonly HashSet<string> StdLib =
|
|
||||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "mscorlib", "System.Core", "System", "System.Xml" };
|
|
||||||
|
|
||||||
private readonly TextWriter _logger;
|
|
||||||
|
|
||||||
public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger))
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
|
|
||||||
ImportAppdomainAssemblies(ReferenceAssembly);
|
|
||||||
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
|
||||||
_logger.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
|
||||||
{
|
|
||||||
string name = args.LoadedAssembly.GetName().Name;
|
|
||||||
if (StdLib.Contains(name))
|
|
||||||
return;
|
|
||||||
ReferenceAssembly(args.LoadedAssembly);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CompilerContext BuildContext(TextWriter tw)
|
|
||||||
{
|
|
||||||
var reporter = new StreamReportPrinter(tw);
|
|
||||||
|
|
||||||
var settings = new CompilerSettings
|
|
||||||
{
|
|
||||||
Version = LanguageVersion.Experimental,
|
|
||||||
GenerateDebugInfo = false,
|
|
||||||
StdLib = true,
|
|
||||||
Target = Target.Library,
|
|
||||||
WarningLevel = 0,
|
|
||||||
EnhancedWarnings = false
|
|
||||||
};
|
|
||||||
|
|
||||||
return new CompilerContext(settings, reporter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
|
||||||
{
|
|
||||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
||||||
{
|
|
||||||
string name = assembly.GetName().Name;
|
|
||||||
if (StdLib.Contains(name))
|
|
||||||
continue;
|
|
||||||
import(assembly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.SceneManagement;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class ScenePage : MainMenu.WindowPage
|
|
||||||
{
|
|
||||||
public static ScenePage Instance;
|
|
||||||
|
|
||||||
public override string Name { get => "Scene Explorer"; set => base.Name = value; }
|
|
||||||
|
|
||||||
// ----- Holders for GUI elements ----- //
|
|
||||||
|
|
||||||
private string m_currentScene = "";
|
|
||||||
|
|
||||||
// gameobject list
|
|
||||||
private Transform m_currentTransform;
|
|
||||||
private List<GameObject> m_objectList = new List<GameObject>();
|
|
||||||
|
|
||||||
// search bar
|
|
||||||
private bool m_searching = false;
|
|
||||||
private string m_searchInput = "";
|
|
||||||
private List<GameObject> m_searchResults = new List<GameObject>();
|
|
||||||
|
|
||||||
// ------------ Init and Update ------------ //
|
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnSceneChange()
|
|
||||||
{
|
|
||||||
m_currentScene = CppExplorer.ActiveSceneName;
|
|
||||||
|
|
||||||
m_currentTransform = null;
|
|
||||||
CancelSearch();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update()
|
|
||||||
{
|
|
||||||
if (!m_searching)
|
|
||||||
{
|
|
||||||
m_objectList = new List<GameObject>();
|
|
||||||
if (m_currentTransform)
|
|
||||||
{
|
|
||||||
var noChildren = new List<GameObject>();
|
|
||||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
|
||||||
{
|
|
||||||
var child = m_currentTransform.GetChild(i);
|
|
||||||
|
|
||||||
if (child)
|
|
||||||
{
|
|
||||||
if (child.childCount > 0)
|
|
||||||
m_objectList.Add(child.gameObject);
|
|
||||||
else
|
|
||||||
noChildren.Add(child.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_objectList.AddRange(noChildren);
|
|
||||||
noChildren = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var scene = SceneManager.GetActiveScene();
|
|
||||||
var rootObjects = scene.GetRootGameObjects();
|
|
||||||
|
|
||||||
// add objects with children first
|
|
||||||
foreach (var obj in rootObjects.Where(x => x.transform.childCount > 0))
|
|
||||||
{
|
|
||||||
m_objectList.Add(obj);
|
|
||||||
}
|
|
||||||
foreach (var obj in rootObjects.Where(x => x.transform.childCount == 0))
|
|
||||||
{
|
|
||||||
m_objectList.Add(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------- GUI Draw Functions --------- //
|
|
||||||
|
|
||||||
public override void DrawWindow()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Current Scene label
|
|
||||||
GUILayout.Label("Current Scene: <color=cyan>" + m_currentScene + "</color>", null);
|
|
||||||
|
|
||||||
// ----- GameObject Search -----
|
|
||||||
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
|
||||||
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
|
|
||||||
m_searchInput = GUILayout.TextField(m_searchInput, null);
|
|
||||||
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
|
|
||||||
{
|
|
||||||
Search();
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.Space(15);
|
|
||||||
|
|
||||||
// ************** GameObject list ***************
|
|
||||||
|
|
||||||
// ----- main explorer ------
|
|
||||||
if (!m_searching)
|
|
||||||
{
|
|
||||||
if (m_currentTransform != null)
|
|
||||||
{
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
|
||||||
{
|
|
||||||
TraverseUp();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Label(CppExplorer.GetGameObjectPath(m_currentTransform), null);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Label("Scene Root GameObjects:", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_objectList.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var obj in m_objectList)
|
|
||||||
{
|
|
||||||
UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// if m_currentTransform != null ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // ------ Scene Search results ------
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
|
||||||
{
|
|
||||||
CancelSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label("Search Results:", null);
|
|
||||||
|
|
||||||
if (m_searchResults.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var obj in m_searchResults)
|
|
||||||
{
|
|
||||||
UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
m_currentTransform = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------- Actual Methods (not drawing GUI) ---------- //
|
|
||||||
|
|
||||||
public void SetTransformTarget(GameObject obj)
|
|
||||||
{
|
|
||||||
m_currentTransform = obj.transform;
|
|
||||||
CancelSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TraverseUp()
|
|
||||||
{
|
|
||||||
if (m_currentTransform.parent != null)
|
|
||||||
{
|
|
||||||
m_currentTransform = m_currentTransform.parent;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_currentTransform = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Search()
|
|
||||||
{
|
|
||||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
|
||||||
m_searching = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CancelSearch()
|
|
||||||
{
|
|
||||||
m_searching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GameObject> SearchSceneObjects(string _search)
|
|
||||||
{
|
|
||||||
var matches = new List<GameObject>();
|
|
||||||
|
|
||||||
foreach (var obj in Resources.FindObjectsOfTypeAll<GameObject>())
|
|
||||||
{
|
|
||||||
if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == CppExplorer.ActiveSceneName)
|
|
||||||
{
|
|
||||||
matches.Add(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,434 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Reflection;
|
|
||||||
using UnityEngine.SceneManagement;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
using UnhollowerRuntimeLib;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class SearchPage : MainMenu.WindowPage
|
|
||||||
{
|
|
||||||
public static SearchPage Instance;
|
|
||||||
|
|
||||||
public override string Name { get => "Advanced Search"; set => base.Name = value; }
|
|
||||||
|
|
||||||
private string m_searchInput = "";
|
|
||||||
private string m_typeInput = "";
|
|
||||||
private int m_limit = 100;
|
|
||||||
|
|
||||||
public SceneFilter SceneMode = SceneFilter.Any;
|
|
||||||
public TypeFilter TypeMode = TypeFilter.Object;
|
|
||||||
|
|
||||||
public enum SceneFilter
|
|
||||||
{
|
|
||||||
Any,
|
|
||||||
This,
|
|
||||||
DontDestroy,
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TypeFilter
|
|
||||||
{
|
|
||||||
Object,
|
|
||||||
GameObject,
|
|
||||||
Component,
|
|
||||||
Custom
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<object> m_searchResults = new List<object>();
|
|
||||||
private Vector2 resultsScroll = Vector2.zero;
|
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnSceneChange()
|
|
||||||
{
|
|
||||||
m_searchResults.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DrawWindow()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// helpers
|
|
||||||
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
|
||||||
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
|
||||||
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
|
|
||||||
{
|
|
||||||
m_searchResults = GetInstanceClassScanner().ToList();
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
// search box
|
|
||||||
SearchBox();
|
|
||||||
|
|
||||||
// results
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
|
||||||
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
|
||||||
GUILayout.Label("<b><color=orange>Results</color></b>", null);
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
|
|
||||||
resultsScroll = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView);
|
|
||||||
|
|
||||||
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
|
||||||
|
|
||||||
if (m_searchResults.Count > 0)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < m_searchResults.Count; i++)
|
|
||||||
{
|
|
||||||
var obj = m_searchResults[i];
|
|
||||||
|
|
||||||
UIStyles.DrawValue(ref obj, _temprect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
m_searchResults.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SearchBox()
|
|
||||||
{
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
|
||||||
|
|
||||||
// ----- GameObject Search -----
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
|
||||||
GUILayout.Label("<b><color=orange>Search</color></b>", null);
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
|
|
||||||
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
|
||||||
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
|
||||||
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
||||||
GUILayout.Label("Result limit:", new GUILayoutOption[] { GUILayout.Width(100) });
|
|
||||||
var resultinput = m_limit.ToString();
|
|
||||||
resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
|
|
||||||
if (int.TryParse(resultinput, out int _i) && _i > 0)
|
|
||||||
{
|
|
||||||
m_limit = _i;
|
|
||||||
}
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
|
|
||||||
GUILayout.Label("Class Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
|
||||||
ClassFilterToggle(TypeFilter.Object, "Object");
|
|
||||||
ClassFilterToggle(TypeFilter.GameObject, "GameObject");
|
|
||||||
ClassFilterToggle(TypeFilter.Component, "Component");
|
|
||||||
ClassFilterToggle(TypeFilter.Custom, "Custom");
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
if (TypeMode == TypeFilter.Custom)
|
|
||||||
{
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
||||||
GUILayout.Label("Custom Class:", new GUILayoutOption[] { GUILayout.Width(250) });
|
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
|
||||||
m_typeInput = GUILayout.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) });
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Label("Scene Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
|
||||||
SceneFilterToggle(SceneFilter.Any, "Any", 60);
|
|
||||||
SceneFilterToggle(SceneFilter.This, "This Scene", 100);
|
|
||||||
SceneFilterToggle(SceneFilter.DontDestroy, "DontDestroyOnLoad", 140);
|
|
||||||
SceneFilterToggle(SceneFilter.None, "No Scene", 80);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
if (GUILayout.Button("<b><color=cyan>Search</color></b>", null))
|
|
||||||
{
|
|
||||||
Search();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClassFilterToggle(TypeFilter mode, string label)
|
|
||||||
{
|
|
||||||
if (TypeMode == mode)
|
|
||||||
{
|
|
||||||
GUI.color = Color.green;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
|
||||||
{
|
|
||||||
TypeMode = mode;
|
|
||||||
}
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SceneFilterToggle(SceneFilter mode, string label, float width)
|
|
||||||
{
|
|
||||||
if (SceneMode == mode)
|
|
||||||
{
|
|
||||||
GUI.color = Color.green;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width) }))
|
|
||||||
{
|
|
||||||
SceneMode = mode;
|
|
||||||
}
|
|
||||||
GUI.color = Color.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
|
|
||||||
|
|
||||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
|
||||||
public static IEnumerable<object> GetInstanceClassScanner()
|
|
||||||
{
|
|
||||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
|
||||||
.Where(x => !x.FullName.StartsWith("Mono"))
|
|
||||||
.SelectMany(GetTypesSafe)
|
|
||||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
|
||||||
|
|
||||||
foreach (var type in query)
|
|
||||||
{
|
|
||||||
object obj = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (obj != null && !obj.ToString().StartsWith("Mono"))
|
|
||||||
{
|
|
||||||
yield return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
|
|
||||||
{
|
|
||||||
try { return asm.GetTypes(); }
|
|
||||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
|
||||||
catch { return Enumerable.Empty<Type>(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======= search functions =======
|
|
||||||
|
|
||||||
private void Search()
|
|
||||||
{
|
|
||||||
m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
|
||||||
{
|
|
||||||
Il2CppSystem.Type type = null;
|
|
||||||
|
|
||||||
if (TypeMode == TypeFilter.Custom)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var findType = CppExplorer.GetType(_type);
|
|
||||||
type = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
|
||||||
MelonLogger.Log("Got type: " + type.AssemblyQualifiedName);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (TypeMode == TypeFilter.Object)
|
|
||||||
{
|
|
||||||
type = Il2CppType.Of<Object>();
|
|
||||||
}
|
|
||||||
else if (TypeMode == TypeFilter.GameObject)
|
|
||||||
{
|
|
||||||
type = Il2CppType.Of<GameObject>();
|
|
||||||
}
|
|
||||||
else if (TypeMode == TypeFilter.Component)
|
|
||||||
{
|
|
||||||
type = Il2CppType.Of<Component>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Il2CppType.Of<Object>().IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
MelonLogger.LogError("Your Class Type must inherit from UnityEngine.Object! Leave blank to default to UnityEngine.Object");
|
|
||||||
return new List<object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var matches = new List<object>();
|
|
||||||
int added = 0;
|
|
||||||
|
|
||||||
//MelonLogger.Log("Trying to get IL Type. ASM name: " + type.Assembly.GetName().Name + ", Namespace: " + type.Namespace + ", name: " + type.Name);
|
|
||||||
|
|
||||||
//var asmName = type.Assembly.GetName().Name;
|
|
||||||
//if (asmName.Contains("UnityEngine"))
|
|
||||||
//{
|
|
||||||
// asmName = "UnityEngine";
|
|
||||||
//}
|
|
||||||
|
|
||||||
//var intPtr = IL2CPP.GetIl2CppClass(asmName, type.Namespace, type.Name);
|
|
||||||
//var ilType = Il2CppType.TypeFromPointer(intPtr);
|
|
||||||
|
|
||||||
foreach (var obj in Resources.FindObjectsOfTypeAll(type))
|
|
||||||
{
|
|
||||||
if (added == m_limit)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SceneMode != SceneFilter.Any)
|
|
||||||
{
|
|
||||||
if (SceneMode == SceneFilter.None)
|
|
||||||
{
|
|
||||||
if (!NoSceneFilter(obj, obj.GetType()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GameObject go;
|
|
||||||
|
|
||||||
var objtype = obj.GetType();
|
|
||||||
if (objtype == typeof(GameObject))
|
|
||||||
{
|
|
||||||
go = obj as GameObject;
|
|
||||||
}
|
|
||||||
else if (typeof(Component).IsAssignableFrom(objtype))
|
|
||||||
{
|
|
||||||
go = (obj as Component).gameObject;
|
|
||||||
}
|
|
||||||
else { continue; }
|
|
||||||
|
|
||||||
if (!go) { continue; }
|
|
||||||
|
|
||||||
if (SceneMode == SceneFilter.This)
|
|
||||||
{
|
|
||||||
if (go.scene.name != CppExplorer.ActiveSceneName || go.scene.name == "DontDestroyOnLoad")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (SceneMode == SceneFilter.DontDestroy)
|
|
||||||
{
|
|
||||||
if (go.scene.name != "DontDestroyOnLoad")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matches.Contains(obj))
|
|
||||||
{
|
|
||||||
matches.Add(obj);
|
|
||||||
added++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ThisSceneFilter(object obj, Type type)
|
|
||||||
{
|
|
||||||
if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
var go = obj as GameObject ?? (obj as Component).gameObject;
|
|
||||||
|
|
||||||
if (go != null && go.scene.name == CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool DontDestroyFilter(object obj, Type type)
|
|
||||||
{
|
|
||||||
if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
var go = obj as GameObject ?? (obj as Component).gameObject;
|
|
||||||
|
|
||||||
if (go != null && go.scene.name == "DontDestroyOnLoad")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool NoSceneFilter(object obj, Type type)
|
|
||||||
{
|
|
||||||
if (type == typeof(GameObject))
|
|
||||||
{
|
|
||||||
var go = obj as GameObject;
|
|
||||||
|
|
||||||
if (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (typeof(Component).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
var go = (obj as Component).gameObject;
|
|
||||||
|
|
||||||
if (go == null || (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad"))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +1,22 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Explorer;
|
using UnityExplorer;
|
||||||
|
|
||||||
|
#if ML
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
|
|
||||||
[assembly: MelonInfo(typeof(CppExplorer), CppExplorer.NAME, CppExplorer.VERSION, CppExplorer.AUTHOR)]
|
[assembly: MelonInfo(typeof(ExplorerMelonMod), "UnityExplorer", ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
|
||||||
[assembly: MelonGame(null, null)]
|
[assembly: MelonGame(null, null)]
|
||||||
|
#endif
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
// set of attributes. Change these attribute values to modify the information
|
// set of attributes. Change these attribute values to modify the information
|
||||||
// associated with an assembly.
|
// associated with an assembly.
|
||||||
[assembly: AssemblyTitle(CppExplorer.NAME)]
|
[assembly: AssemblyTitle(ExplorerCore.NAME)]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany(CppExplorer.AUTHOR)]
|
[assembly: AssemblyCompany(ExplorerCore.AUTHOR)]
|
||||||
[assembly: AssemblyProduct(CppExplorer.NAME)]
|
[assembly: AssemblyProduct(ExplorerCore.NAME)]
|
||||||
[assembly: AssemblyCopyright("")]
|
[assembly: AssemblyCopyright("")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
@ -37,5 +39,5 @@ using MelonLoader;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
[assembly: AssemblyVersion(ExplorerCore.VERSION)]
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
[assembly: AssemblyFileVersion(ExplorerCore.VERSION)]
|
||||||
|
256
src/Tests/Tests.cs
Normal file
256
src/Tests/Tests.cs
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
#if CPP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.Tests
|
||||||
|
{
|
||||||
|
public static class StaticTestClass
|
||||||
|
{
|
||||||
|
public static int StaticProperty => 5;
|
||||||
|
public static int StaticField = 69;
|
||||||
|
public static List<string> StaticList = new List<string>
|
||||||
|
{
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
"three",
|
||||||
|
};
|
||||||
|
public static void StaticMethod() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestClass
|
||||||
|
{
|
||||||
|
public Vector2 AATestVector2 = new Vector2(1, 2);
|
||||||
|
public Vector3 AATestVector3 = new Vector3(1, 2, 3);
|
||||||
|
public Vector4 AATestVector4 = new Vector4(1, 2, 3, 4);
|
||||||
|
public Rect AATestRect = new Rect(1, 2, 3, 4);
|
||||||
|
public Color AATestColor = new Color(0.1f, 0.2f, 0.3f, 0.4f);
|
||||||
|
|
||||||
|
public bool ATestBoolMethod() => false;
|
||||||
|
|
||||||
|
public bool this[int index]
|
||||||
|
{
|
||||||
|
get => index % 2 == 0;
|
||||||
|
set => m_thisBool = value;
|
||||||
|
}
|
||||||
|
internal bool m_thisBool;
|
||||||
|
|
||||||
|
static int testInt;
|
||||||
|
public static List<string> ExceptionList
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
testInt++;
|
||||||
|
if (testInt % 2 == 0)
|
||||||
|
throw new Exception("its even");
|
||||||
|
else
|
||||||
|
return new List<string> { "one" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool abool;
|
||||||
|
public static bool ATestExceptionBool
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
abool = !abool;
|
||||||
|
if (!abool)
|
||||||
|
throw new Exception("false");
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ExceptionString => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public static string ANullString = null;
|
||||||
|
public static float ATestFloat = 420.69f;
|
||||||
|
public static int ATestInt = -1;
|
||||||
|
public static string ATestString = "hello world";
|
||||||
|
public static uint ATestUInt = 1u;
|
||||||
|
public static byte ATestByte = 255;
|
||||||
|
public static ulong AReadonlyUlong = 82934UL;
|
||||||
|
|
||||||
|
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
|
||||||
|
private static TestClass m_instance;
|
||||||
|
|
||||||
|
public object AmbigObject;
|
||||||
|
|
||||||
|
public List<List<List<string>>> ANestedNestedList = new List<List<List<string>>>
|
||||||
|
{
|
||||||
|
new List<List<string>>
|
||||||
|
{
|
||||||
|
new List<string>
|
||||||
|
{
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
},
|
||||||
|
new List<string>
|
||||||
|
{
|
||||||
|
"three",
|
||||||
|
"four"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new List<List<string>>
|
||||||
|
{
|
||||||
|
new List<string>
|
||||||
|
{
|
||||||
|
"five",
|
||||||
|
"six"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool SetOnlyProperty
|
||||||
|
{
|
||||||
|
set => m_setOnlyProperty = value;
|
||||||
|
}
|
||||||
|
private static bool m_setOnlyProperty;
|
||||||
|
public static bool ReadSetOnlyProperty => m_setOnlyProperty;
|
||||||
|
|
||||||
|
public Texture TestTexture;
|
||||||
|
public static Sprite TestSprite;
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
public static Il2CppSystem.Collections.Generic.HashSet<string> CppHashSetTest;
|
||||||
|
public static Il2CppSystem.Collections.Generic.List<string> CppStringTest;
|
||||||
|
public static Il2CppSystem.Collections.IList CppIList;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public TestClass()
|
||||||
|
{
|
||||||
|
int a = 0;
|
||||||
|
foreach (var list in ANestedNestedList)
|
||||||
|
{
|
||||||
|
foreach (var list2 in list)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 33; i++)
|
||||||
|
list2.Add(a++.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPP
|
||||||
|
TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600);
|
||||||
|
TestTexture.name = "TestTexture";
|
||||||
|
|
||||||
|
var r = new Rect(0, 0, TestTexture.width, TestTexture.height);
|
||||||
|
var v2 = Vector2.zero;
|
||||||
|
var v4 = Vector4.zero;
|
||||||
|
TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
|
||||||
|
|
||||||
|
GameObject.DontDestroyOnLoad(TestTexture);
|
||||||
|
GameObject.DontDestroyOnLoad(TestSprite);
|
||||||
|
|
||||||
|
//// test loading a tex from file
|
||||||
|
//var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png");
|
||||||
|
//ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
|
||||||
|
|
||||||
|
CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
|
||||||
|
CppHashSetTest.Add("1");
|
||||||
|
CppHashSetTest.Add("2");
|
||||||
|
CppHashSetTest.Add("3");
|
||||||
|
|
||||||
|
CppStringTest = new Il2CppSystem.Collections.Generic.List<string>();
|
||||||
|
CppStringTest.Add("1");
|
||||||
|
CppStringTest.Add("2");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component
|
||||||
|
{
|
||||||
|
arg2 = "this is arg2";
|
||||||
|
|
||||||
|
return $"T: '{typeof(T).FullName}', ref arg0: '{arg0}', in arg1: '{arg1}', out arg2: '{arg2}'";
|
||||||
|
}
|
||||||
|
|
||||||
|
// test a non-generic dictionary
|
||||||
|
|
||||||
|
public Hashtable TestNonGenericDict()
|
||||||
|
{
|
||||||
|
return new Hashtable
|
||||||
|
{
|
||||||
|
{ "One", 1 },
|
||||||
|
{ "Two", 2 },
|
||||||
|
{ "Three", 3 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// test HashSets
|
||||||
|
|
||||||
|
public static HashSet<string> HashSetTest = new HashSet<string>
|
||||||
|
{
|
||||||
|
"One",
|
||||||
|
"Two",
|
||||||
|
"Three"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Test indexed parameter
|
||||||
|
|
||||||
|
public string this[int arg0, string arg1]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return $"arg0: {arg0}, arg1: {arg1}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test basic list
|
||||||
|
|
||||||
|
public static List<string> TestList = new List<string>
|
||||||
|
{
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"etc..."
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test a nested dictionary
|
||||||
|
|
||||||
|
public static Dictionary<int, Dictionary<string, int>> NestedDictionary = new Dictionary<int, Dictionary<string, int>>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
1,
|
||||||
|
new Dictionary<string, int>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"Sub 1", 123
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sub 2", 456
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
2,
|
||||||
|
new Dictionary<string, int>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"Sub 3", 789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sub 4", 000
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test a basic method
|
||||||
|
|
||||||
|
public static Color TestMethod(float r, float g, float b, float a)
|
||||||
|
{
|
||||||
|
return new Color(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A method with default arguments
|
||||||
|
|
||||||
|
public static Vector3 TestDefaultArgs(float arg0, float arg1, float arg2 = 5.0f)
|
||||||
|
{
|
||||||
|
return new Vector3(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
230
src/UI/ForceUnlockCursor.cs
Normal file
230
src/UI/ForceUnlockCursor.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
using BF = System.Reflection.BindingFlags;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
#if ML
|
||||||
|
using Harmony;
|
||||||
|
#else
|
||||||
|
using HarmonyLib;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI
|
||||||
|
{
|
||||||
|
public class ForceUnlockCursor
|
||||||
|
{
|
||||||
|
public static bool Unlock
|
||||||
|
{
|
||||||
|
get => m_forceUnlock;
|
||||||
|
set => SetForceUnlock(value);
|
||||||
|
}
|
||||||
|
private static bool m_forceUnlock;
|
||||||
|
|
||||||
|
private static void SetForceUnlock(bool unlock)
|
||||||
|
{
|
||||||
|
m_forceUnlock = unlock;
|
||||||
|
UpdateCursorControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ShouldForceMouse => ExplorerCore.ShowMenu && Unlock;
|
||||||
|
|
||||||
|
private static CursorLockMode m_lastLockMode;
|
||||||
|
private static bool m_lastVisibleState;
|
||||||
|
|
||||||
|
private static bool m_currentlySettingCursor = false;
|
||||||
|
|
||||||
|
private static Type CursorType
|
||||||
|
=> m_cursorType
|
||||||
|
?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor"));
|
||||||
|
private static Type m_cursorType;
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
ModConfig.OnConfigChanged += ModConfig_OnConfigChanged;
|
||||||
|
|
||||||
|
SetupPatches();
|
||||||
|
|
||||||
|
Unlock = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ModConfig_OnConfigChanged()
|
||||||
|
{
|
||||||
|
Unlock = ModConfig.Instance.Force_Unlock_Mouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetupPatches()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CursorType == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Could not find Type 'UnityEngine.Cursor'!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current cursor state and enable cursor
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//m_lastLockMode = Cursor.lockState;
|
||||||
|
m_lastLockMode = (CursorLockMode?)typeof(Cursor).GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
|
||||||
|
?? CursorLockMode.None;
|
||||||
|
|
||||||
|
//m_lastVisibleState = Cursor.visible;
|
||||||
|
m_lastVisibleState = (bool?)typeof(Cursor).GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
|
||||||
|
?? false;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// Setup Harmony Patches
|
||||||
|
TryPatch(typeof(Cursor),
|
||||||
|
"lockState",
|
||||||
|
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_lockState))),
|
||||||
|
true);
|
||||||
|
|
||||||
|
TryPatch(typeof(Cursor),
|
||||||
|
"visible",
|
||||||
|
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_visible))),
|
||||||
|
true);
|
||||||
|
|
||||||
|
#if BIE
|
||||||
|
#if CPP
|
||||||
|
// temporarily disabling this patch in BepInEx il2cpp as it's causing a crash in some games.
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
TryPatch(typeof(EventSystem),
|
||||||
|
"current",
|
||||||
|
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_EventSystem_set_current))),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.Log($"Exception on CursorControl.Init! {e.GetType()}, {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryPatch(Type type, string property, HarmonyMethod patch, bool setter)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var harmony =
|
||||||
|
#if ML
|
||||||
|
ExplorerMelonMod.Instance.harmonyInstance;
|
||||||
|
#else
|
||||||
|
ExplorerBepInPlugin.HarmonyInstance;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
System.Reflection.PropertyInfo prop = type.GetProperty(property);
|
||||||
|
|
||||||
|
if (setter) // setter is prefix
|
||||||
|
{
|
||||||
|
harmony.Patch(prop.GetSetMethod(), prefix: patch);
|
||||||
|
}
|
||||||
|
else // getter is postfix
|
||||||
|
{
|
||||||
|
harmony.Patch(prop.GetGetMethod(), postfix: patch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
string suf = setter ? "set_" : "get_";
|
||||||
|
ExplorerCore.Log($"Unable to patch {type.Name}.{suf}{property}: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateCursorControl()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_currentlySettingCursor = true;
|
||||||
|
if (ShouldForceMouse)
|
||||||
|
{
|
||||||
|
Cursor.lockState = CursorLockMode.None;
|
||||||
|
Cursor.visible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Cursor.lockState = m_lastLockMode;
|
||||||
|
Cursor.visible = m_lastVisibleState;
|
||||||
|
}
|
||||||
|
m_currentlySettingCursor = false;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event system overrides
|
||||||
|
|
||||||
|
private static bool m_settingEventSystem;
|
||||||
|
private static EventSystem m_lastEventSystem;
|
||||||
|
private static BaseInputModule m_lastInputModule;
|
||||||
|
|
||||||
|
public static void SetEventSystem()
|
||||||
|
{
|
||||||
|
m_settingEventSystem = true;
|
||||||
|
UIManager.SetEventSystem();
|
||||||
|
m_settingEventSystem = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReleaseEventSystem()
|
||||||
|
{
|
||||||
|
if (m_lastEventSystem)
|
||||||
|
{
|
||||||
|
m_settingEventSystem = true;
|
||||||
|
EventSystem.current = m_lastEventSystem;
|
||||||
|
m_lastInputModule?.ActivateModule();
|
||||||
|
m_settingEventSystem = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||||
|
{
|
||||||
|
if (!m_settingEventSystem)
|
||||||
|
{
|
||||||
|
m_lastEventSystem = value;
|
||||||
|
m_lastInputModule = value?.currentInputModule;
|
||||||
|
|
||||||
|
if (ExplorerCore.ShowMenu)
|
||||||
|
{
|
||||||
|
value = UIManager.EventSys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
|
||||||
|
// Also keep track of when anything else tries to set Cursor state, this will be the
|
||||||
|
// value that we set back to when we close the menu or disable force-unlock.
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||||
|
{
|
||||||
|
if (!m_currentlySettingCursor)
|
||||||
|
{
|
||||||
|
m_lastLockMode = value;
|
||||||
|
|
||||||
|
if (ShouldForceMouse)
|
||||||
|
{
|
||||||
|
value = CursorLockMode.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
public static void Prefix_set_visible(ref bool value)
|
||||||
|
{
|
||||||
|
if (!m_currentlySettingCursor)
|
||||||
|
{
|
||||||
|
m_lastVisibleState = value;
|
||||||
|
|
||||||
|
if (ShouldForceMouse)
|
||||||
|
{
|
||||||
|
value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
288
src/UI/MainMenu.cs
Normal file
288
src/UI/MainMenu.cs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityExplorer.CSConsole;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI
|
||||||
|
{
|
||||||
|
public class MainMenu
|
||||||
|
{
|
||||||
|
public abstract class Page
|
||||||
|
{
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
public GameObject Content;
|
||||||
|
public Button RefNavbarButton { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get => Content?.activeSelf ?? false;
|
||||||
|
set => Content?.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void Init();
|
||||||
|
public abstract void Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MainMenu Instance { get; set; }
|
||||||
|
|
||||||
|
public PanelDragger Dragger { get; private set; }
|
||||||
|
|
||||||
|
public GameObject MainPanel { get; private set; }
|
||||||
|
public GameObject PageViewport { get; private set; }
|
||||||
|
|
||||||
|
public readonly List<Page> Pages = new List<Page>();
|
||||||
|
private Page m_activePage;
|
||||||
|
|
||||||
|
// Navbar buttons
|
||||||
|
private Button m_lastNavButtonPressed;
|
||||||
|
private readonly Color m_navButtonNormal = new Color(0.3f, 0.3f, 0.3f, 1);
|
||||||
|
private readonly Color m_navButtonHighlight = new Color(0.3f, 0.6f, 0.3f);
|
||||||
|
private readonly Color m_navButtonSelected = new Color(0.2f, 0.5f, 0.2f, 1);
|
||||||
|
|
||||||
|
public MainMenu()
|
||||||
|
{
|
||||||
|
if (Instance != null)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("An instance of MainMenu already exists, cannot create another!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
Pages.Add(new HomePage());
|
||||||
|
Pages.Add(new SearchPage());
|
||||||
|
Pages.Add(new CSConsolePage());
|
||||||
|
Pages.Add(new OptionsPage());
|
||||||
|
|
||||||
|
ConstructMenu();
|
||||||
|
|
||||||
|
foreach (Page page in Pages)
|
||||||
|
{
|
||||||
|
page.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide menu until each page has init layout (bit of a hack)
|
||||||
|
initPos = MainPanel.transform.position;
|
||||||
|
MainPanel.transform.position = new Vector3(9999, 9999);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Vector3 initPos;
|
||||||
|
internal bool pageLayoutInit;
|
||||||
|
internal int layoutInitIndex;
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!pageLayoutInit)
|
||||||
|
{
|
||||||
|
if (layoutInitIndex < Pages.Count)
|
||||||
|
{
|
||||||
|
SetPage(Pages[layoutInitIndex]);
|
||||||
|
layoutInitIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageLayoutInit = true;
|
||||||
|
MainPanel.transform.position = initPos;
|
||||||
|
SetPage(Pages[0]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_activePage?.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPage(Page page)
|
||||||
|
{
|
||||||
|
if (page == null || m_activePage == page)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// WIP, was going to hide current page if you press current page's button,
|
||||||
|
// but the main panel does not resize so its just a big empty gap there.
|
||||||
|
// Could be good if I resize that gap, not bothering for now.
|
||||||
|
// Would need a fix in PanelDragger as well.
|
||||||
|
|
||||||
|
//if (m_activePage == page)
|
||||||
|
//{
|
||||||
|
// SetButtonInactiveColors(page.RefNavbarButton);
|
||||||
|
// m_activePage.Content.SetActive(false);
|
||||||
|
// m_activePage = null;
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
m_activePage?.Content?.SetActive(false);
|
||||||
|
|
||||||
|
// unique case for console page, at the moment this will just go here
|
||||||
|
if (m_activePage is CSConsolePage)
|
||||||
|
AutoCompleter.m_mainObj?.SetActive(false);
|
||||||
|
|
||||||
|
m_activePage = page;
|
||||||
|
|
||||||
|
m_activePage.Content?.SetActive(true);
|
||||||
|
|
||||||
|
Button button = page.RefNavbarButton;
|
||||||
|
SetButtonActiveColors(button);
|
||||||
|
|
||||||
|
if (m_lastNavButtonPressed && m_lastNavButtonPressed != button)
|
||||||
|
SetButtonInactiveColors(m_lastNavButtonPressed);
|
||||||
|
|
||||||
|
m_lastNavButtonPressed = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetButtonActiveColors(Button button)
|
||||||
|
{
|
||||||
|
ColorBlock colors = button.colors;
|
||||||
|
colors.normalColor = m_navButtonSelected;
|
||||||
|
button.colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetButtonInactiveColors(Button button)
|
||||||
|
{
|
||||||
|
ColorBlock colors = button.colors;
|
||||||
|
colors.normalColor = m_navButtonNormal;
|
||||||
|
button.colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI Construction
|
||||||
|
|
||||||
|
private void ConstructMenu()
|
||||||
|
{
|
||||||
|
MainPanel = UIFactory.CreatePanel(UIManager.CanvasRoot, "MainMenu", out GameObject content);
|
||||||
|
|
||||||
|
RectTransform panelRect = MainPanel.GetComponent<RectTransform>();
|
||||||
|
panelRect.anchorMin = new Vector2(0.25f, 0.1f);
|
||||||
|
panelRect.anchorMax = new Vector2(0.78f, 0.95f);
|
||||||
|
|
||||||
|
MainPanel.AddComponent<Mask>();
|
||||||
|
|
||||||
|
ConstructTitleBar(content);
|
||||||
|
|
||||||
|
ConstructNavbar(content);
|
||||||
|
|
||||||
|
ConstructMainViewport(content);
|
||||||
|
|
||||||
|
new DebugConsole(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConstructTitleBar(GameObject content)
|
||||||
|
{
|
||||||
|
// Core title bar holder
|
||||||
|
|
||||||
|
GameObject titleBar = UIFactory.CreateHorizontalGroup(content);
|
||||||
|
|
||||||
|
HorizontalLayoutGroup titleGroup = titleBar.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
titleGroup.childControlHeight = true;
|
||||||
|
titleGroup.childControlWidth = true;
|
||||||
|
titleGroup.childForceExpandHeight = true;
|
||||||
|
titleGroup.childForceExpandWidth = true;
|
||||||
|
titleGroup.padding.left = 15;
|
||||||
|
titleGroup.padding.right = 3;
|
||||||
|
titleGroup.padding.top = 3;
|
||||||
|
titleGroup.padding.bottom = 3;
|
||||||
|
|
||||||
|
LayoutElement titleLayout = titleBar.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minHeight = 25;
|
||||||
|
titleLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// Explorer label
|
||||||
|
|
||||||
|
GameObject textObj = UIFactory.CreateLabel(titleBar, TextAnchor.MiddleLeft);
|
||||||
|
|
||||||
|
Text text = textObj.GetComponent<Text>();
|
||||||
|
text.text = $"<b>UnityExplorer</b> <i>v{ExplorerCore.VERSION}</i>";
|
||||||
|
text.fontSize = 15;
|
||||||
|
LayoutElement textLayout = textObj.AddComponent<LayoutElement>();
|
||||||
|
textLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
// Add PanelDragger using the label object
|
||||||
|
|
||||||
|
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), MainPanel.GetComponent<RectTransform>());
|
||||||
|
|
||||||
|
// Hide button
|
||||||
|
|
||||||
|
GameObject hideBtnObj = UIFactory.CreateButton(titleBar);
|
||||||
|
|
||||||
|
Button hideBtn = hideBtnObj.GetComponent<Button>();
|
||||||
|
hideBtn.onClick.AddListener(() => { ExplorerCore.ShowMenu = false; });
|
||||||
|
ColorBlock colorBlock = hideBtn.colors;
|
||||||
|
colorBlock.normalColor = new Color(65f / 255f, 23f / 255f, 23f / 255f);
|
||||||
|
colorBlock.pressedColor = new Color(35f / 255f, 10f / 255f, 10f / 255f);
|
||||||
|
colorBlock.highlightedColor = new Color(156f / 255f, 0f, 0f);
|
||||||
|
hideBtn.colors = colorBlock;
|
||||||
|
|
||||||
|
LayoutElement btnLayout = hideBtnObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minWidth = 90;
|
||||||
|
btnLayout.flexibleWidth = 2;
|
||||||
|
|
||||||
|
Text hideText = hideBtnObj.GetComponentInChildren<Text>();
|
||||||
|
hideText.color = Color.white;
|
||||||
|
hideText.resizeTextForBestFit = true;
|
||||||
|
hideText.resizeTextMinSize = 8;
|
||||||
|
hideText.resizeTextMaxSize = 14;
|
||||||
|
hideText.text = $"Hide ({ModConfig.Instance.Main_Menu_Toggle})";
|
||||||
|
|
||||||
|
ModConfig.OnConfigChanged += ModConfig_OnConfigChanged;
|
||||||
|
|
||||||
|
void ModConfig_OnConfigChanged()
|
||||||
|
{
|
||||||
|
hideText.text = $"Hide ({ModConfig.Instance.Main_Menu_Toggle})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConstructNavbar(GameObject content)
|
||||||
|
{
|
||||||
|
GameObject navbarObj = UIFactory.CreateHorizontalGroup(content);
|
||||||
|
|
||||||
|
HorizontalLayoutGroup navGroup = navbarObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
navGroup.spacing = 5;
|
||||||
|
navGroup.childControlHeight = true;
|
||||||
|
navGroup.childControlWidth = true;
|
||||||
|
navGroup.childForceExpandHeight = true;
|
||||||
|
navGroup.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
LayoutElement navLayout = navbarObj.AddComponent<LayoutElement>();
|
||||||
|
navLayout.minHeight = 25;
|
||||||
|
navLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
foreach (Page page in Pages)
|
||||||
|
{
|
||||||
|
GameObject btnObj = UIFactory.CreateButton(navbarObj);
|
||||||
|
Button btn = btnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
page.RefNavbarButton = btn;
|
||||||
|
|
||||||
|
btn.onClick.AddListener(() => { SetPage(page); });
|
||||||
|
|
||||||
|
Text text = btnObj.GetComponentInChildren<Text>();
|
||||||
|
text.text = page.Name;
|
||||||
|
|
||||||
|
// Set button colors
|
||||||
|
ColorBlock colorBlock = btn.colors;
|
||||||
|
colorBlock.normalColor = m_navButtonNormal;
|
||||||
|
//try { colorBlock.selectedColor = colorBlock.normalColor; } catch { }
|
||||||
|
colorBlock.highlightedColor = m_navButtonHighlight;
|
||||||
|
colorBlock.pressedColor = m_navButtonSelected;
|
||||||
|
btn.colors = colorBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConstructMainViewport(GameObject content)
|
||||||
|
{
|
||||||
|
GameObject mainObj = UIFactory.CreateHorizontalGroup(content);
|
||||||
|
HorizontalLayoutGroup mainGroup = mainObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
PageViewport = mainObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
120
src/UI/Modules/CSConsolePage.cs
Normal file
120
src/UI/Modules/CSConsolePage.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using UnityExplorer.CSConsole;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Modules
|
||||||
|
{
|
||||||
|
public class CSConsolePage : MainMenu.Page
|
||||||
|
{
|
||||||
|
public override string Name => "C# Console";
|
||||||
|
|
||||||
|
public static CSConsolePage Instance { get; private set; }
|
||||||
|
|
||||||
|
public CodeEditor m_codeEditor;
|
||||||
|
public ScriptEvaluator m_evaluator;
|
||||||
|
|
||||||
|
public static List<string> UsingDirectives;
|
||||||
|
|
||||||
|
public static readonly string[] DefaultUsing = new string[]
|
||||||
|
{
|
||||||
|
"System",
|
||||||
|
"System.Linq",
|
||||||
|
"System.Collections",
|
||||||
|
"System.Collections.Generic",
|
||||||
|
"System.Reflection",
|
||||||
|
"UnityEngine",
|
||||||
|
#if CPP
|
||||||
|
"UnhollowerBaseLib",
|
||||||
|
"UnhollowerRuntimeLib",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_codeEditor = new CodeEditor();
|
||||||
|
|
||||||
|
AutoCompleter.Init();
|
||||||
|
|
||||||
|
ResetConsole();
|
||||||
|
|
||||||
|
// Make sure compiler is supported on this platform
|
||||||
|
m_evaluator.Compile("");
|
||||||
|
|
||||||
|
foreach (string use in DefaultUsing)
|
||||||
|
{
|
||||||
|
AddUsing(use);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// TODO remove page button from menu?
|
||||||
|
ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
m_codeEditor?.Update();
|
||||||
|
|
||||||
|
AutoCompleter.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddUsing(string asm)
|
||||||
|
{
|
||||||
|
if (!UsingDirectives.Contains(asm))
|
||||||
|
{
|
||||||
|
Evaluate($"using {asm};", true);
|
||||||
|
UsingDirectives.Add(asm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Evaluate(string code, bool suppressWarning = false)
|
||||||
|
{
|
||||||
|
m_evaluator.Compile(code, out Mono.CSharp.CompiledMethod compiled);
|
||||||
|
|
||||||
|
if (compiled == null)
|
||||||
|
{
|
||||||
|
if (!suppressWarning)
|
||||||
|
ExplorerCore.LogWarning("Unable to compile the code!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object ret = VoidType.Value;
|
||||||
|
compiled.Invoke(ref ret);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!suppressWarning)
|
||||||
|
ExplorerCore.LogWarning($"Exception executing code: {e.GetType()}, {e.Message}\r\n{e.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetConsole()
|
||||||
|
{
|
||||||
|
if (m_evaluator != null)
|
||||||
|
{
|
||||||
|
m_evaluator.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) };
|
||||||
|
|
||||||
|
UsingDirectives = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VoidType
|
||||||
|
{
|
||||||
|
public static readonly VoidType Value = new VoidType();
|
||||||
|
private VoidType() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
301
src/UI/Modules/DebugConsole.cs
Normal file
301
src/UI/Modules/DebugConsole.cs
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Modules
|
||||||
|
{
|
||||||
|
public class DebugConsole
|
||||||
|
{
|
||||||
|
public static DebugConsole Instance { get; private set; }
|
||||||
|
|
||||||
|
public static bool LogUnity { get; set; } = ModConfig.Instance.Log_Unity_Debug;
|
||||||
|
public static bool SaveToDisk { get; set; } = ModConfig.Instance.Save_Logs_To_Disk;
|
||||||
|
|
||||||
|
internal static StreamWriter s_streamWriter;
|
||||||
|
|
||||||
|
public static readonly List<string> AllMessages = new List<string>();
|
||||||
|
public static readonly List<Text> MessageHolders = new List<Text>();
|
||||||
|
|
||||||
|
// logs that occured before the actual UI was ready.
|
||||||
|
// these ones include the hex color codes.
|
||||||
|
internal static readonly List<string> s_preInitMessages = new List<string>();
|
||||||
|
|
||||||
|
private InputField m_textInput;
|
||||||
|
internal const int MAX_TEXT_LEN = 10000;
|
||||||
|
|
||||||
|
public DebugConsole(GameObject parent)
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
ConstructUI(parent);
|
||||||
|
|
||||||
|
// append messages that logged before we were set up
|
||||||
|
string preAppend = "";
|
||||||
|
for (int i = s_preInitMessages.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var msg = s_preInitMessages[i];
|
||||||
|
if (preAppend != "")
|
||||||
|
preAppend += "\r\n";
|
||||||
|
preAppend += msg;
|
||||||
|
}
|
||||||
|
m_textInput.text = preAppend;
|
||||||
|
|
||||||
|
// set up IO
|
||||||
|
|
||||||
|
if (!SaveToDisk)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var path = ExplorerCore.EXPLORER_FOLDER + @"\Logs";
|
||||||
|
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
|
// clean old log(s)
|
||||||
|
var files = Directory.GetFiles(path);
|
||||||
|
if (files.Length >= 10)
|
||||||
|
{
|
||||||
|
var sorted = files.ToList();
|
||||||
|
// sort by 'datetime.ToString("u")' will put the oldest ones first
|
||||||
|
sorted.Sort();
|
||||||
|
for (int i = 0; i < files.Length - 9; i++)
|
||||||
|
File.Delete(files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = "UnityExplorer " + DateTime.Now.ToString("u") + ".txt";
|
||||||
|
fileName = ExplorerCore.RemoveInvalidFilenameChars(fileName);
|
||||||
|
|
||||||
|
var stream = File.Create(path + @"\" + fileName);
|
||||||
|
s_streamWriter = new StreamWriter(stream)
|
||||||
|
{
|
||||||
|
AutoFlush = true
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var msg in AllMessages)
|
||||||
|
s_streamWriter.WriteLine(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string message)
|
||||||
|
{
|
||||||
|
Log(message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string message, Color color)
|
||||||
|
{
|
||||||
|
Log(message, color.ToHex());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string message, string hexColor)
|
||||||
|
{
|
||||||
|
message = $"{AllMessages.Count}: {message}";
|
||||||
|
|
||||||
|
AllMessages.Add(message);
|
||||||
|
s_streamWriter?.WriteLine(message);
|
||||||
|
|
||||||
|
if (hexColor != null)
|
||||||
|
message = $"<color=#{hexColor}>{message}</color>";
|
||||||
|
|
||||||
|
if (Instance?.m_textInput)
|
||||||
|
{
|
||||||
|
var input = Instance.m_textInput;
|
||||||
|
var wanted = $"{message}\n{input.text}";
|
||||||
|
|
||||||
|
if (wanted.Length > MAX_TEXT_LEN)
|
||||||
|
wanted = wanted.Substring(0, MAX_TEXT_LEN);
|
||||||
|
|
||||||
|
input.text = wanted;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s_preInitMessages.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConstructUI(GameObject parent)
|
||||||
|
{
|
||||||
|
var mainObj = UIFactory.CreateVerticalGroup(parent, new Color(0.1f, 0.1f, 0.1f, 1.0f));
|
||||||
|
|
||||||
|
var mainGroup = mainObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
var mainImage = mainObj.GetComponent<Image>();
|
||||||
|
mainImage.maskable = true;
|
||||||
|
|
||||||
|
var mask = mainObj.AddComponent<Mask>();
|
||||||
|
mask.showMaskGraphic = true;
|
||||||
|
|
||||||
|
var mainLayout = mainObj.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.minHeight = 190;
|
||||||
|
mainLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
#region LOG AREA
|
||||||
|
var logAreaObj = UIFactory.CreateHorizontalGroup(mainObj);
|
||||||
|
var logAreaGroup = logAreaObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
logAreaGroup.childControlHeight = true;
|
||||||
|
logAreaGroup.childControlWidth = true;
|
||||||
|
logAreaGroup.childForceExpandHeight = true;
|
||||||
|
logAreaGroup.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
var logAreaLayout = logAreaObj.AddComponent<LayoutElement>();
|
||||||
|
logAreaLayout.preferredHeight = 190;
|
||||||
|
logAreaLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var inputScrollerObj = UIFactory.CreateSrollInputField(logAreaObj, out InputFieldScroller inputScroll, 14, new Color(0.05f, 0.05f, 0.05f));
|
||||||
|
|
||||||
|
inputScroll.inputField.textComponent.font = UIManager.ConsoleFont;
|
||||||
|
inputScroll.inputField.readOnly = true;
|
||||||
|
|
||||||
|
m_textInput = inputScroll.inputField;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BOTTOM BAR
|
||||||
|
|
||||||
|
var bottomBarObj = UIFactory.CreateHorizontalGroup(mainObj);
|
||||||
|
LayoutElement topBarLayout = bottomBarObj.AddComponent<LayoutElement>();
|
||||||
|
topBarLayout.minHeight = 30;
|
||||||
|
topBarLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
var bottomGroup = bottomBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
bottomGroup.padding.left = 10;
|
||||||
|
bottomGroup.padding.right = 10;
|
||||||
|
bottomGroup.padding.top = 2;
|
||||||
|
bottomGroup.padding.bottom = 2;
|
||||||
|
bottomGroup.spacing = 10;
|
||||||
|
bottomGroup.childForceExpandHeight = true;
|
||||||
|
bottomGroup.childForceExpandWidth = false;
|
||||||
|
bottomGroup.childControlWidth = true;
|
||||||
|
bottomGroup.childControlHeight = true;
|
||||||
|
bottomGroup.childAlignment = TextAnchor.MiddleLeft;
|
||||||
|
|
||||||
|
// Debug Console label
|
||||||
|
|
||||||
|
var bottomLabel = UIFactory.CreateLabel(bottomBarObj, TextAnchor.MiddleLeft);
|
||||||
|
var topBarLabelLayout = bottomLabel.AddComponent<LayoutElement>();
|
||||||
|
topBarLabelLayout.minWidth = 100;
|
||||||
|
topBarLabelLayout.flexibleWidth = 0;
|
||||||
|
var topBarText = bottomLabel.GetComponent<Text>();
|
||||||
|
topBarText.fontStyle = FontStyle.Bold;
|
||||||
|
topBarText.text = "Debug Console";
|
||||||
|
topBarText.fontSize = 14;
|
||||||
|
|
||||||
|
// Hide button
|
||||||
|
|
||||||
|
var hideButtonObj = UIFactory.CreateButton(bottomBarObj);
|
||||||
|
|
||||||
|
var hideBtnText = hideButtonObj.GetComponentInChildren<Text>();
|
||||||
|
hideBtnText.text = "Hide";
|
||||||
|
|
||||||
|
var hideButton = hideButtonObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
hideButton.onClick.AddListener(HideCallback);
|
||||||
|
void HideCallback()
|
||||||
|
{
|
||||||
|
if (logAreaObj.activeSelf)
|
||||||
|
{
|
||||||
|
logAreaObj.SetActive(false);
|
||||||
|
hideBtnText.text = "Show";
|
||||||
|
mainLayout.minHeight = 30;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logAreaObj.SetActive(true);
|
||||||
|
hideBtnText.text = "Hide";
|
||||||
|
mainLayout.minHeight = 190;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hideBtnColors = hideButton.colors;
|
||||||
|
//hideBtnColors.normalColor = new Color(160f / 255f, 140f / 255f, 40f / 255f);
|
||||||
|
hideButton.colors = hideBtnColors;
|
||||||
|
|
||||||
|
var hideBtnLayout = hideButtonObj.AddComponent<LayoutElement>();
|
||||||
|
hideBtnLayout.minWidth = 80;
|
||||||
|
hideBtnLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
// Clear button
|
||||||
|
|
||||||
|
var clearButtonObj = UIFactory.CreateButton(bottomBarObj);
|
||||||
|
|
||||||
|
var clearBtnText = clearButtonObj.GetComponentInChildren<Text>();
|
||||||
|
clearBtnText.text = "Clear";
|
||||||
|
|
||||||
|
var clearButton = clearButtonObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
clearButton.onClick.AddListener(ClearCallback);
|
||||||
|
void ClearCallback()
|
||||||
|
{
|
||||||
|
m_textInput.text = "";
|
||||||
|
AllMessages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearBtnColors = clearButton.colors;
|
||||||
|
//clearBtnColors.normalColor = new Color(160f/255f, 140f/255f, 40f/255f);
|
||||||
|
clearButton.colors = clearBtnColors;
|
||||||
|
|
||||||
|
var clearBtnLayout = clearButtonObj.AddComponent<LayoutElement>();
|
||||||
|
clearBtnLayout.minWidth = 80;
|
||||||
|
clearBtnLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
// Unity log toggle
|
||||||
|
|
||||||
|
var unityToggleObj = UIFactory.CreateToggle(bottomBarObj, out Toggle unityToggle, out Text unityToggleText);
|
||||||
|
|
||||||
|
unityToggle.onValueChanged.AddListener(ToggleLogUnity);
|
||||||
|
|
||||||
|
unityToggle.isOn = LogUnity;
|
||||||
|
unityToggleText.text = "Print Unity Debug?";
|
||||||
|
unityToggleText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
|
||||||
|
void ToggleLogUnity(bool val)
|
||||||
|
{
|
||||||
|
LogUnity = val;
|
||||||
|
ModConfig.Instance.Log_Unity_Debug = val;
|
||||||
|
ModConfig.SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
var unityToggleLayout = unityToggleObj.AddComponent<LayoutElement>();
|
||||||
|
unityToggleLayout.minWidth = 170;
|
||||||
|
unityToggleLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
var unityToggleRect = unityToggleObj.transform.Find("Background").GetComponent<RectTransform>();
|
||||||
|
var pos = unityToggleRect.localPosition;
|
||||||
|
pos.y = -4;
|
||||||
|
unityToggleRect.localPosition = pos;
|
||||||
|
|
||||||
|
// // Save to disk button
|
||||||
|
|
||||||
|
// var saveToDiskObj = UIFactory.CreateToggle(bottomBarObj, out Toggle diskToggle, out Text diskToggleText);
|
||||||
|
|
||||||
|
// diskToggle.onValueChanged.AddListener(ToggleDisk);
|
||||||
|
|
||||||
|
// diskToggle.isOn = SaveToDisk;
|
||||||
|
// diskToggleText.text = "Save logs to 'Mods\\UnityExplorer\\Logs'?";
|
||||||
|
// diskToggleText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
|
||||||
|
// void ToggleDisk(bool val)
|
||||||
|
// {
|
||||||
|
// SaveToDisk = val;
|
||||||
|
// ModConfig.Instance.Save_Logs_To_Disk = val;
|
||||||
|
// ModConfig.SaveSettings();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var diskToggleLayout = saveToDiskObj.AddComponent<LayoutElement>();
|
||||||
|
// diskToggleLayout.minWidth = 340;
|
||||||
|
// diskToggleLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
// var saveToDiskRect = saveToDiskObj.transform.Find("Background").GetComponent<RectTransform>();
|
||||||
|
// pos = unityToggleRect.localPosition;
|
||||||
|
// pos.y = -8;
|
||||||
|
// saveToDiskRect.localPosition = pos;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/UI/Modules/HomePage.cs
Normal file
52
src/UI/Modules/HomePage.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Inspectors;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Modules
|
||||||
|
{
|
||||||
|
public class HomePage : MainMenu.Page
|
||||||
|
{
|
||||||
|
public override string Name => "Home";
|
||||||
|
|
||||||
|
public static HomePage Instance { get; internal set; }
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
ConstructMenu();
|
||||||
|
|
||||||
|
new SceneExplorer();
|
||||||
|
|
||||||
|
new InspectorManager();
|
||||||
|
|
||||||
|
SceneExplorer.Instance.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
SceneExplorer.Instance.Update();
|
||||||
|
InspectorManager.Instance.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConstructMenu()
|
||||||
|
{
|
||||||
|
GameObject parent = MainMenu.Instance.PageViewport;
|
||||||
|
|
||||||
|
Content = UIFactory.CreateHorizontalGroup(parent);
|
||||||
|
var mainGroup = Content.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
mainGroup.padding.left = 1;
|
||||||
|
mainGroup.padding.right = 1;
|
||||||
|
mainGroup.padding.top = 1;
|
||||||
|
mainGroup.padding.bottom = 1;
|
||||||
|
mainGroup.spacing = 3;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
239
src/UI/Modules/OptionsPage.cs
Normal file
239
src/UI/Modules/OptionsPage.cs
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
//using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Modules
|
||||||
|
{
|
||||||
|
public class OptionsPage : MainMenu.Page
|
||||||
|
{
|
||||||
|
public override string Name => "Options";
|
||||||
|
|
||||||
|
private InputField m_keycodeInput;
|
||||||
|
private Toggle m_unlockMouseToggle;
|
||||||
|
private InputField m_pageLimitInput;
|
||||||
|
private InputField m_defaultOutputInput;
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
ConstructUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
// not needed?
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnApply()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(m_keycodeInput.text) && Enum.Parse(typeof(KeyCode), m_keycodeInput.text) is KeyCode keyCode)
|
||||||
|
{
|
||||||
|
ModConfig.Instance.Main_Menu_Toggle = keyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModConfig.Instance.Force_Unlock_Mouse = m_unlockMouseToggle.isOn;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(m_pageLimitInput.text) && int.TryParse(m_pageLimitInput.text, out int lim))
|
||||||
|
{
|
||||||
|
ModConfig.Instance.Default_Page_Limit = lim;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModConfig.Instance.Default_Output_Path = m_defaultOutputInput.text;
|
||||||
|
|
||||||
|
// todo default output path
|
||||||
|
|
||||||
|
ModConfig.SaveSettings();
|
||||||
|
ModConfig.InvokeConfigChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructUI()
|
||||||
|
{
|
||||||
|
GameObject parent = MainMenu.Instance.PageViewport;
|
||||||
|
|
||||||
|
Content = UIFactory.CreateVerticalGroup(parent, new Color(0.15f, 0.15f, 0.15f));
|
||||||
|
var mainGroup = Content.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.padding.left = 4;
|
||||||
|
mainGroup.padding.right = 4;
|
||||||
|
mainGroup.padding.top = 4;
|
||||||
|
mainGroup.padding.bottom = 4;
|
||||||
|
mainGroup.spacing = 5;
|
||||||
|
mainGroup.childForceExpandHeight = false;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
|
||||||
|
// ~~~~~ Title ~~~~~
|
||||||
|
|
||||||
|
GameObject titleObj = UIFactory.CreateLabel(Content, TextAnchor.UpperLeft);
|
||||||
|
Text titleLabel = titleObj.GetComponent<Text>();
|
||||||
|
titleLabel.text = "Options";
|
||||||
|
titleLabel.fontSize = 20;
|
||||||
|
LayoutElement titleLayout = titleObj.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minHeight = 30;
|
||||||
|
titleLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// ~~~~~ Actual options ~~~~~
|
||||||
|
|
||||||
|
var optionsGroupObj = UIFactory.CreateVerticalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var optionsGroup = optionsGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
optionsGroup.childForceExpandHeight = false;
|
||||||
|
optionsGroup.childForceExpandWidth = true;
|
||||||
|
optionsGroup.childControlWidth = true;
|
||||||
|
optionsGroup.childControlHeight = true;
|
||||||
|
optionsGroup.spacing = 5;
|
||||||
|
optionsGroup.padding.top = 5;
|
||||||
|
optionsGroup.padding.left = 5;
|
||||||
|
optionsGroup.padding.right = 5;
|
||||||
|
optionsGroup.padding.bottom = 5;
|
||||||
|
|
||||||
|
ConstructKeycodeOpt(optionsGroupObj);
|
||||||
|
ConstructMouseUnlockOpt(optionsGroupObj);
|
||||||
|
ConstructPageLimitOpt(optionsGroupObj);
|
||||||
|
ConstructOutputPathOpt(optionsGroupObj);
|
||||||
|
|
||||||
|
var applyBtnObj = UIFactory.CreateButton(Content, new Color(0.2f, 0.2f, 0.2f));
|
||||||
|
var applyText = applyBtnObj.GetComponentInChildren<Text>();
|
||||||
|
applyText.text = "Apply and Save";
|
||||||
|
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
|
||||||
|
applyLayout.minHeight = 30;
|
||||||
|
applyLayout.flexibleWidth = 1000;
|
||||||
|
var applyBtn = applyBtnObj.GetComponent<Button>();
|
||||||
|
var applyColors = applyBtn.colors;
|
||||||
|
applyColors.normalColor = new Color(0.3f, 0.7f, 0.3f);
|
||||||
|
applyBtn.colors = applyColors;
|
||||||
|
|
||||||
|
applyBtn.onClick.AddListener(OnApply);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructKeycodeOpt(GameObject parent)
|
||||||
|
{
|
||||||
|
//public KeyCode Main_Menu_Toggle = KeyCode.F7;
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childControlWidth = true;
|
||||||
|
rowGroup.childForceExpandWidth = false;
|
||||||
|
rowGroup.childControlHeight = true;
|
||||||
|
rowGroup.childForceExpandHeight = true;
|
||||||
|
var groupLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
groupLayout.minHeight = 25;
|
||||||
|
groupLayout.flexibleHeight = 0;
|
||||||
|
groupLayout.minWidth = 200;
|
||||||
|
groupLayout.flexibleWidth = 1000;
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.text = "Main Menu Toggle:";
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 150;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var keycodeInputObj = UIFactory.CreateInputField(rowObj);
|
||||||
|
|
||||||
|
m_keycodeInput = keycodeInputObj.GetComponent<InputField>();
|
||||||
|
m_keycodeInput.text = ModConfig.Instance.Main_Menu_Toggle.ToString();
|
||||||
|
|
||||||
|
m_keycodeInput.placeholder.gameObject.GetComponent<Text>().text = "KeyCode, eg. F7";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructMouseUnlockOpt(GameObject parent)
|
||||||
|
{
|
||||||
|
//public bool Force_Unlock_Mouse = true;
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childControlWidth = true;
|
||||||
|
rowGroup.childForceExpandWidth = false;
|
||||||
|
rowGroup.childControlHeight = true;
|
||||||
|
rowGroup.childForceExpandHeight = true;
|
||||||
|
var groupLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
groupLayout.minHeight = 25;
|
||||||
|
groupLayout.flexibleHeight = 0;
|
||||||
|
groupLayout.minWidth = 200;
|
||||||
|
groupLayout.flexibleWidth = 1000;
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.text = "Force Unlock Mouse:";
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 150;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
UIFactory.CreateToggle(rowObj, out m_unlockMouseToggle, out Text toggleText);
|
||||||
|
m_unlockMouseToggle.isOn = ModConfig.Instance.Force_Unlock_Mouse;
|
||||||
|
toggleText.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructPageLimitOpt(GameObject parent)
|
||||||
|
{
|
||||||
|
//public int Default_Page_Limit = 20;
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childControlWidth = true;
|
||||||
|
rowGroup.childForceExpandWidth = false;
|
||||||
|
rowGroup.childControlHeight = true;
|
||||||
|
rowGroup.childForceExpandHeight = true;
|
||||||
|
var groupLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
groupLayout.minHeight = 25;
|
||||||
|
groupLayout.flexibleHeight = 0;
|
||||||
|
groupLayout.minWidth = 200;
|
||||||
|
groupLayout.flexibleWidth = 1000;
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.text = "Default Page Limit:";
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 150;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateInputField(rowObj);
|
||||||
|
|
||||||
|
m_pageLimitInput = inputObj.GetComponent<InputField>();
|
||||||
|
m_pageLimitInput.text = ModConfig.Instance.Default_Page_Limit.ToString();
|
||||||
|
|
||||||
|
m_pageLimitInput.placeholder.gameObject.GetComponent<Text>().text = "Integer, eg. 20";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructOutputPathOpt(GameObject parent)
|
||||||
|
{
|
||||||
|
//public string Default_Output_Path = ExplorerCore.EXPLORER_FOLDER;
|
||||||
|
|
||||||
|
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
|
||||||
|
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
rowGroup.childControlWidth = true;
|
||||||
|
rowGroup.childForceExpandWidth = false;
|
||||||
|
rowGroup.childControlHeight = true;
|
||||||
|
rowGroup.childForceExpandHeight = true;
|
||||||
|
var groupLayout = rowObj.AddComponent<LayoutElement>();
|
||||||
|
groupLayout.minHeight = 25;
|
||||||
|
groupLayout.flexibleHeight = 0;
|
||||||
|
groupLayout.minWidth = 200;
|
||||||
|
groupLayout.flexibleWidth = 1000;
|
||||||
|
|
||||||
|
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
|
||||||
|
var labelText = labelObj.GetComponent<Text>();
|
||||||
|
labelText.text = "Default Output Path:";
|
||||||
|
var labelLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
labelLayout.minWidth = 150;
|
||||||
|
labelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var inputObj = UIFactory.CreateInputField(rowObj);
|
||||||
|
|
||||||
|
m_defaultOutputInput = inputObj.GetComponent<InputField>();
|
||||||
|
m_defaultOutputInput.text = ModConfig.Instance.Default_Output_Path.ToString();
|
||||||
|
|
||||||
|
m_defaultOutputInput.placeholder.gameObject.GetComponent<Text>().text = @"Directory, eg. Mods\UnityExplorer";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
712
src/UI/Modules/SearchPage.cs
Normal file
712
src/UI/Modules/SearchPage.cs
Normal file
@ -0,0 +1,712 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
//using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.Inspectors;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
#if CPP
|
||||||
|
using UnhollowerRuntimeLib;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Modules
|
||||||
|
{
|
||||||
|
internal enum SearchContext
|
||||||
|
{
|
||||||
|
UnityObject,
|
||||||
|
GameObject,
|
||||||
|
Component,
|
||||||
|
Custom,
|
||||||
|
Instance,
|
||||||
|
StaticClass
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum SceneFilter
|
||||||
|
{
|
||||||
|
Any,
|
||||||
|
Asset,
|
||||||
|
DontDestroyOnLoad,
|
||||||
|
Explicit,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ChildFilter
|
||||||
|
{
|
||||||
|
Any,
|
||||||
|
RootObject,
|
||||||
|
HasParent
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchPage : MainMenu.Page
|
||||||
|
{
|
||||||
|
public override string Name => "Search";
|
||||||
|
|
||||||
|
public static SearchPage Instance;
|
||||||
|
|
||||||
|
// ui elements
|
||||||
|
|
||||||
|
private Text m_resultCountText;
|
||||||
|
|
||||||
|
internal SearchContext m_context;
|
||||||
|
private InputField m_customTypeInput;
|
||||||
|
|
||||||
|
private InputField m_nameInput;
|
||||||
|
|
||||||
|
private Button m_selectedContextButton;
|
||||||
|
private readonly Dictionary<SearchContext, Button> m_contextButtons = new Dictionary<SearchContext, Button>();
|
||||||
|
|
||||||
|
private Dropdown m_sceneDropdown;
|
||||||
|
private int m_lastSceneCount = -1;
|
||||||
|
private SceneFilter m_sceneFilter;
|
||||||
|
|
||||||
|
private ChildFilter m_childFilter;
|
||||||
|
|
||||||
|
private GameObject m_extraFilterRow;
|
||||||
|
|
||||||
|
// Results
|
||||||
|
|
||||||
|
internal object[] m_results;
|
||||||
|
internal readonly List<object> m_resultShortList = new List<object>();
|
||||||
|
|
||||||
|
private int m_lastCount;
|
||||||
|
public PageHandler m_resultListPageHandler;
|
||||||
|
private GameObject m_resultListContent;
|
||||||
|
private readonly List<Text> m_resultListTexts = new List<Text>();
|
||||||
|
|
||||||
|
public SearchPage()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
ConstructUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSceneChange()
|
||||||
|
{
|
||||||
|
m_results = new object[0];
|
||||||
|
RefreshResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
if (HaveScenesChanged())
|
||||||
|
{
|
||||||
|
RefreshSceneDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_customTypeInput.isFocused && m_context != SearchContext.Custom)
|
||||||
|
{
|
||||||
|
OnContextButtonClicked(SearchContext.Custom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updating result list content
|
||||||
|
|
||||||
|
private void RefreshResultList()
|
||||||
|
{
|
||||||
|
m_resultListPageHandler.ListCount = m_results.Length;
|
||||||
|
|
||||||
|
int newCount = 0;
|
||||||
|
|
||||||
|
foreach (var itemIndex in m_resultListPageHandler)
|
||||||
|
{
|
||||||
|
newCount++;
|
||||||
|
|
||||||
|
// normalized index starting from 0
|
||||||
|
var i = itemIndex - m_resultListPageHandler.StartIndex;
|
||||||
|
|
||||||
|
if (itemIndex >= m_results.Length)
|
||||||
|
{
|
||||||
|
if (i > m_lastCount || i >= m_resultListTexts.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
GameObject label = m_resultListTexts[i].transform.parent.parent.gameObject;
|
||||||
|
if (label.activeSelf)
|
||||||
|
label.SetActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var obj = m_results[itemIndex];
|
||||||
|
|
||||||
|
var uObj = obj as UnityEngine.Object;
|
||||||
|
|
||||||
|
if (obj == null || (uObj != null && !uObj))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i >= m_resultShortList.Count)
|
||||||
|
{
|
||||||
|
m_resultShortList.Add(obj);
|
||||||
|
AddResultButton();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_resultShortList[i] = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = m_resultListTexts[i];
|
||||||
|
|
||||||
|
var name = $"<color={UISyntaxHighlight.Class_Instance}>{ReflectionHelpers.GetActualType(obj).Name}</color>";
|
||||||
|
|
||||||
|
if (m_context != SearchContext.Instance && m_context != SearchContext.StaticClass)
|
||||||
|
{
|
||||||
|
if (uObj && !string.IsNullOrEmpty(uObj.name))
|
||||||
|
name += $": {uObj.name}";
|
||||||
|
else
|
||||||
|
name += ": <i><color=grey>untitled</color></i>";
|
||||||
|
}
|
||||||
|
|
||||||
|
text.text = name;
|
||||||
|
|
||||||
|
var label = text.transform.parent.parent.gameObject;
|
||||||
|
if (!label.activeSelf)
|
||||||
|
label.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastCount = newCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scene dropdown update
|
||||||
|
|
||||||
|
internal bool HaveScenesChanged()
|
||||||
|
{
|
||||||
|
if (m_lastSceneCount != SceneManager.sceneCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
int dropdownIndex = i + 3;
|
||||||
|
if (dropdownIndex >= m_sceneDropdown.options.Count
|
||||||
|
|| m_sceneDropdown.options[dropdownIndex].text != SceneManager.GetSceneAt(i).name)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RefreshSceneDropdown()
|
||||||
|
{
|
||||||
|
m_sceneDropdown.OnCancel(null);
|
||||||
|
|
||||||
|
m_sceneDropdown.options.Clear();
|
||||||
|
|
||||||
|
m_sceneDropdown.options.Add(new Dropdown.OptionData
|
||||||
|
{
|
||||||
|
text = "Any"
|
||||||
|
});
|
||||||
|
|
||||||
|
m_sceneDropdown.options.Add(new Dropdown.OptionData
|
||||||
|
{
|
||||||
|
text = "None (Asset / Resource)"
|
||||||
|
});
|
||||||
|
m_sceneDropdown.options.Add(new Dropdown.OptionData
|
||||||
|
{
|
||||||
|
text = "DontDestroyOnLoad"
|
||||||
|
});
|
||||||
|
|
||||||
|
m_lastSceneCount = 0;
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
m_lastSceneCount++;
|
||||||
|
|
||||||
|
var scene = SceneManager.GetSceneAt(i).name;
|
||||||
|
m_sceneDropdown.options.Add(new Dropdown.OptionData
|
||||||
|
{
|
||||||
|
text = scene
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sceneDropdown.transform.Find("Label").GetComponent<Text>().text = "Any";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~~ UI Callbacks ~~~~~
|
||||||
|
|
||||||
|
internal void OnUnitySearchClicked()
|
||||||
|
{
|
||||||
|
m_resultListPageHandler.CurrentPage = 0;
|
||||||
|
|
||||||
|
Type searchType = null;
|
||||||
|
switch (m_context)
|
||||||
|
{
|
||||||
|
case SearchContext.GameObject:
|
||||||
|
searchType = typeof(GameObject); break;
|
||||||
|
|
||||||
|
case SearchContext.Component:
|
||||||
|
searchType = typeof(Component); break;
|
||||||
|
|
||||||
|
case SearchContext.Custom:
|
||||||
|
if (string.IsNullOrEmpty(m_customTypeInput.text))
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Custom Type input must not be empty!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ReflectionHelpers.GetTypeByName(m_customTypeInput.text) is Type customType)
|
||||||
|
if (typeof(UnityEngine.Object).IsAssignableFrom(customType))
|
||||||
|
searchType = customType;
|
||||||
|
else
|
||||||
|
ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!");
|
||||||
|
else
|
||||||
|
ExplorerCore.LogWarning($"Could not find a type by the name '{m_customTypeInput.text}'!");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
searchType = typeof(UnityEngine.Object); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchType == null)
|
||||||
|
return;
|
||||||
|
#if MONO
|
||||||
|
var allObjects = ResourcesUnstrip.FindObjectsOfTypeAll(searchType);
|
||||||
|
#else
|
||||||
|
var allObjects = ResourcesUnstrip.FindObjectsOfTypeAll(Il2CppType.From(searchType));
|
||||||
|
#endif
|
||||||
|
var results = new List<object>();
|
||||||
|
|
||||||
|
// perform filter comparers
|
||||||
|
|
||||||
|
string nameFilter = null;
|
||||||
|
if (!string.IsNullOrEmpty(m_nameInput.text))
|
||||||
|
nameFilter = m_nameInput.text.ToLower();
|
||||||
|
|
||||||
|
bool canGetGameObject = (m_sceneFilter != SceneFilter.Any || m_childFilter != ChildFilter.Any)
|
||||||
|
&& (m_context == SearchContext.GameObject || typeof(Component).IsAssignableFrom(searchType));
|
||||||
|
|
||||||
|
string sceneFilter = null;
|
||||||
|
if (!canGetGameObject)
|
||||||
|
{
|
||||||
|
if (m_context != SearchContext.UnityObject && (m_sceneFilter != SceneFilter.Any || m_childFilter != ChildFilter.Any))
|
||||||
|
ExplorerCore.LogWarning($"Type '{searchType}' cannot have Scene or Child filters applied to it");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_sceneFilter == SceneFilter.DontDestroyOnLoad)
|
||||||
|
sceneFilter = "DontDestroyOnLoad";
|
||||||
|
else if (m_sceneFilter == SceneFilter.Explicit)
|
||||||
|
sceneFilter = m_sceneDropdown.options[m_sceneDropdown.value].text;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var obj in allObjects)
|
||||||
|
{
|
||||||
|
// name check
|
||||||
|
if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ToLower().Contains(nameFilter))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (canGetGameObject)
|
||||||
|
{
|
||||||
|
#if MONO
|
||||||
|
var go = m_context == SearchContext.GameObject
|
||||||
|
? obj as GameObject
|
||||||
|
: (obj as Component).gameObject;
|
||||||
|
#else
|
||||||
|
var go = m_context == SearchContext.GameObject
|
||||||
|
? obj.TryCast<GameObject>()
|
||||||
|
: obj.TryCast<Component>().gameObject;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!go)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// scene check
|
||||||
|
if (m_sceneFilter != SceneFilter.Any)
|
||||||
|
{
|
||||||
|
switch (m_context)
|
||||||
|
{
|
||||||
|
case SearchContext.GameObject:
|
||||||
|
if (go.scene.name != sceneFilter)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
case SearchContext.Custom:
|
||||||
|
case SearchContext.Component:
|
||||||
|
if (go.scene.name != sceneFilter)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// root object check (no parent)
|
||||||
|
if (m_childFilter == ChildFilter.HasParent && !go.transform.parent)
|
||||||
|
continue;
|
||||||
|
else if (m_childFilter == ChildFilter.RootObject && go.transform.parent)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_results = results.ToArray();
|
||||||
|
|
||||||
|
if (m_results.Length > 0)
|
||||||
|
m_resultCountText.text = $"{m_results.Length} Results";
|
||||||
|
else
|
||||||
|
m_resultCountText.text = "No results...";
|
||||||
|
|
||||||
|
RefreshResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResultPageTurn()
|
||||||
|
{
|
||||||
|
RefreshResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnResultClicked(int index)
|
||||||
|
{
|
||||||
|
if (m_context == SearchContext.StaticClass)
|
||||||
|
InspectorManager.Instance.Inspect((Type)m_resultShortList[index]);
|
||||||
|
else
|
||||||
|
InspectorManager.Instance.Inspect(m_resultShortList[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnContextButtonClicked(SearchContext context)
|
||||||
|
{
|
||||||
|
if (m_selectedContextButton && m_context == context)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_selectedContextButton)
|
||||||
|
UIFactory.SetDefaultColorTransitionValues(m_selectedContextButton);
|
||||||
|
|
||||||
|
var button = m_contextButtons[context];
|
||||||
|
|
||||||
|
m_selectedContextButton = button;
|
||||||
|
|
||||||
|
var colors = m_selectedContextButton.colors;
|
||||||
|
colors.normalColor = new Color(0.35f, 0.7f, 0.35f);
|
||||||
|
colors.highlightedColor = colors.normalColor;
|
||||||
|
m_selectedContextButton.colors = colors;
|
||||||
|
|
||||||
|
m_context = context;
|
||||||
|
|
||||||
|
// if extra filters are valid
|
||||||
|
if (context == SearchContext.Component
|
||||||
|
|| context == SearchContext.GameObject
|
||||||
|
|| context == SearchContext.Custom)
|
||||||
|
{
|
||||||
|
m_extraFilterRow?.SetActive(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_extraFilterRow?.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
internal void ConstructUI()
|
||||||
|
{
|
||||||
|
GameObject parent = MainMenu.Instance.PageViewport;
|
||||||
|
|
||||||
|
Content = UIFactory.CreateVerticalGroup(parent);
|
||||||
|
var mainGroup = Content.GetComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.padding.left = 4;
|
||||||
|
mainGroup.padding.right = 4;
|
||||||
|
mainGroup.padding.top = 4;
|
||||||
|
mainGroup.padding.bottom = 4;
|
||||||
|
mainGroup.spacing = 5;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
|
||||||
|
ConstructTopArea();
|
||||||
|
|
||||||
|
ConstructResultsArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructTopArea()
|
||||||
|
{
|
||||||
|
var topAreaObj = UIFactory.CreateVerticalGroup(Content, new Color(0.15f, 0.15f, 0.15f));
|
||||||
|
var topGroup = topAreaObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
topGroup.childForceExpandHeight = false;
|
||||||
|
topGroup.childControlHeight = true;
|
||||||
|
topGroup.childForceExpandWidth = true;
|
||||||
|
topGroup.childControlWidth = true;
|
||||||
|
topGroup.padding.top = 5;
|
||||||
|
topGroup.padding.left = 5;
|
||||||
|
topGroup.padding.right = 5;
|
||||||
|
topGroup.padding.bottom = 5;
|
||||||
|
topGroup.spacing = 5;
|
||||||
|
|
||||||
|
GameObject titleObj = UIFactory.CreateLabel(topAreaObj, TextAnchor.UpperLeft);
|
||||||
|
Text titleLabel = titleObj.GetComponent<Text>();
|
||||||
|
titleLabel.text = "Search";
|
||||||
|
titleLabel.fontSize = 20;
|
||||||
|
LayoutElement titleLayout = titleObj.AddComponent<LayoutElement>();
|
||||||
|
titleLayout.minHeight = 30;
|
||||||
|
titleLayout.flexibleHeight = 0;
|
||||||
|
|
||||||
|
// top area options
|
||||||
|
|
||||||
|
var optionsGroupObj = UIFactory.CreateVerticalGroup(topAreaObj, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
var optionsGroup = optionsGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
optionsGroup.childForceExpandHeight = false;
|
||||||
|
optionsGroup.childControlHeight = true;
|
||||||
|
optionsGroup.childForceExpandWidth = true;
|
||||||
|
optionsGroup.childControlWidth = true;
|
||||||
|
optionsGroup.spacing = 10;
|
||||||
|
optionsGroup.padding.top = 4;
|
||||||
|
optionsGroup.padding.right = 4;
|
||||||
|
optionsGroup.padding.left = 4;
|
||||||
|
optionsGroup.padding.bottom = 4;
|
||||||
|
var optionsLayout = optionsGroupObj.AddComponent<LayoutElement>();
|
||||||
|
optionsLayout.minWidth = 500;
|
||||||
|
optionsLayout.minHeight = 70;
|
||||||
|
optionsLayout.flexibleHeight = 100;
|
||||||
|
|
||||||
|
// search context row
|
||||||
|
|
||||||
|
var contextRowObj = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0));
|
||||||
|
var contextGroup = contextRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
contextGroup.childForceExpandWidth = false;
|
||||||
|
contextGroup.childControlWidth = true;
|
||||||
|
contextGroup.childForceExpandHeight = false;
|
||||||
|
contextGroup.childControlHeight = true;
|
||||||
|
contextGroup.spacing = 3;
|
||||||
|
var contextLayout = contextRowObj.AddComponent<LayoutElement>();
|
||||||
|
contextLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var contextLabelObj = UIFactory.CreateLabel(contextRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var contextText = contextLabelObj.GetComponent<Text>();
|
||||||
|
contextText.text = "Searching for:";
|
||||||
|
var contextLabelLayout = contextLabelObj.AddComponent<LayoutElement>();
|
||||||
|
contextLabelLayout.minWidth = 125;
|
||||||
|
contextLabelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// context buttons
|
||||||
|
|
||||||
|
AddContextButton(contextRowObj, "UnityEngine.Object", SearchContext.UnityObject, 140);
|
||||||
|
AddContextButton(contextRowObj, "GameObject", SearchContext.GameObject);
|
||||||
|
AddContextButton(contextRowObj, "Component", SearchContext.Component);
|
||||||
|
AddContextButton(contextRowObj, "Custom...", SearchContext.Custom);
|
||||||
|
|
||||||
|
// custom type input
|
||||||
|
|
||||||
|
var customTypeObj = UIFactory.CreateInputField(contextRowObj);
|
||||||
|
var customTypeLayout = customTypeObj.AddComponent<LayoutElement>();
|
||||||
|
customTypeLayout.minWidth = 250;
|
||||||
|
customTypeLayout.flexibleWidth = 2000;
|
||||||
|
customTypeLayout.minHeight = 25;
|
||||||
|
customTypeLayout.flexibleHeight = 0;
|
||||||
|
m_customTypeInput = customTypeObj.GetComponent<InputField>();
|
||||||
|
m_customTypeInput.placeholder.gameObject.GetComponent<Text>().text = "eg. UnityEngine.Texture2D, etc...";
|
||||||
|
|
||||||
|
// search input
|
||||||
|
|
||||||
|
var nameRowObj = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0));
|
||||||
|
var nameRowGroup = nameRowObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
nameRowGroup.childForceExpandWidth = true;
|
||||||
|
nameRowGroup.childControlWidth = true;
|
||||||
|
nameRowGroup.childForceExpandHeight = false;
|
||||||
|
nameRowGroup.childControlHeight = true;
|
||||||
|
var nameRowLayout = nameRowObj.AddComponent<LayoutElement>();
|
||||||
|
nameRowLayout.minHeight = 25;
|
||||||
|
nameRowLayout.flexibleHeight = 0;
|
||||||
|
nameRowLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
var nameLabelObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleLeft);
|
||||||
|
var nameLabelText = nameLabelObj.GetComponent<Text>();
|
||||||
|
nameLabelText.text = "Name contains:";
|
||||||
|
var nameLabelLayout = nameLabelObj.AddComponent<LayoutElement>();
|
||||||
|
nameLabelLayout.minWidth = 125;
|
||||||
|
nameLabelLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var nameInputObj = UIFactory.CreateInputField(nameRowObj);
|
||||||
|
m_nameInput = nameInputObj.GetComponent<InputField>();
|
||||||
|
//m_nameInput.placeholder.gameObject.GetComponent<TextMeshProUGUI>().text = "";
|
||||||
|
var nameInputLayout = nameInputObj.AddComponent<LayoutElement>();
|
||||||
|
nameInputLayout.minWidth = 150;
|
||||||
|
nameInputLayout.flexibleWidth = 5000;
|
||||||
|
nameInputLayout.minHeight = 25;
|
||||||
|
|
||||||
|
// extra filter row
|
||||||
|
|
||||||
|
m_extraFilterRow = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0));
|
||||||
|
m_extraFilterRow.SetActive(false);
|
||||||
|
var extraGroup = m_extraFilterRow.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
extraGroup.childForceExpandHeight = true;
|
||||||
|
extraGroup.childControlHeight = true;
|
||||||
|
extraGroup.childForceExpandWidth = false;
|
||||||
|
extraGroup.childControlWidth = true;
|
||||||
|
var filterRowLayout = m_extraFilterRow.AddComponent<LayoutElement>();
|
||||||
|
filterRowLayout.minHeight = 25;
|
||||||
|
filterRowLayout.flexibleHeight = 0;
|
||||||
|
filterRowLayout.minWidth = 125;
|
||||||
|
filterRowLayout.flexibleWidth = 150;
|
||||||
|
|
||||||
|
// scene filter
|
||||||
|
|
||||||
|
var sceneLabelObj = UIFactory.CreateLabel(m_extraFilterRow, TextAnchor.MiddleLeft);
|
||||||
|
var sceneLabel = sceneLabelObj.GetComponent<Text>();
|
||||||
|
sceneLabel.text = "Scene Filter:";
|
||||||
|
var sceneLayout = sceneLabelObj.AddComponent<LayoutElement>();
|
||||||
|
sceneLayout.minWidth = 125;
|
||||||
|
sceneLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var sceneDropObj = UIFactory.CreateDropdown(m_extraFilterRow, out m_sceneDropdown);
|
||||||
|
m_sceneDropdown.itemText.text = "Any";
|
||||||
|
m_sceneDropdown.itemText.fontSize = 12;
|
||||||
|
var sceneDropLayout = sceneDropObj.AddComponent<LayoutElement>();
|
||||||
|
sceneDropLayout.minWidth = 220;
|
||||||
|
sceneDropLayout.minHeight = 25;
|
||||||
|
|
||||||
|
m_sceneDropdown.onValueChanged.AddListener(OnSceneDropdownChanged);
|
||||||
|
void OnSceneDropdownChanged(int value)
|
||||||
|
{
|
||||||
|
if (value < 4)
|
||||||
|
m_sceneFilter = (SceneFilter)value;
|
||||||
|
else
|
||||||
|
m_sceneFilter = SceneFilter.Explicit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invisible space
|
||||||
|
|
||||||
|
var invis = UIFactory.CreateUIObject("spacer", m_extraFilterRow);
|
||||||
|
var invisLayout = invis.AddComponent<LayoutElement>();
|
||||||
|
invisLayout.minWidth = 25;
|
||||||
|
invisLayout.flexibleWidth = 0;
|
||||||
|
|
||||||
|
// children filter
|
||||||
|
|
||||||
|
var childLabelObj = UIFactory.CreateLabel(m_extraFilterRow, TextAnchor.MiddleLeft);
|
||||||
|
var childLabel = childLabelObj.GetComponent<Text>();
|
||||||
|
childLabel.text = "Child Filter:";
|
||||||
|
var childLayout = childLabelObj.AddComponent<LayoutElement>();
|
||||||
|
childLayout.minWidth = 100;
|
||||||
|
childLayout.minHeight = 25;
|
||||||
|
|
||||||
|
var childDropObj = UIFactory.CreateDropdown(m_extraFilterRow, out Dropdown childDrop);
|
||||||
|
childDrop.itemText.text = "Any";
|
||||||
|
childDrop.itemText.fontSize = 12;
|
||||||
|
var childDropLayout = childDropObj.AddComponent<LayoutElement>();
|
||||||
|
childDropLayout.minWidth = 180;
|
||||||
|
childDropLayout.minHeight = 25;
|
||||||
|
|
||||||
|
childDrop.options.Add(new Dropdown.OptionData { text = "Any" });
|
||||||
|
childDrop.options.Add(new Dropdown.OptionData { text = "Root Objects Only" });
|
||||||
|
childDrop.options.Add(new Dropdown.OptionData { text = "Children Only" });
|
||||||
|
|
||||||
|
childDrop.onValueChanged.AddListener(OnChildDropdownChanged);
|
||||||
|
void OnChildDropdownChanged(int value)
|
||||||
|
{
|
||||||
|
m_childFilter = (ChildFilter)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search button
|
||||||
|
|
||||||
|
var searchBtnObj = UIFactory.CreateButton(topAreaObj);
|
||||||
|
var searchText = searchBtnObj.GetComponentInChildren<Text>();
|
||||||
|
searchText.text = "Search";
|
||||||
|
LayoutElement searchBtnLayout = searchBtnObj.AddComponent<LayoutElement>();
|
||||||
|
searchBtnLayout.minHeight = 30;
|
||||||
|
searchBtnLayout.flexibleHeight = 0;
|
||||||
|
var searchBtn = searchBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
searchBtn.onClick.AddListener(OnUnitySearchClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddContextButton(GameObject parent, string label, SearchContext context, float width = 110)
|
||||||
|
{
|
||||||
|
var btnObj = UIFactory.CreateButton(parent);
|
||||||
|
|
||||||
|
var btn = btnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
m_contextButtons.Add(context, btn);
|
||||||
|
|
||||||
|
btn.onClick.AddListener(() => { OnContextButtonClicked(context); });
|
||||||
|
|
||||||
|
var btnLayout = btnObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.minWidth = width;
|
||||||
|
|
||||||
|
var btnText = btnObj.GetComponentInChildren<Text>();
|
||||||
|
btnText.text = label;
|
||||||
|
|
||||||
|
// if first button
|
||||||
|
if (!m_selectedContextButton)
|
||||||
|
{
|
||||||
|
OnContextButtonClicked(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ConstructResultsArea()
|
||||||
|
{
|
||||||
|
// Result group holder (NOT actual result list content)
|
||||||
|
|
||||||
|
var resultGroupObj = UIFactory.CreateVerticalGroup(Content, new Color(1,1,1,0));
|
||||||
|
var resultGroup = resultGroupObj.GetComponent<VerticalLayoutGroup>();
|
||||||
|
resultGroup.childForceExpandHeight = false;
|
||||||
|
resultGroup.childForceExpandWidth = true;
|
||||||
|
resultGroup.childControlHeight = true;
|
||||||
|
resultGroup.childControlWidth = true;
|
||||||
|
resultGroup.spacing = 5;
|
||||||
|
resultGroup.padding.top = 5;
|
||||||
|
resultGroup.padding.right = 5;
|
||||||
|
resultGroup.padding.left = 5;
|
||||||
|
resultGroup.padding.bottom = 5;
|
||||||
|
|
||||||
|
var resultCountObj = UIFactory.CreateLabel(resultGroupObj, TextAnchor.MiddleCenter);
|
||||||
|
m_resultCountText = resultCountObj.GetComponent<Text>();
|
||||||
|
m_resultCountText.text = "No results...";
|
||||||
|
|
||||||
|
GameObject scrollObj = UIFactory.CreateScrollView(resultGroupObj,
|
||||||
|
out m_resultListContent,
|
||||||
|
out SliderScrollbar scroller,
|
||||||
|
new Color(0.07f, 0.07f, 0.07f, 1));
|
||||||
|
|
||||||
|
m_resultListPageHandler = new PageHandler(scroller);
|
||||||
|
m_resultListPageHandler.ConstructUI(resultGroupObj);
|
||||||
|
m_resultListPageHandler.OnPageChanged += OnResultPageTurn;
|
||||||
|
|
||||||
|
// actual result list content
|
||||||
|
var contentGroup = m_resultListContent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
contentGroup.spacing = 2;
|
||||||
|
contentGroup.childForceExpandHeight = false;
|
||||||
|
contentGroup.childControlHeight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddResultButton()
|
||||||
|
{
|
||||||
|
int thisIndex = m_resultListTexts.Count();
|
||||||
|
|
||||||
|
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(m_resultListContent, new Color(0.1f, 0.1f, 0.1f));
|
||||||
|
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
btnGroup.childForceExpandWidth = true;
|
||||||
|
btnGroup.childControlWidth = true;
|
||||||
|
btnGroup.childForceExpandHeight = false;
|
||||||
|
btnGroup.childControlHeight = true;
|
||||||
|
btnGroup.padding.top = 1;
|
||||||
|
btnGroup.padding.left = 1;
|
||||||
|
btnGroup.padding.right = 1;
|
||||||
|
btnGroup.padding.bottom = 1;
|
||||||
|
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
|
||||||
|
btnLayout.flexibleWidth = 320;
|
||||||
|
btnLayout.minHeight = 25;
|
||||||
|
btnLayout.flexibleHeight = 0;
|
||||||
|
btnGroupObj.AddComponent<Mask>();
|
||||||
|
|
||||||
|
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
|
||||||
|
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
|
||||||
|
mainBtnLayout.minHeight = 25;
|
||||||
|
mainBtnLayout.flexibleHeight = 0;
|
||||||
|
mainBtnLayout.minWidth = 230;
|
||||||
|
mainBtnLayout.flexibleWidth = 0;
|
||||||
|
Button mainBtn = mainButtonObj.GetComponent<Button>();
|
||||||
|
ColorBlock mainColors = mainBtn.colors;
|
||||||
|
mainColors.normalColor = new Color(0.1f, 0.1f, 0.1f);
|
||||||
|
mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
mainBtn.colors = mainColors;
|
||||||
|
|
||||||
|
mainBtn.onClick.AddListener(() => { OnResultClicked(thisIndex); });
|
||||||
|
|
||||||
|
Text mainText = mainButtonObj.GetComponentInChildren<Text>();
|
||||||
|
mainText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
m_resultListTexts.Add(mainText);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
359
src/UI/PanelDragger.cs
Normal file
359
src/UI/PanelDragger.cs
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Input;
|
||||||
|
using System.IO;
|
||||||
|
using UnityExplorer.Inspectors;
|
||||||
|
#if CPP
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI
|
||||||
|
{
|
||||||
|
// Handles dragging and resizing for the main explorer window.
|
||||||
|
|
||||||
|
public class PanelDragger
|
||||||
|
{
|
||||||
|
public static PanelDragger Instance { get; private set; }
|
||||||
|
|
||||||
|
public RectTransform Panel { get; set; }
|
||||||
|
|
||||||
|
public static event Action OnFinishResize;
|
||||||
|
|
||||||
|
public PanelDragger(RectTransform dragArea, RectTransform panelToDrag)
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
DragableArea = dragArea;
|
||||||
|
Panel = panelToDrag;
|
||||||
|
|
||||||
|
UpdateResizeCache();
|
||||||
|
|
||||||
|
SceneExplorer.OnToggleShow += OnEndResize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
Vector3 rawMousePos = InputManager.MousePosition;
|
||||||
|
|
||||||
|
ResizeTypes type;
|
||||||
|
Vector3 resizePos = Panel.InverseTransformPoint(rawMousePos);
|
||||||
|
|
||||||
|
Vector3 dragPos = DragableArea.InverseTransformPoint(rawMousePos);
|
||||||
|
bool inDragPos = DragableArea.rect.Contains(dragPos);
|
||||||
|
|
||||||
|
if (WasHoveringResize && s_resizeCursorImage)
|
||||||
|
{
|
||||||
|
UpdateHoverImagePos();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Mouse pressed this frame
|
||||||
|
if (InputManager.GetMouseButtonDown(0))
|
||||||
|
{
|
||||||
|
if (inDragPos)
|
||||||
|
{
|
||||||
|
OnBeginDrag();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (MouseInResizeArea(resizePos))
|
||||||
|
{
|
||||||
|
type = GetResizeType(resizePos);
|
||||||
|
if (type != ResizeTypes.NONE)
|
||||||
|
{
|
||||||
|
OnBeginResize(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If mouse still pressed from last frame
|
||||||
|
else if (InputManager.GetMouseButton(0))
|
||||||
|
{
|
||||||
|
if (WasDragging)
|
||||||
|
{
|
||||||
|
OnDrag();
|
||||||
|
}
|
||||||
|
else if (WasResizing)
|
||||||
|
{
|
||||||
|
OnResize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If mouse not pressed
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (WasDragging)
|
||||||
|
{
|
||||||
|
OnEndDrag();
|
||||||
|
}
|
||||||
|
else if (WasResizing)
|
||||||
|
{
|
||||||
|
OnEndResize();
|
||||||
|
}
|
||||||
|
else if (!inDragPos && MouseInResizeArea(resizePos) && (type = GetResizeType(resizePos)) != ResizeTypes.NONE)
|
||||||
|
{
|
||||||
|
OnHoverResize(type);
|
||||||
|
}
|
||||||
|
else if (WasHoveringResize)
|
||||||
|
{
|
||||||
|
OnHoverResizeEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region DRAGGING
|
||||||
|
|
||||||
|
public RectTransform DragableArea { get; set; }
|
||||||
|
public bool WasDragging { get; set; }
|
||||||
|
private Vector3 m_lastDragPosition;
|
||||||
|
|
||||||
|
public void OnBeginDrag()
|
||||||
|
{
|
||||||
|
WasDragging = true;
|
||||||
|
m_lastDragPosition = InputManager.MousePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDrag()
|
||||||
|
{
|
||||||
|
Vector3 diff = InputManager.MousePosition - m_lastDragPosition;
|
||||||
|
m_lastDragPosition = InputManager.MousePosition;
|
||||||
|
|
||||||
|
Vector3 pos = Panel.localPosition;
|
||||||
|
float z = pos.z;
|
||||||
|
pos += diff;
|
||||||
|
pos.z = z;
|
||||||
|
Panel.localPosition = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEndDrag()
|
||||||
|
{
|
||||||
|
WasDragging = false;
|
||||||
|
//UpdateResizeCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region RESIZE
|
||||||
|
|
||||||
|
private const int RESIZE_THICKNESS = 15;
|
||||||
|
|
||||||
|
internal readonly Vector2 minResize = new Vector2(400, 400);
|
||||||
|
|
||||||
|
private bool WasResizing { get; set; }
|
||||||
|
private ResizeTypes m_currentResizeType = ResizeTypes.NONE;
|
||||||
|
private Vector2 m_lastResizePos;
|
||||||
|
|
||||||
|
private bool WasHoveringResize { get; set; }
|
||||||
|
private ResizeTypes m_lastResizeHoverType;
|
||||||
|
public static GameObject s_resizeCursorImage;
|
||||||
|
|
||||||
|
private Rect m_resizeRect;
|
||||||
|
|
||||||
|
private readonly Dictionary<ResizeTypes, Rect> m_resizeMask = new Dictionary<ResizeTypes, Rect>
|
||||||
|
{
|
||||||
|
{ ResizeTypes.Top, Rect.zero },
|
||||||
|
{ ResizeTypes.Left, Rect.zero },
|
||||||
|
{ ResizeTypes.Right, Rect.zero },
|
||||||
|
{ ResizeTypes.Bottom, Rect.zero },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ResizeTypes
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
Top = 1,
|
||||||
|
Left = 2,
|
||||||
|
Right = 4,
|
||||||
|
Bottom = 8,
|
||||||
|
TopLeft = Top | Left,
|
||||||
|
TopRight = Top | Right,
|
||||||
|
BottomLeft = Bottom | Left,
|
||||||
|
BottomRight = Bottom | Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateResizeCache()
|
||||||
|
{
|
||||||
|
int halfThick = RESIZE_THICKNESS / 2;
|
||||||
|
int dblThick = RESIZE_THICKNESS * 2;
|
||||||
|
|
||||||
|
m_resizeRect = new Rect(Panel.rect.x - halfThick,
|
||||||
|
Panel.rect.y - halfThick,
|
||||||
|
Panel.rect.width + dblThick,
|
||||||
|
Panel.rect.height + dblThick);
|
||||||
|
|
||||||
|
// calculate the four cross sections to use as flags
|
||||||
|
|
||||||
|
m_resizeMask[ResizeTypes.Bottom] = new Rect(m_resizeRect.x, m_resizeRect.y, m_resizeRect.width, RESIZE_THICKNESS);
|
||||||
|
|
||||||
|
m_resizeMask[ResizeTypes.Left] = new Rect(m_resizeRect.x, m_resizeRect.y, RESIZE_THICKNESS, m_resizeRect.height);
|
||||||
|
|
||||||
|
m_resizeMask[ResizeTypes.Top] = new Rect(m_resizeRect.x, m_resizeRect.y + Panel.rect.height, m_resizeRect.width, RESIZE_THICKNESS);
|
||||||
|
|
||||||
|
m_resizeMask[ResizeTypes.Right] = new Rect(m_resizeRect.x + Panel.rect.width, m_resizeRect.y, RESIZE_THICKNESS, m_resizeRect.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MouseInResizeArea(Vector2 mousePos)
|
||||||
|
{
|
||||||
|
return m_resizeRect.Contains(mousePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResizeTypes GetResizeType(Vector2 mousePos)
|
||||||
|
{
|
||||||
|
// Calculate which part of the resize area we're in, if any.
|
||||||
|
|
||||||
|
ResizeTypes mask = 0;
|
||||||
|
|
||||||
|
if (m_resizeMask[ResizeTypes.Top].Contains(mousePos))
|
||||||
|
mask |= ResizeTypes.Top;
|
||||||
|
else if (m_resizeMask[ResizeTypes.Bottom].Contains(mousePos))
|
||||||
|
mask |= ResizeTypes.Bottom;
|
||||||
|
|
||||||
|
if (m_resizeMask[ResizeTypes.Left].Contains(mousePos))
|
||||||
|
mask |= ResizeTypes.Left;
|
||||||
|
else if (m_resizeMask[ResizeTypes.Right].Contains(mousePos))
|
||||||
|
mask |= ResizeTypes.Right;
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHoverResize(ResizeTypes resizeType)
|
||||||
|
{
|
||||||
|
if (WasHoveringResize && m_lastResizeHoverType == resizeType)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// we are entering resize, or the resize type has changed.
|
||||||
|
|
||||||
|
WasHoveringResize = true;
|
||||||
|
m_lastResizeHoverType = resizeType;
|
||||||
|
|
||||||
|
s_resizeCursorImage.SetActive(true);
|
||||||
|
|
||||||
|
// set the rotation for the resize icon
|
||||||
|
float iconRotation = 0f;
|
||||||
|
switch (resizeType)
|
||||||
|
{
|
||||||
|
case ResizeTypes.TopRight:
|
||||||
|
case ResizeTypes.BottomLeft:
|
||||||
|
iconRotation = 45f; break;
|
||||||
|
case ResizeTypes.Top:
|
||||||
|
case ResizeTypes.Bottom:
|
||||||
|
iconRotation = 90f; break;
|
||||||
|
case ResizeTypes.TopLeft:
|
||||||
|
case ResizeTypes.BottomRight:
|
||||||
|
iconRotation = 135f; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quaternion rot = s_resizeCursorImage.transform.rotation;
|
||||||
|
rot.eulerAngles = new Vector3(0, 0, iconRotation);
|
||||||
|
s_resizeCursorImage.transform.rotation = rot;
|
||||||
|
|
||||||
|
UpdateHoverImagePos();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the resize icon position to be above the mouse
|
||||||
|
private void UpdateHoverImagePos()
|
||||||
|
{
|
||||||
|
RectTransform t = UIManager.CanvasRoot.GetComponent<RectTransform>();
|
||||||
|
s_resizeCursorImage.transform.localPosition = t.InverseTransformPoint(InputManager.MousePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHoverResizeEnd()
|
||||||
|
{
|
||||||
|
WasHoveringResize = false;
|
||||||
|
s_resizeCursorImage.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeginResize(ResizeTypes resizeType)
|
||||||
|
{
|
||||||
|
m_currentResizeType = resizeType;
|
||||||
|
m_lastResizePos = InputManager.MousePosition;
|
||||||
|
WasResizing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnResize()
|
||||||
|
{
|
||||||
|
Vector3 mousePos = InputManager.MousePosition;
|
||||||
|
Vector2 diff = m_lastResizePos - (Vector2)mousePos;
|
||||||
|
|
||||||
|
if ((Vector2)mousePos == m_lastResizePos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_lastResizePos = mousePos;
|
||||||
|
|
||||||
|
float diffX = (float)((decimal)diff.x / Screen.width);
|
||||||
|
float diffY = (float)((decimal)diff.y / Screen.height);
|
||||||
|
|
||||||
|
Vector2 anchorMin = Panel.anchorMin;
|
||||||
|
Vector2 anchorMax = Panel.anchorMax;
|
||||||
|
|
||||||
|
if (m_currentResizeType.HasFlag(ResizeTypes.Left))
|
||||||
|
anchorMin.x -= diffX;
|
||||||
|
else if (m_currentResizeType.HasFlag(ResizeTypes.Right))
|
||||||
|
anchorMax.x -= diffX;
|
||||||
|
|
||||||
|
if (m_currentResizeType.HasFlag(ResizeTypes.Top))
|
||||||
|
anchorMax.y -= diffY;
|
||||||
|
else if (m_currentResizeType.HasFlag(ResizeTypes.Bottom))
|
||||||
|
anchorMin.y -= diffY;
|
||||||
|
|
||||||
|
Panel.anchorMin = new Vector2(anchorMin.x, anchorMin.y);
|
||||||
|
Panel.anchorMax = new Vector2(anchorMax.x, anchorMax.y);
|
||||||
|
|
||||||
|
var newWidth = (anchorMax.x - anchorMin.x) * Screen.width;
|
||||||
|
var newHeight = (anchorMax.y - anchorMin.y) * Screen.height;
|
||||||
|
|
||||||
|
if (newWidth >= minResize.x)
|
||||||
|
{
|
||||||
|
Panel.anchorMin = new Vector2(anchorMin.x, Panel.anchorMin.y);
|
||||||
|
Panel.anchorMax = new Vector2(anchorMax.x, Panel.anchorMax.y);
|
||||||
|
}
|
||||||
|
if (newHeight >= minResize.y)
|
||||||
|
{
|
||||||
|
Panel.anchorMin = new Vector2(Panel.anchorMin.x, anchorMin.y);
|
||||||
|
Panel.anchorMax = new Vector2(Panel.anchorMax.x, anchorMax.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEndResize()
|
||||||
|
{
|
||||||
|
WasResizing = false;
|
||||||
|
UpdateResizeCache();
|
||||||
|
OnFinishResize?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void LoadCursorImage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sprite = UIManager.ResizeCursor;
|
||||||
|
|
||||||
|
s_resizeCursorImage = new GameObject("ResizeCursorImage");
|
||||||
|
s_resizeCursorImage.transform.SetParent(UIManager.CanvasRoot.transform);
|
||||||
|
|
||||||
|
Image image = s_resizeCursorImage.AddComponent<Image>();
|
||||||
|
image.sprite = sprite;
|
||||||
|
image.material = Graphic.defaultGraphicMaterial;
|
||||||
|
RectTransform rect = image.transform.GetComponent<RectTransform>();
|
||||||
|
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 32);
|
||||||
|
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 32);
|
||||||
|
|
||||||
|
s_resizeCursorImage.SetActive(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Exception loading cursor image!\r\n" + e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just to allow Enum to do .HasFlag() in NET 3.5
|
||||||
|
public static class Net35FlagsEx
|
||||||
|
{
|
||||||
|
public static bool HasFlag(this Enum flags, Enum value)
|
||||||
|
{
|
||||||
|
ulong num = Convert.ToUInt64(value);
|
||||||
|
return (Convert.ToUInt64(flags) & num) == num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/UI/Shared/InputFieldScroller.cs
Normal file
104
src/UI/Shared/InputFieldScroller.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Shared
|
||||||
|
{
|
||||||
|
// To fix an issue with Input Fields and allow them to go inside a ScrollRect nicely.
|
||||||
|
|
||||||
|
public class InputFieldScroller
|
||||||
|
{
|
||||||
|
public static readonly List<InputFieldScroller> Instances = new List<InputFieldScroller>();
|
||||||
|
|
||||||
|
internal SliderScrollbar sliderScroller;
|
||||||
|
internal InputField inputField;
|
||||||
|
|
||||||
|
internal RectTransform inputRect;
|
||||||
|
internal LayoutElement layoutElement;
|
||||||
|
internal VerticalLayoutGroup parentLayoutGroup;
|
||||||
|
|
||||||
|
internal static CanvasScaler canvasScaler;
|
||||||
|
|
||||||
|
public InputFieldScroller(SliderScrollbar sliderScroller, InputField inputField)
|
||||||
|
{
|
||||||
|
Instances.Add(this);
|
||||||
|
|
||||||
|
this.sliderScroller = sliderScroller;
|
||||||
|
this.inputField = inputField;
|
||||||
|
|
||||||
|
inputField.onValueChanged.AddListener(OnTextChanged);
|
||||||
|
|
||||||
|
inputRect = inputField.GetComponent<RectTransform>();
|
||||||
|
layoutElement = inputField.gameObject.AddComponent<LayoutElement>();
|
||||||
|
parentLayoutGroup = inputField.transform.parent.GetComponent<VerticalLayoutGroup>();
|
||||||
|
|
||||||
|
layoutElement.minHeight = 25;
|
||||||
|
layoutElement.minWidth = 100;
|
||||||
|
|
||||||
|
if (!canvasScaler)
|
||||||
|
canvasScaler = UIManager.CanvasRoot.GetComponent<CanvasScaler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string m_lastText;
|
||||||
|
internal bool m_updateWanted;
|
||||||
|
|
||||||
|
// only done once, to fix height on creation.
|
||||||
|
internal bool heightInitAfterLayout;
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!heightInitAfterLayout)
|
||||||
|
{
|
||||||
|
heightInitAfterLayout = true;
|
||||||
|
var height = sliderScroller.m_scrollRect.parent.parent.GetComponent<RectTransform>().rect.height;
|
||||||
|
layoutElement.preferredHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_updateWanted && inputField.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
m_updateWanted = false;
|
||||||
|
RefreshUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnTextChanged(string text)
|
||||||
|
{
|
||||||
|
m_lastText = text;
|
||||||
|
m_updateWanted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RefreshUI()
|
||||||
|
{
|
||||||
|
var curInputRect = inputField.textComponent.rectTransform.rect;
|
||||||
|
var scaleFactor = canvasScaler.scaleFactor;
|
||||||
|
|
||||||
|
// Current text settings
|
||||||
|
var texGenSettings = inputField.textComponent.GetGenerationSettings(curInputRect.size);
|
||||||
|
texGenSettings.generateOutOfBounds = false;
|
||||||
|
texGenSettings.scaleFactor = scaleFactor;
|
||||||
|
|
||||||
|
// Preferred text rect height
|
||||||
|
var textGen = inputField.textComponent.cachedTextGeneratorForLayout;
|
||||||
|
float preferredHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10;
|
||||||
|
|
||||||
|
// Default text rect height (fit to scroll parent or expand to fit text)
|
||||||
|
float minHeight = Mathf.Max(preferredHeight, sliderScroller.m_scrollRect.rect.height - 25);
|
||||||
|
|
||||||
|
layoutElement.preferredHeight = minHeight;
|
||||||
|
|
||||||
|
if (inputField.caretPosition == inputField.text.Length
|
||||||
|
&& inputField.text.Length > 0
|
||||||
|
&& inputField.text[inputField.text.Length - 1] == '\n')
|
||||||
|
{
|
||||||
|
sliderScroller.m_slider.value = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
226
src/UI/Shared/PageHandler.cs
Normal file
226
src/UI/Shared/PageHandler.cs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityExplorer.Config;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI.Shared
|
||||||
|
{
|
||||||
|
public enum Turn
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Input for setting page directly
|
||||||
|
|
||||||
|
public class PageHandler : IEnumerator
|
||||||
|
{
|
||||||
|
public PageHandler(SliderScrollbar scroll)
|
||||||
|
{
|
||||||
|
ItemsPerPage = ModConfig.Instance?.Default_Page_Limit ?? 20;
|
||||||
|
m_scrollbar = scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action OnPageChanged;
|
||||||
|
|
||||||
|
private readonly SliderScrollbar m_scrollbar;
|
||||||
|
|
||||||
|
// For now this is just set when the PageHandler is created, based on config.
|
||||||
|
// At some point I might make it possible to change this after creation again.
|
||||||
|
public int ItemsPerPage { get; }
|
||||||
|
|
||||||
|
// IEnumerator.Current
|
||||||
|
public object Current => m_currentIndex;
|
||||||
|
private int m_currentIndex = 0;
|
||||||
|
|
||||||
|
public int CurrentPage
|
||||||
|
{
|
||||||
|
get => m_currentPage;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < PageCount)
|
||||||
|
m_currentPage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private int m_currentPage;
|
||||||
|
|
||||||
|
// ui
|
||||||
|
private GameObject m_pageUIHolder;
|
||||||
|
private Text m_currentPageLabel;
|
||||||
|
|
||||||
|
// set and maintained by owner of list
|
||||||
|
private int m_listCount;
|
||||||
|
public int ListCount
|
||||||
|
{
|
||||||
|
get => m_listCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_listCount = value;
|
||||||
|
|
||||||
|
if (PageCount <= 0 && m_pageUIHolder.activeSelf)
|
||||||
|
{
|
||||||
|
m_pageUIHolder.SetActive(false);
|
||||||
|
}
|
||||||
|
else if (PageCount > 0 && !m_pageUIHolder.activeSelf)
|
||||||
|
{
|
||||||
|
m_pageUIHolder.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PageCount => (int)Math.Ceiling(ListCount / (decimal)ItemsPerPage) - 1;
|
||||||
|
|
||||||
|
// The index of the first element of the current page
|
||||||
|
public int StartIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int offset = m_currentPage * ItemsPerPage;
|
||||||
|
|
||||||
|
if (offset >= ListCount)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
m_currentPage = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int EndIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int end = StartIndex + ItemsPerPage;
|
||||||
|
if (end >= ListCount)
|
||||||
|
end = ListCount - 1;
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IEnumerator.MoveNext()
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
m_currentIndex++;
|
||||||
|
return m_currentIndex < StartIndex + ItemsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IEnumerator.Reset()
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
m_currentIndex = StartIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<int> GetEnumerator()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
while (MoveNext())
|
||||||
|
{
|
||||||
|
yield return m_currentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TurnPage(Turn direction)
|
||||||
|
{
|
||||||
|
bool didTurn = false;
|
||||||
|
if (direction == Turn.Left)
|
||||||
|
{
|
||||||
|
if (m_currentPage > 0)
|
||||||
|
{
|
||||||
|
m_currentPage--;
|
||||||
|
didTurn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_currentPage < PageCount)
|
||||||
|
{
|
||||||
|
m_currentPage++;
|
||||||
|
didTurn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (didTurn)
|
||||||
|
{
|
||||||
|
if (m_scrollbar != null)
|
||||||
|
m_scrollbar.m_scrollbar.value = 1;
|
||||||
|
|
||||||
|
OnPageChanged?.Invoke();
|
||||||
|
RefreshUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
public void Show() => m_pageUIHolder?.SetActive(true);
|
||||||
|
|
||||||
|
public void Hide() => m_pageUIHolder?.SetActive(false);
|
||||||
|
|
||||||
|
public void RefreshUI()
|
||||||
|
{
|
||||||
|
m_currentPageLabel.text = $"Page {m_currentPage + 1} / {PageCount + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConstructUI(GameObject parent)
|
||||||
|
{
|
||||||
|
m_pageUIHolder = UIFactory.CreateHorizontalGroup(parent);
|
||||||
|
|
||||||
|
Image image = m_pageUIHolder.GetComponent<Image>();
|
||||||
|
image.color = new Color(0.2f, 0.2f, 0.2f, 0.5f);
|
||||||
|
|
||||||
|
HorizontalLayoutGroup mainGroup = m_pageUIHolder.GetComponent<HorizontalLayoutGroup>();
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
mainGroup.childForceExpandWidth = false;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
|
||||||
|
LayoutElement mainLayout = m_pageUIHolder.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.minHeight = 25;
|
||||||
|
mainLayout.flexibleHeight = 0;
|
||||||
|
mainLayout.minWidth = 100;
|
||||||
|
mainLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
GameObject leftBtnObj = UIFactory.CreateButton(m_pageUIHolder, new Color(0.15f, 0.15f, 0.15f));
|
||||||
|
Button leftBtn = leftBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
leftBtn.onClick.AddListener(() => { TurnPage(Turn.Left); });
|
||||||
|
|
||||||
|
Text leftBtnText = leftBtnObj.GetComponentInChildren<Text>();
|
||||||
|
leftBtnText.text = "◄";
|
||||||
|
LayoutElement leftBtnLayout = leftBtnObj.AddComponent<LayoutElement>();
|
||||||
|
leftBtnLayout.flexibleHeight = 0f;
|
||||||
|
leftBtnLayout.flexibleWidth = 1500f;
|
||||||
|
leftBtnLayout.minWidth = 25f;
|
||||||
|
leftBtnLayout.minHeight = 25f;
|
||||||
|
|
||||||
|
GameObject labelObj = UIFactory.CreateLabel(m_pageUIHolder, TextAnchor.MiddleCenter);
|
||||||
|
m_currentPageLabel = labelObj.GetComponent<Text>();
|
||||||
|
m_currentPageLabel.text = "Page 1 / TODO";
|
||||||
|
LayoutElement textLayout = labelObj.AddComponent<LayoutElement>();
|
||||||
|
textLayout.minWidth = 100f;
|
||||||
|
textLayout.flexibleWidth = 40f;
|
||||||
|
|
||||||
|
GameObject rightBtnObj = UIFactory.CreateButton(m_pageUIHolder, new Color(0.15f, 0.15f, 0.15f));
|
||||||
|
Button rightBtn = rightBtnObj.GetComponent<Button>();
|
||||||
|
|
||||||
|
rightBtn.onClick.AddListener(() => { TurnPage(Turn.Right); });
|
||||||
|
|
||||||
|
Text rightBtnText = rightBtnObj.GetComponentInChildren<Text>();
|
||||||
|
rightBtnText.text = "►";
|
||||||
|
LayoutElement rightBtnLayout = rightBtnObj.AddComponent<LayoutElement>();
|
||||||
|
rightBtnLayout.flexibleHeight = 0;
|
||||||
|
rightBtnLayout.flexibleWidth = 1500f;
|
||||||
|
rightBtnLayout.minWidth = 25f;
|
||||||
|
rightBtnLayout.minHeight = 25;
|
||||||
|
|
||||||
|
ListCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
71
src/UI/Shared/ScrollRectEx.cs
Normal file
71
src/UI/Shared/ScrollRectEx.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
//using UnityEngine;
|
||||||
|
//using System.Collections;
|
||||||
|
//using UnityEngine.UI;
|
||||||
|
//using System;
|
||||||
|
//using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
|
||||||
|
/////////////// kinda works, not really
|
||||||
|
|
||||||
|
|
||||||
|
//public class ScrollRectEx : ScrollRect, IEventSystemHandler
|
||||||
|
//{
|
||||||
|
// internal SliderScrollbar sliderScrollbar;
|
||||||
|
|
||||||
|
// private bool ShouldRouteToParent(PointerEventData data)
|
||||||
|
// => !sliderScrollbar.IsActive
|
||||||
|
// || sliderScrollbar.m_slider.value < 0.001f && data.delta.y > 0
|
||||||
|
// || sliderScrollbar.m_slider.value == 1f && data.delta.y < 0;
|
||||||
|
|
||||||
|
// private void DoForParents<T>(Action<T> action) where T : IEventSystemHandler
|
||||||
|
// {
|
||||||
|
// Transform parent = transform.parent;
|
||||||
|
// while (parent != null)
|
||||||
|
// {
|
||||||
|
// foreach (var component in parent.GetComponents<Component>())
|
||||||
|
// {
|
||||||
|
// if (component is T)
|
||||||
|
// action((T)(IEventSystemHandler)component);
|
||||||
|
// }
|
||||||
|
// parent = parent.parent;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public override void OnScroll(PointerEventData data)
|
||||||
|
// {
|
||||||
|
// if (ShouldRouteToParent(data))
|
||||||
|
// DoForParents<IScrollHandler>((parent) => { parent.OnScroll(data); });
|
||||||
|
// else
|
||||||
|
// base.OnScroll(data);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public override void OnInitializePotentialDrag(PointerEventData eventData)
|
||||||
|
// {
|
||||||
|
// DoForParents<IInitializePotentialDragHandler>((parent) => { parent.OnInitializePotentialDrag(eventData); });
|
||||||
|
// base.OnInitializePotentialDrag(eventData);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public override void OnDrag(PointerEventData data)
|
||||||
|
// {
|
||||||
|
// if (ShouldRouteToParent(data))
|
||||||
|
// DoForParents<IDragHandler>((parent) => { parent.OnDrag(data); });
|
||||||
|
// else
|
||||||
|
// base.OnDrag(data);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public override void OnBeginDrag(UnityEngine.EventSystems.PointerEventData data)
|
||||||
|
// {
|
||||||
|
// if (ShouldRouteToParent(data))
|
||||||
|
// DoForParents<IBeginDragHandler>((parent) => { parent.OnBeginDrag(data); });
|
||||||
|
// else
|
||||||
|
// base.OnBeginDrag(data);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public override void OnEndDrag(UnityEngine.EventSystems.PointerEventData data)
|
||||||
|
// {
|
||||||
|
// if (ShouldRouteToParent(data))
|
||||||
|
// DoForParents<IEndDragHandler>((parent) => { parent.OnEndDrag(data); });
|
||||||
|
// else
|
||||||
|
// base.OnEndDrag(data);
|
||||||
|
// }
|
||||||
|
//}
|
178
src/UI/Shared/SliderScrollbar.cs
Normal file
178
src/UI/Shared/SliderScrollbar.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI;
|
||||||
|
|
||||||
|
// Basically just to fix an issue with Scrollbars, instead we use a Slider as the scrollbar.
|
||||||
|
public class SliderScrollbar
|
||||||
|
{
|
||||||
|
internal static readonly List<SliderScrollbar> Instances = new List<SliderScrollbar>();
|
||||||
|
|
||||||
|
public bool IsActive { get; private set; }
|
||||||
|
|
||||||
|
internal readonly Scrollbar m_scrollbar;
|
||||||
|
internal readonly Slider m_slider;
|
||||||
|
internal readonly RectTransform m_scrollRect;
|
||||||
|
|
||||||
|
public SliderScrollbar(Scrollbar scrollbar, Slider slider)
|
||||||
|
{
|
||||||
|
Instances.Add(this);
|
||||||
|
|
||||||
|
this.m_scrollbar = scrollbar;
|
||||||
|
this.m_slider = slider;
|
||||||
|
this.m_scrollRect = scrollbar.transform.parent.GetComponent<RectTransform>();
|
||||||
|
|
||||||
|
this.m_scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged);
|
||||||
|
this.m_slider.onValueChanged.AddListener(this.OnSliderValueChanged);
|
||||||
|
|
||||||
|
this.RefreshVisibility();
|
||||||
|
this.m_slider.Set(1f, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool CheckDestroyed()
|
||||||
|
{
|
||||||
|
if (!m_slider || !m_scrollbar)
|
||||||
|
{
|
||||||
|
Instances.Remove(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update()
|
||||||
|
{
|
||||||
|
this.RefreshVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RefreshVisibility()
|
||||||
|
{
|
||||||
|
if (!m_slider.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldShow = !Mathf.Approximately(this.m_scrollbar.size, 1);
|
||||||
|
var obj = this.m_slider.handleRect.gameObject;
|
||||||
|
|
||||||
|
if (IsActive != shouldShow)
|
||||||
|
{
|
||||||
|
IsActive = shouldShow;
|
||||||
|
obj.SetActive(IsActive);
|
||||||
|
|
||||||
|
if (IsActive)
|
||||||
|
this.m_slider.Set(this.m_scrollbar.value, false);
|
||||||
|
else
|
||||||
|
m_slider.Set(1f, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnScrollbarValueChanged(float _value)
|
||||||
|
{
|
||||||
|
if (this.m_slider.value != _value)
|
||||||
|
this.m_slider.Set(_value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSliderValueChanged(float _value)
|
||||||
|
{
|
||||||
|
this.m_scrollbar.value = _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region UI CONSTRUCTION
|
||||||
|
|
||||||
|
public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider)
|
||||||
|
{
|
||||||
|
GameObject sliderObj = UIFactory.CreateUIObject("Slider", parent, UIFactory.thinSize);
|
||||||
|
|
||||||
|
GameObject bgObj = UIFactory.CreateUIObject("Background", sliderObj);
|
||||||
|
GameObject fillAreaObj = UIFactory.CreateUIObject("Fill Area", sliderObj);
|
||||||
|
GameObject fillObj = UIFactory.CreateUIObject("Fill", fillAreaObj);
|
||||||
|
GameObject handleSlideAreaObj = UIFactory.CreateUIObject("Handle Slide Area", sliderObj);
|
||||||
|
GameObject handleObj = UIFactory.CreateUIObject("Handle", handleSlideAreaObj);
|
||||||
|
|
||||||
|
Image bgImage = bgObj.AddComponent<Image>();
|
||||||
|
bgImage.type = Image.Type.Sliced;
|
||||||
|
bgImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
|
||||||
|
|
||||||
|
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
|
||||||
|
bgRect.anchorMin = Vector2.zero;
|
||||||
|
bgRect.anchorMax = Vector2.one;
|
||||||
|
bgRect.sizeDelta = Vector2.zero;
|
||||||
|
bgRect.offsetMax = new Vector2(-10f, 0f);
|
||||||
|
|
||||||
|
RectTransform fillAreaRect = fillAreaObj.GetComponent<RectTransform>();
|
||||||
|
fillAreaRect.anchorMin = new Vector2(0f, 0.25f);
|
||||||
|
fillAreaRect.anchorMax = new Vector2(1f, 0.75f);
|
||||||
|
fillAreaRect.anchoredPosition = new Vector2(-5f, 0f);
|
||||||
|
fillAreaRect.sizeDelta = new Vector2(-20f, 0f);
|
||||||
|
|
||||||
|
Image fillImage = fillObj.AddComponent<Image>();
|
||||||
|
fillImage.type = Image.Type.Sliced;
|
||||||
|
fillImage.color = Color.clear;
|
||||||
|
|
||||||
|
fillObj.GetComponent<RectTransform>().sizeDelta = new Vector2(10f, 0f);
|
||||||
|
|
||||||
|
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
|
||||||
|
handleSlideRect.anchorMin = new Vector2(0f, 0f);
|
||||||
|
handleSlideRect.anchorMax = new Vector2(1f, 1f);
|
||||||
|
handleSlideRect.offsetMin = new Vector2(15f, 30f);
|
||||||
|
handleSlideRect.offsetMax = new Vector2(-15f, 0f);
|
||||||
|
handleSlideRect.sizeDelta = new Vector2(-30f, -30f);
|
||||||
|
|
||||||
|
Image handleImage = handleObj.AddComponent<Image>();
|
||||||
|
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
|
|
||||||
|
var handleRect = handleObj.GetComponent<RectTransform>();
|
||||||
|
handleRect.sizeDelta = new Vector2(15f, 30f);
|
||||||
|
handleRect.offsetMin = new Vector2(-13f, -28f);
|
||||||
|
handleRect.offsetMax = new Vector2(3f, -2f);
|
||||||
|
|
||||||
|
var sliderBarLayout = sliderObj.AddComponent<LayoutElement>();
|
||||||
|
sliderBarLayout.minWidth = 25;
|
||||||
|
sliderBarLayout.flexibleWidth = 0;
|
||||||
|
sliderBarLayout.minHeight = 30;
|
||||||
|
sliderBarLayout.flexibleHeight = 5000;
|
||||||
|
|
||||||
|
slider = sliderObj.AddComponent<Slider>();
|
||||||
|
slider.fillRect = fillObj.GetComponent<RectTransform>();
|
||||||
|
slider.handleRect = handleObj.GetComponent<RectTransform>();
|
||||||
|
slider.targetGraphic = handleImage;
|
||||||
|
slider.direction = Slider.Direction.BottomToTop;
|
||||||
|
UIFactory.SetDefaultColorTransitionValues(slider);
|
||||||
|
|
||||||
|
return sliderObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MONO
|
||||||
|
public static class SliderExtensions
|
||||||
|
{
|
||||||
|
// il2cpp can just use the orig method directly (forced public)
|
||||||
|
|
||||||
|
private static MethodInfo m_setMethod;
|
||||||
|
private static MethodInfo SetMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_setMethod == null)
|
||||||
|
{
|
||||||
|
m_setMethod = typeof(Slider).GetMethod("Set", ReflectionHelpers.CommonFlags, null, new[] { typeof(float), typeof(bool) }, null);
|
||||||
|
}
|
||||||
|
return m_setMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Set(this Slider slider, float value, bool invokeCallback)
|
||||||
|
{
|
||||||
|
SetMethod.Invoke(slider, new object[] { value, invokeCallback });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
717
src/UI/UIFactory.cs
Normal file
717
src/UI/UIFactory.cs
Normal file
@ -0,0 +1,717 @@
|
|||||||
|
using System;
|
||||||
|
//using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI
|
||||||
|
{
|
||||||
|
public static class UIFactory
|
||||||
|
{
|
||||||
|
internal static Vector2 thickSize = new Vector2(160f, 30f);
|
||||||
|
internal static Vector2 thinSize = new Vector2(160f, 20f);
|
||||||
|
internal static Color defaultTextColor = new Color(0.95f, 0.95f, 0.95f, 1f);
|
||||||
|
internal static Font s_defaultFont;
|
||||||
|
|
||||||
|
public static GameObject CreateUIObject(string name, GameObject parent, Vector2 size = default)
|
||||||
|
{
|
||||||
|
GameObject obj = new GameObject(name);
|
||||||
|
|
||||||
|
RectTransform rect = obj.AddComponent<RectTransform>();
|
||||||
|
if (size != default)
|
||||||
|
{
|
||||||
|
rect.sizeDelta = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetParentAndAlign(obj, parent);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetParentAndAlign(GameObject child, GameObject parent)
|
||||||
|
{
|
||||||
|
if (parent == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
child.transform.SetParent(parent.transform, false);
|
||||||
|
SetLayerRecursively(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetLayerRecursively(GameObject go)
|
||||||
|
{
|
||||||
|
go.layer = 5;
|
||||||
|
Transform transform = go.transform;
|
||||||
|
for (int i = 0; i < transform.childCount; i++)
|
||||||
|
{
|
||||||
|
SetLayerRecursively(transform.GetChild(i).gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetDefaultTextValues(Text lbl)
|
||||||
|
{
|
||||||
|
lbl.color = defaultTextColor;
|
||||||
|
|
||||||
|
if (!s_defaultFont)
|
||||||
|
s_defaultFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||||
|
|
||||||
|
if (s_defaultFont)
|
||||||
|
lbl.font = s_defaultFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetDefaultColorTransitionValues(Selectable selectable)
|
||||||
|
{
|
||||||
|
ColorBlock colors = selectable.colors;
|
||||||
|
colors.normalColor = new Color(0.35f, 0.35f, 0.35f);
|
||||||
|
colors.highlightedColor = new Color(0.45f, 0.45f, 0.45f);
|
||||||
|
colors.pressedColor = new Color(0.25f, 0.25f, 0.25f);
|
||||||
|
//colors.disabledColor = new Color(0.6f, 0.6f, 0.6f);
|
||||||
|
|
||||||
|
// fix to make all buttons become de-selected after being clicked.
|
||||||
|
// this is because i'm not setting any ColorBlock.selectedColor, because it is commonly stripped.
|
||||||
|
if (selectable is Button button)
|
||||||
|
{
|
||||||
|
button.onClick.AddListener(Deselect);
|
||||||
|
void Deselect()
|
||||||
|
{
|
||||||
|
button.OnDeselect(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
selectable.colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreatePanel(GameObject parent, string name, out GameObject content)
|
||||||
|
{
|
||||||
|
GameObject panelObj = CreateUIObject($"Panel_{name}", parent, thickSize);
|
||||||
|
|
||||||
|
RectTransform rect = panelObj.GetComponent<RectTransform>();
|
||||||
|
rect.anchorMin = Vector2.zero;
|
||||||
|
rect.anchorMax = Vector2.one;
|
||||||
|
rect.anchoredPosition = Vector2.zero;
|
||||||
|
rect.sizeDelta = Vector2.zero;
|
||||||
|
|
||||||
|
var img = panelObj.AddComponent<Image>();
|
||||||
|
img.color = Color.white;
|
||||||
|
|
||||||
|
VerticalLayoutGroup group = panelObj.AddComponent<VerticalLayoutGroup>();
|
||||||
|
group.childControlHeight = true;
|
||||||
|
group.childControlWidth = true;
|
||||||
|
group.childForceExpandHeight = true;
|
||||||
|
group.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
content = new GameObject("Content");
|
||||||
|
content.transform.parent = panelObj.transform;
|
||||||
|
|
||||||
|
Image image2 = content.AddComponent<Image>();
|
||||||
|
image2.type = Image.Type.Filled;
|
||||||
|
image2.color = new Color(0.1f, 0.1f, 0.1f);
|
||||||
|
|
||||||
|
VerticalLayoutGroup group2 = content.AddComponent<VerticalLayoutGroup>();
|
||||||
|
group2.padding.left = 3;
|
||||||
|
group2.padding.right = 3;
|
||||||
|
group2.padding.bottom = 3;
|
||||||
|
group2.padding.top = 3;
|
||||||
|
group2.spacing = 3;
|
||||||
|
group2.childControlHeight = true;
|
||||||
|
group2.childControlWidth = true;
|
||||||
|
group2.childForceExpandHeight = false;
|
||||||
|
group2.childForceExpandWidth = true;
|
||||||
|
|
||||||
|
return panelObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateGridGroup(GameObject parent, Vector2 cellSize, Vector2 spacing, Color color = default)
|
||||||
|
{
|
||||||
|
GameObject groupObj = CreateUIObject("GridLayout", parent);
|
||||||
|
|
||||||
|
GridLayoutGroup gridGroup = groupObj.AddComponent<GridLayoutGroup>();
|
||||||
|
gridGroup.childAlignment = TextAnchor.UpperLeft;
|
||||||
|
gridGroup.cellSize = cellSize;
|
||||||
|
gridGroup.spacing = spacing;
|
||||||
|
|
||||||
|
Image image = groupObj.AddComponent<Image>();
|
||||||
|
if (color != default)
|
||||||
|
{
|
||||||
|
image.color = color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
image.color = new Color(44f / 255f, 44f / 255f, 44f / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateVerticalGroup(GameObject parent, Color color = default)
|
||||||
|
{
|
||||||
|
GameObject groupObj = CreateUIObject("VerticalLayout", parent);
|
||||||
|
|
||||||
|
VerticalLayoutGroup horiGroup = groupObj.AddComponent<VerticalLayoutGroup>();
|
||||||
|
horiGroup.childAlignment = TextAnchor.UpperLeft;
|
||||||
|
horiGroup.childControlWidth = true;
|
||||||
|
horiGroup.childControlHeight = true;
|
||||||
|
|
||||||
|
Image image = groupObj.AddComponent<Image>();
|
||||||
|
if (color != default)
|
||||||
|
{
|
||||||
|
image.color = color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
image.color = new Color(44f / 255f, 44f / 255f, 44f / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateHorizontalGroup(GameObject parent, Color color = default)
|
||||||
|
{
|
||||||
|
GameObject groupObj = CreateUIObject("HorizontalLayout", parent);
|
||||||
|
|
||||||
|
HorizontalLayoutGroup horiGroup = groupObj.AddComponent<HorizontalLayoutGroup>();
|
||||||
|
horiGroup.childAlignment = TextAnchor.UpperLeft;
|
||||||
|
horiGroup.childControlWidth = true;
|
||||||
|
horiGroup.childControlHeight = true;
|
||||||
|
|
||||||
|
Image image = groupObj.AddComponent<Image>();
|
||||||
|
if (color != default)
|
||||||
|
{
|
||||||
|
image.color = color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
image.color = new Color(44f / 255f, 44f / 255f, 44f / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
//public static GameObject CreateTMPLabel(GameObject parent, TextAlignmentOptions alignment)
|
||||||
|
//{
|
||||||
|
// GameObject labelObj = CreateUIObject("Label", parent, thinSize);
|
||||||
|
|
||||||
|
// TextMeshProUGUI text = labelObj.AddComponent<TextMeshProUGUI>();
|
||||||
|
|
||||||
|
// text.alignment = alignment;
|
||||||
|
// text.richText = true;
|
||||||
|
|
||||||
|
// return labelObj;
|
||||||
|
//}
|
||||||
|
|
||||||
|
public static GameObject CreateLabel(GameObject parent, TextAnchor alignment)
|
||||||
|
{
|
||||||
|
GameObject labelObj = CreateUIObject("Label", parent, thinSize);
|
||||||
|
|
||||||
|
Text text = labelObj.AddComponent<Text>();
|
||||||
|
SetDefaultTextValues(text);
|
||||||
|
text.alignment = alignment;
|
||||||
|
text.supportRichText = true;
|
||||||
|
|
||||||
|
return labelObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateButton(GameObject parent, Color normalColor = default)
|
||||||
|
{
|
||||||
|
GameObject buttonObj = CreateUIObject("Button", parent, thinSize);
|
||||||
|
|
||||||
|
GameObject textObj = new GameObject("Text");
|
||||||
|
textObj.AddComponent<RectTransform>();
|
||||||
|
SetParentAndAlign(textObj, buttonObj);
|
||||||
|
|
||||||
|
Image image = buttonObj.AddComponent<Image>();
|
||||||
|
image.type = Image.Type.Sliced;
|
||||||
|
image.color = new Color(1, 1, 1, 0.75f);
|
||||||
|
|
||||||
|
SetDefaultColorTransitionValues(buttonObj.AddComponent<Button>());
|
||||||
|
|
||||||
|
if (normalColor != default)
|
||||||
|
{
|
||||||
|
var btn = buttonObj.GetComponent<Button>();
|
||||||
|
var colors = btn.colors;
|
||||||
|
colors.normalColor = normalColor;
|
||||||
|
btn.colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text text = textObj.AddComponent<Text>();
|
||||||
|
text.text = "Button";
|
||||||
|
SetDefaultTextValues(text);
|
||||||
|
text.alignment = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
|
RectTransform rect = textObj.GetComponent<RectTransform>();
|
||||||
|
rect.anchorMin = Vector2.zero;
|
||||||
|
rect.anchorMax = Vector2.one;
|
||||||
|
rect.sizeDelta = Vector2.zero;
|
||||||
|
|
||||||
|
return buttonObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateSlider(GameObject parent)
|
||||||
|
{
|
||||||
|
GameObject sliderObj = CreateUIObject("Slider", parent, thinSize);
|
||||||
|
|
||||||
|
GameObject bgObj = CreateUIObject("Background", sliderObj);
|
||||||
|
GameObject fillAreaObj = CreateUIObject("Fill Area", sliderObj);
|
||||||
|
GameObject fillObj = CreateUIObject("Fill", fillAreaObj);
|
||||||
|
GameObject handleSlideAreaObj = CreateUIObject("Handle Slide Area", sliderObj);
|
||||||
|
GameObject handleObj = CreateUIObject("Handle", handleSlideAreaObj);
|
||||||
|
|
||||||
|
Image bgImage = bgObj.AddComponent<Image>();
|
||||||
|
bgImage.type = Image.Type.Sliced;
|
||||||
|
bgImage.color = new Color(0.15f, 0.15f, 0.15f, 1.0f);
|
||||||
|
|
||||||
|
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
|
||||||
|
bgRect.anchorMin = new Vector2(0f, 0.25f);
|
||||||
|
bgRect.anchorMax = new Vector2(1f, 0.75f);
|
||||||
|
bgRect.sizeDelta = new Vector2(0f, 0f);
|
||||||
|
|
||||||
|
RectTransform fillAreaRect = fillAreaObj.GetComponent<RectTransform>();
|
||||||
|
fillAreaRect.anchorMin = new Vector2(0f, 0.25f);
|
||||||
|
fillAreaRect.anchorMax = new Vector2(1f, 0.75f);
|
||||||
|
fillAreaRect.anchoredPosition = new Vector2(-5f, 0f);
|
||||||
|
fillAreaRect.sizeDelta = new Vector2(-20f, 0f);
|
||||||
|
|
||||||
|
Image fillImage = fillObj.AddComponent<Image>();
|
||||||
|
fillImage.type = Image.Type.Sliced;
|
||||||
|
fillImage.color = new Color(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
|
||||||
|
fillObj.GetComponent<RectTransform>().sizeDelta = new Vector2(10f, 0f);
|
||||||
|
|
||||||
|
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
|
||||||
|
handleSlideRect.sizeDelta = new Vector2(-20f, 0f);
|
||||||
|
handleSlideRect.anchorMin = new Vector2(0f, 0f);
|
||||||
|
handleSlideRect.anchorMax = new Vector2(1f, 1f);
|
||||||
|
|
||||||
|
Image handleImage = handleObj.AddComponent<Image>();
|
||||||
|
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
|
|
||||||
|
handleObj.GetComponent<RectTransform>().sizeDelta = new Vector2(20f, 0f);
|
||||||
|
|
||||||
|
Slider slider = sliderObj.AddComponent<Slider>();
|
||||||
|
slider.fillRect = fillObj.GetComponent<RectTransform>();
|
||||||
|
slider.handleRect = handleObj.GetComponent<RectTransform>();
|
||||||
|
slider.targetGraphic = handleImage;
|
||||||
|
slider.direction = Slider.Direction.LeftToRight;
|
||||||
|
SetDefaultColorTransitionValues(slider);
|
||||||
|
|
||||||
|
return sliderObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateScrollbar(GameObject parent)
|
||||||
|
{
|
||||||
|
GameObject scrollObj = CreateUIObject("Scrollbar", parent, thinSize);
|
||||||
|
|
||||||
|
GameObject slideAreaObj = CreateUIObject("Sliding Area", scrollObj);
|
||||||
|
GameObject handleObj = CreateUIObject("Handle", slideAreaObj);
|
||||||
|
|
||||||
|
Image scrollImage = scrollObj.AddComponent<Image>();
|
||||||
|
scrollImage.type = Image.Type.Sliced;
|
||||||
|
scrollImage.color = new Color(0.1f, 0.1f, 0.1f);
|
||||||
|
|
||||||
|
Image handleImage = handleObj.AddComponent<Image>();
|
||||||
|
handleImage.type = Image.Type.Sliced;
|
||||||
|
handleImage.color = new Color(0.4f, 0.4f, 0.4f);
|
||||||
|
|
||||||
|
RectTransform slideAreaRect = slideAreaObj.GetComponent<RectTransform>();
|
||||||
|
slideAreaRect.sizeDelta = new Vector2(-20f, -20f);
|
||||||
|
slideAreaRect.anchorMin = Vector2.zero;
|
||||||
|
slideAreaRect.anchorMax = Vector2.one;
|
||||||
|
|
||||||
|
RectTransform handleRect = handleObj.GetComponent<RectTransform>();
|
||||||
|
handleRect.sizeDelta = new Vector2(20f, 20f);
|
||||||
|
|
||||||
|
Scrollbar scrollbar = scrollObj.AddComponent<Scrollbar>();
|
||||||
|
scrollbar.handleRect = handleRect;
|
||||||
|
scrollbar.targetGraphic = handleImage;
|
||||||
|
SetDefaultColorTransitionValues(scrollbar);
|
||||||
|
|
||||||
|
return scrollObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateToggle(GameObject parent, out Toggle toggle, out Text text, Color bgColor = default)
|
||||||
|
{
|
||||||
|
GameObject toggleObj = CreateUIObject("Toggle", parent, thinSize);
|
||||||
|
|
||||||
|
GameObject bgObj = CreateUIObject("Background", toggleObj);
|
||||||
|
GameObject checkObj = CreateUIObject("Checkmark", bgObj);
|
||||||
|
GameObject labelObj = CreateUIObject("Label", toggleObj);
|
||||||
|
|
||||||
|
toggle = toggleObj.AddComponent<Toggle>();
|
||||||
|
toggle.isOn = true;
|
||||||
|
Toggle toggleComp = toggle;
|
||||||
|
|
||||||
|
toggle.onValueChanged.AddListener(Deselect);
|
||||||
|
void Deselect(bool _)
|
||||||
|
{
|
||||||
|
toggleComp.OnDeselect(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Image bgImage = bgObj.AddComponent<Image>();
|
||||||
|
bgImage.color = bgColor == default
|
||||||
|
? new Color(0.2f, 0.2f, 0.2f, 1.0f)
|
||||||
|
: bgColor;
|
||||||
|
|
||||||
|
Image checkImage = checkObj.AddComponent<Image>();
|
||||||
|
checkImage.color = new Color(0.3f, 0.5f, 0.3f, 1.0f);
|
||||||
|
|
||||||
|
text = labelObj.AddComponent<Text>();
|
||||||
|
text.text = "Toggle";
|
||||||
|
SetDefaultTextValues(text);
|
||||||
|
|
||||||
|
toggle.graphic = checkImage;
|
||||||
|
toggle.targetGraphic = bgImage;
|
||||||
|
SetDefaultColorTransitionValues(toggle);
|
||||||
|
|
||||||
|
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
|
||||||
|
bgRect.anchorMin = new Vector2(0f, 1f);
|
||||||
|
bgRect.anchorMax = new Vector2(0f, 1f);
|
||||||
|
bgRect.anchoredPosition = new Vector2(13f, -13f);
|
||||||
|
bgRect.sizeDelta = new Vector2(20f, 20f);
|
||||||
|
|
||||||
|
RectTransform checkRect = checkObj.GetComponent<RectTransform>();
|
||||||
|
checkRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||||
|
checkRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||||
|
checkRect.anchoredPosition = Vector2.zero;
|
||||||
|
checkRect.sizeDelta = new Vector2(14f, 14f);
|
||||||
|
|
||||||
|
RectTransform labelRect = labelObj.GetComponent<RectTransform>();
|
||||||
|
labelRect.anchorMin = new Vector2(0f, 0f);
|
||||||
|
labelRect.anchorMax = new Vector2(1f, 1f);
|
||||||
|
labelRect.offsetMin = new Vector2(28f, 2f);
|
||||||
|
labelRect.offsetMax = new Vector2(-5f, -5f);
|
||||||
|
return toggleObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateSrollInputField(GameObject parent, out InputFieldScroller inputScroll, int fontSize = 14, Color color = default)
|
||||||
|
{
|
||||||
|
if (color == default)
|
||||||
|
color = new Color(0.15f, 0.15f, 0.15f);
|
||||||
|
|
||||||
|
var mainObj = CreateScrollView(parent, out GameObject scrollContent, out SliderScrollbar scroller, color);
|
||||||
|
|
||||||
|
var inputObj = CreateInputField(scrollContent, fontSize, 0);
|
||||||
|
|
||||||
|
var inputField = inputObj.GetComponent<InputField>();
|
||||||
|
inputField.lineType = InputField.LineType.MultiLineNewline;
|
||||||
|
inputField.targetGraphic.color = color;
|
||||||
|
|
||||||
|
inputScroll = new InputFieldScroller(scroller, inputField);
|
||||||
|
|
||||||
|
return mainObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateInputField(GameObject parent, int fontSize = 14, int alignment = 3, int wrap = 0)
|
||||||
|
{
|
||||||
|
GameObject mainObj = CreateUIObject("InputField", parent);
|
||||||
|
|
||||||
|
Image mainImage = mainObj.AddComponent<Image>();
|
||||||
|
mainImage.type = Image.Type.Sliced;
|
||||||
|
mainImage.color = new Color(0.15f, 0.15f, 0.15f);
|
||||||
|
|
||||||
|
InputField mainInput = mainObj.AddComponent<InputField>();
|
||||||
|
Navigation nav = mainInput.navigation;
|
||||||
|
nav.mode = Navigation.Mode.None;
|
||||||
|
mainInput.navigation = nav;
|
||||||
|
mainInput.lineType = InputField.LineType.SingleLine;
|
||||||
|
mainInput.interactable = true;
|
||||||
|
mainInput.transition = Selectable.Transition.ColorTint;
|
||||||
|
mainInput.targetGraphic = mainImage;
|
||||||
|
|
||||||
|
ColorBlock mainColors = mainInput.colors;
|
||||||
|
mainColors.normalColor = new Color(1, 1, 1, 1);
|
||||||
|
mainColors.highlightedColor = new Color(245f / 255f, 245f / 255f, 245f / 255f, 1.0f);
|
||||||
|
mainColors.pressedColor = new Color(200f / 255f, 200f / 255f, 200f / 255f, 1.0f);
|
||||||
|
mainColors.highlightedColor = new Color(245f / 255f, 245f / 255f, 245f / 255f, 1.0f);
|
||||||
|
mainInput.colors = mainColors;
|
||||||
|
|
||||||
|
VerticalLayoutGroup mainGroup = mainObj.AddComponent<VerticalLayoutGroup>();
|
||||||
|
mainGroup.childControlHeight = true;
|
||||||
|
mainGroup.childControlWidth = true;
|
||||||
|
mainGroup.childForceExpandWidth = true;
|
||||||
|
mainGroup.childForceExpandHeight = true;
|
||||||
|
|
||||||
|
GameObject textArea = CreateUIObject("TextArea", mainObj);
|
||||||
|
textArea.AddComponent<RectMask2D>();
|
||||||
|
|
||||||
|
RectTransform textAreaRect = textArea.GetComponent<RectTransform>();
|
||||||
|
textAreaRect.anchorMin = Vector2.zero;
|
||||||
|
textAreaRect.anchorMax = Vector2.one;
|
||||||
|
textAreaRect.offsetMin = Vector2.zero;
|
||||||
|
textAreaRect.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
// mainInput.textViewport = textArea.GetComponent<RectTransform>();
|
||||||
|
|
||||||
|
GameObject placeHolderObj = CreateUIObject("Placeholder", textArea);
|
||||||
|
Text placeholderText = placeHolderObj.AddComponent<Text>();
|
||||||
|
SetDefaultTextValues(placeholderText);
|
||||||
|
placeholderText.text = "...";
|
||||||
|
placeholderText.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
|
placeholderText.horizontalOverflow = (HorizontalWrapMode)wrap;
|
||||||
|
placeholderText.alignment = (TextAnchor)alignment;
|
||||||
|
placeholderText.fontSize = fontSize;
|
||||||
|
|
||||||
|
RectTransform placeHolderRect = placeHolderObj.GetComponent<RectTransform>();
|
||||||
|
placeHolderRect.anchorMin = Vector2.zero;
|
||||||
|
placeHolderRect.anchorMax = Vector2.one;
|
||||||
|
placeHolderRect.offsetMin = Vector2.zero;
|
||||||
|
placeHolderRect.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
LayoutElement placeholderLayout = placeHolderObj.AddComponent<LayoutElement>();
|
||||||
|
placeholderLayout.minWidth = 500;
|
||||||
|
placeholderLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
mainInput.placeholder = placeholderText;
|
||||||
|
|
||||||
|
GameObject inputTextObj = CreateUIObject("Text", textArea);
|
||||||
|
Text inputText = inputTextObj.AddComponent<Text>();
|
||||||
|
SetDefaultTextValues(inputText);
|
||||||
|
inputText.text = "";
|
||||||
|
inputText.color = new Color(1f, 1f, 1f, 1f);
|
||||||
|
inputText.horizontalOverflow = (HorizontalWrapMode)wrap;
|
||||||
|
inputText.alignment = (TextAnchor)alignment;
|
||||||
|
inputText.fontSize = fontSize;
|
||||||
|
|
||||||
|
RectTransform inputTextRect = inputTextObj.GetComponent<RectTransform>();
|
||||||
|
inputTextRect.anchorMin = Vector2.zero;
|
||||||
|
inputTextRect.anchorMax = Vector2.one;
|
||||||
|
inputTextRect.offsetMin = Vector2.zero;
|
||||||
|
inputTextRect.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
LayoutElement inputTextLayout = inputTextObj.AddComponent<LayoutElement>();
|
||||||
|
inputTextLayout.minWidth = 500;
|
||||||
|
inputTextLayout.flexibleWidth = 5000;
|
||||||
|
|
||||||
|
mainInput.textComponent = inputText;
|
||||||
|
|
||||||
|
return mainObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateDropdown(GameObject parent, out Dropdown dropdown)
|
||||||
|
{
|
||||||
|
GameObject dropdownObj = CreateUIObject("Dropdown", parent, thickSize);
|
||||||
|
|
||||||
|
GameObject labelObj = CreateUIObject("Label", dropdownObj);
|
||||||
|
GameObject arrowObj = CreateUIObject("Arrow", dropdownObj);
|
||||||
|
GameObject templateObj = CreateUIObject("Template", dropdownObj);
|
||||||
|
GameObject viewportObj = CreateUIObject("Viewport", templateObj);
|
||||||
|
GameObject contentObj = CreateUIObject("Content", viewportObj);
|
||||||
|
GameObject itemObj = CreateUIObject("Item", contentObj);
|
||||||
|
GameObject itemBgObj = CreateUIObject("Item Background", itemObj);
|
||||||
|
GameObject itemCheckObj = CreateUIObject("Item Checkmark", itemObj);
|
||||||
|
GameObject itemLabelObj = CreateUIObject("Item Label", itemObj);
|
||||||
|
|
||||||
|
GameObject scrollbarObj = CreateScrollbar(templateObj);
|
||||||
|
scrollbarObj.name = "Scrollbar";
|
||||||
|
Scrollbar scrollbar = scrollbarObj.GetComponent<Scrollbar>();
|
||||||
|
scrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true);
|
||||||
|
|
||||||
|
RectTransform scrollRectTransform = scrollbarObj.GetComponent<RectTransform>();
|
||||||
|
scrollRectTransform.anchorMin = Vector2.right;
|
||||||
|
scrollRectTransform.anchorMax = Vector2.one;
|
||||||
|
scrollRectTransform.pivot = Vector2.one;
|
||||||
|
scrollRectTransform.sizeDelta = new Vector2(scrollRectTransform.sizeDelta.x, 0f);
|
||||||
|
|
||||||
|
Text itemLabelText = itemLabelObj.AddComponent<Text>();
|
||||||
|
SetDefaultTextValues(itemLabelText);
|
||||||
|
itemLabelText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
|
||||||
|
var arrowText = arrowObj.AddComponent<Text>();
|
||||||
|
SetDefaultTextValues(arrowText);
|
||||||
|
arrowText.text = "▼";
|
||||||
|
var arrowRect = arrowObj.GetComponent<RectTransform>();
|
||||||
|
arrowRect.anchorMin = new Vector2(1f, 0.5f);
|
||||||
|
arrowRect.anchorMax = new Vector2(1f, 0.5f);
|
||||||
|
arrowRect.sizeDelta = new Vector2(20f, 20f);
|
||||||
|
arrowRect.anchoredPosition = new Vector2(-15f, 0f);
|
||||||
|
|
||||||
|
Image itemBgImage = itemBgObj.AddComponent<Image>();
|
||||||
|
itemBgImage.color = new Color(0.25f, 0.45f, 0.25f, 1.0f);
|
||||||
|
|
||||||
|
Toggle itemToggle = itemObj.AddComponent<Toggle>();
|
||||||
|
itemToggle.targetGraphic = itemBgImage;
|
||||||
|
itemToggle.isOn = true;
|
||||||
|
ColorBlock colors = itemToggle.colors;
|
||||||
|
colors.normalColor = new Color(0.35f, 0.35f, 0.35f, 1.0f);
|
||||||
|
colors.highlightedColor = new Color(0.25f, 0.45f, 0.25f, 1.0f);
|
||||||
|
itemToggle.colors = colors;
|
||||||
|
|
||||||
|
itemToggle.onValueChanged.AddListener((bool val) => { itemToggle.OnDeselect(null); });
|
||||||
|
Image templateImage = templateObj.AddComponent<Image>();
|
||||||
|
templateImage.type = Image.Type.Sliced;
|
||||||
|
templateImage.color = new Color(0.15f, 0.15f, 0.15f, 1.0f);
|
||||||
|
|
||||||
|
var scrollRect = templateObj.AddComponent<ScrollRect>();
|
||||||
|
scrollRect.scrollSensitivity = 35;
|
||||||
|
scrollRect.content = contentObj.GetComponent<RectTransform>();
|
||||||
|
scrollRect.viewport = viewportObj.GetComponent<RectTransform>();
|
||||||
|
scrollRect.horizontal = false;
|
||||||
|
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||||
|
scrollRect.verticalScrollbar = scrollbar;
|
||||||
|
scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport;
|
||||||
|
scrollRect.verticalScrollbarSpacing = -3f;
|
||||||
|
|
||||||
|
viewportObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||||
|
|
||||||
|
Image viewportImage = viewportObj.AddComponent<Image>();
|
||||||
|
viewportImage.type = Image.Type.Sliced;
|
||||||
|
|
||||||
|
Text labelText = labelObj.AddComponent<Text>();
|
||||||
|
SetDefaultTextValues(labelText);
|
||||||
|
labelText.alignment = TextAnchor.MiddleLeft;
|
||||||
|
|
||||||
|
Image dropdownImage = dropdownObj.AddComponent<Image>();
|
||||||
|
dropdownImage.color = new Color(0.2f, 0.2f, 0.2f, 1);
|
||||||
|
dropdownImage.type = Image.Type.Sliced;
|
||||||
|
|
||||||
|
dropdown = dropdownObj.AddComponent<Dropdown>();
|
||||||
|
dropdown.targetGraphic = dropdownImage;
|
||||||
|
dropdown.template = templateObj.GetComponent<RectTransform>();
|
||||||
|
dropdown.captionText = labelText;
|
||||||
|
dropdown.itemText = itemLabelText;
|
||||||
|
itemLabelText.text = "DEFAULT";
|
||||||
|
|
||||||
|
dropdown.RefreshShownValue();
|
||||||
|
|
||||||
|
RectTransform labelRect = labelObj.GetComponent<RectTransform>();
|
||||||
|
labelRect.anchorMin = Vector2.zero;
|
||||||
|
labelRect.anchorMax = Vector2.one;
|
||||||
|
labelRect.offsetMin = new Vector2(10f, 2f);
|
||||||
|
labelRect.offsetMax = new Vector2(-28f, -2f);
|
||||||
|
|
||||||
|
RectTransform templateRect = templateObj.GetComponent<RectTransform>();
|
||||||
|
templateRect.anchorMin = new Vector2(0f, 0f);
|
||||||
|
templateRect.anchorMax = new Vector2(1f, 0f);
|
||||||
|
templateRect.pivot = new Vector2(0.5f, 1f);
|
||||||
|
templateRect.anchoredPosition = new Vector2(0f, 2f);
|
||||||
|
templateRect.sizeDelta = new Vector2(0f, 150f);
|
||||||
|
|
||||||
|
RectTransform viewportRect = viewportObj.GetComponent<RectTransform>();
|
||||||
|
viewportRect.anchorMin = new Vector2(0f, 0f);
|
||||||
|
viewportRect.anchorMax = new Vector2(1f, 1f);
|
||||||
|
viewportRect.sizeDelta = new Vector2(-18f, 0f);
|
||||||
|
viewportRect.pivot = new Vector2(0f, 1f);
|
||||||
|
|
||||||
|
RectTransform contentRect = contentObj.GetComponent<RectTransform>();
|
||||||
|
contentRect.anchorMin = new Vector2(0f, 1f);
|
||||||
|
contentRect.anchorMax = new Vector2(1f, 1f);
|
||||||
|
contentRect.pivot = new Vector2(0.5f, 1f);
|
||||||
|
contentRect.anchoredPosition = new Vector2(0f, 0f);
|
||||||
|
contentRect.sizeDelta = new Vector2(0f, 28f);
|
||||||
|
|
||||||
|
RectTransform itemRect = itemObj.GetComponent<RectTransform>();
|
||||||
|
itemRect.anchorMin = new Vector2(0f, 0.5f);
|
||||||
|
itemRect.anchorMax = new Vector2(1f, 0.5f);
|
||||||
|
itemRect.sizeDelta = new Vector2(0f, 25f);
|
||||||
|
|
||||||
|
RectTransform itemBgRect = itemBgObj.GetComponent<RectTransform>();
|
||||||
|
itemBgRect.anchorMin = Vector2.zero;
|
||||||
|
itemBgRect.anchorMax = Vector2.one;
|
||||||
|
itemBgRect.sizeDelta = Vector2.zero;
|
||||||
|
|
||||||
|
RectTransform itemLabelRect = itemLabelObj.GetComponent<RectTransform>();
|
||||||
|
itemLabelRect.anchorMin = Vector2.zero;
|
||||||
|
itemLabelRect.anchorMax = Vector2.one;
|
||||||
|
itemLabelRect.offsetMin = new Vector2(20f, 1f);
|
||||||
|
itemLabelRect.offsetMax = new Vector2(-10f, -2f);
|
||||||
|
templateObj.SetActive(false);
|
||||||
|
|
||||||
|
return dropdownObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameObject CreateScrollView(GameObject parent, out GameObject content, out SliderScrollbar scroller, Color color = default)
|
||||||
|
{
|
||||||
|
GameObject mainObj = CreateUIObject("DynamicScrollView", parent);
|
||||||
|
|
||||||
|
var mainLayout = mainObj.AddComponent<LayoutElement>();
|
||||||
|
mainLayout.minWidth = 100;
|
||||||
|
mainLayout.minHeight = 30;
|
||||||
|
mainLayout.flexibleWidth = 5000;
|
||||||
|
mainLayout.flexibleHeight = 5000;
|
||||||
|
|
||||||
|
Image mainImage = mainObj.AddComponent<Image>();
|
||||||
|
mainImage.type = Image.Type.Filled;
|
||||||
|
mainImage.color = (color == default) ? new Color(0.3f, 0.3f, 0.3f, 1f) : color;
|
||||||
|
|
||||||
|
GameObject viewportObj = CreateUIObject("Viewport", mainObj);
|
||||||
|
|
||||||
|
var viewportRect = viewportObj.GetComponent<RectTransform>();
|
||||||
|
viewportRect.anchorMin = Vector2.zero;
|
||||||
|
viewportRect.anchorMax = Vector2.one;
|
||||||
|
viewportRect.pivot = new Vector2(0.0f, 1.0f);
|
||||||
|
viewportRect.sizeDelta = new Vector2(-15.0f, 0.0f);
|
||||||
|
viewportRect.offsetMax = new Vector2(-20.0f, 0.0f);
|
||||||
|
|
||||||
|
viewportObj.AddComponent<Image>().color = Color.white;
|
||||||
|
viewportObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||||
|
|
||||||
|
content = CreateUIObject("Content", viewportObj);
|
||||||
|
var contentRect = content.GetComponent<RectTransform>();
|
||||||
|
contentRect.anchorMin = new Vector2(0.0f, 1.0f);
|
||||||
|
contentRect.anchorMax = new Vector2(1.0f, 1.0f);
|
||||||
|
contentRect.pivot = new Vector2(0.0f, 1.0f);
|
||||||
|
contentRect.sizeDelta = new Vector2(5f, 0f);
|
||||||
|
contentRect.offsetMax = new Vector2(0f, 0f);
|
||||||
|
var contentFitter = content.AddComponent<ContentSizeFitter>();
|
||||||
|
contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||||
|
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
|
||||||
|
var contentLayout = content.AddComponent<VerticalLayoutGroup>();
|
||||||
|
contentLayout.childForceExpandHeight = true;
|
||||||
|
contentLayout.childControlHeight = true;
|
||||||
|
contentLayout.childForceExpandWidth = true;
|
||||||
|
contentLayout.childControlWidth = true;
|
||||||
|
contentLayout.padding.left = 5;
|
||||||
|
contentLayout.padding.right = 5;
|
||||||
|
contentLayout.padding.top = 5;
|
||||||
|
contentLayout.padding.bottom = 5;
|
||||||
|
contentLayout.spacing = 5;
|
||||||
|
|
||||||
|
GameObject scrollBarObj = CreateUIObject("DynamicScrollbar", mainObj);
|
||||||
|
|
||||||
|
var scrollbarLayout = scrollBarObj.AddComponent<VerticalLayoutGroup>();
|
||||||
|
scrollbarLayout.childForceExpandHeight = true;
|
||||||
|
scrollbarLayout.childControlHeight = true;
|
||||||
|
|
||||||
|
RectTransform scrollBarRect = scrollBarObj.GetComponent<RectTransform>();
|
||||||
|
scrollBarRect.anchorMin = new Vector2(1.0f, 0.0f);
|
||||||
|
scrollBarRect.anchorMax = new Vector2(1.0f, 1.0f);
|
||||||
|
scrollBarRect.sizeDelta = new Vector2(15.0f, 0.0f);
|
||||||
|
scrollBarRect.offsetMin = new Vector2(-15.0f, 0.0f);
|
||||||
|
|
||||||
|
GameObject hiddenBar = CreateScrollbar(scrollBarObj);
|
||||||
|
var hiddenScroll = hiddenBar.GetComponent<Scrollbar>();
|
||||||
|
hiddenScroll.SetDirection(Scrollbar.Direction.BottomToTop, true);
|
||||||
|
|
||||||
|
for (int i = 0; i < hiddenBar.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
var child = hiddenBar.transform.GetChild(i);
|
||||||
|
child.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SliderScrollbar.CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider);
|
||||||
|
|
||||||
|
// Back to the main scrollview ScrollRect, setting it up now that we have all references.
|
||||||
|
|
||||||
|
var scrollRect = mainObj.AddComponent<ScrollRect>();
|
||||||
|
scrollRect.horizontal = false;
|
||||||
|
scrollRect.vertical = true;
|
||||||
|
scrollRect.verticalScrollbar = hiddenScroll;
|
||||||
|
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||||
|
scrollRect.scrollSensitivity = 35;
|
||||||
|
scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport;
|
||||||
|
scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.Permanent;
|
||||||
|
|
||||||
|
scrollRect.viewport = viewportRect;
|
||||||
|
scrollRect.content = contentRect;
|
||||||
|
|
||||||
|
// Create a custom DynamicScrollbar module
|
||||||
|
scroller = new SliderScrollbar(hiddenScroll, scrollSlider);
|
||||||
|
|
||||||
|
//scrollRect.sliderScrollbar = scroller;
|
||||||
|
|
||||||
|
return mainObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
188
src/UI/UIManager.cs
Normal file
188
src/UI/UIManager.cs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityExplorer.Inspectors;
|
||||||
|
using UnityExplorer.UI.Modules;
|
||||||
|
using System.IO;
|
||||||
|
//using TMPro;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityExplorer.Helpers;
|
||||||
|
using UnityExplorer.UI.Shared;
|
||||||
|
#if CPP
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI
|
||||||
|
{
|
||||||
|
public static class UIManager
|
||||||
|
{
|
||||||
|
public static GameObject CanvasRoot { get; private set; }
|
||||||
|
public static EventSystem EventSys { get; private set; }
|
||||||
|
public static StandaloneInputModule InputModule { get; private set; }
|
||||||
|
|
||||||
|
//internal static Material UIMaterial { get; private set; }
|
||||||
|
internal static Sprite ResizeCursor { get; private set; }
|
||||||
|
internal static Font ConsoleFont { get; private set; }
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
var bundlePath = ExplorerCore.EXPLORER_FOLDER + @"\explorerui.bundle";
|
||||||
|
if (File.Exists(bundlePath))
|
||||||
|
{
|
||||||
|
var bundle = AssetBundle.LoadFromFile(bundlePath);
|
||||||
|
|
||||||
|
// Fix for games which don't ship with 'UI/Default' shader.
|
||||||
|
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
|
||||||
|
{
|
||||||
|
ExplorerCore.Log("This game does not ship with the 'UI/Default' shader, using manual Default Shader...");
|
||||||
|
Graphic.defaultGraphicMaterial.shader = bundle.LoadAsset<Shader>("DefaultUI");
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeCursor = bundle.LoadAsset<Sprite>("cursor");
|
||||||
|
|
||||||
|
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
|
||||||
|
|
||||||
|
ExplorerCore.Log("Loaded UI bundle");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExplorerCore.LogWarning("Could not find the ExplorerUI Bundle! It should exist at '" + bundlePath + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create core UI Canvas and Event System handler
|
||||||
|
CreateRootCanvas();
|
||||||
|
|
||||||
|
// Create submodules
|
||||||
|
new MainMenu();
|
||||||
|
MouseInspector.ConstructUI();
|
||||||
|
PanelDragger.LoadCursorImage();
|
||||||
|
|
||||||
|
// Force refresh of anchors
|
||||||
|
Canvas.ForceUpdateCanvases();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetEventSystem()
|
||||||
|
{
|
||||||
|
EventSystem.current = EventSys;
|
||||||
|
InputModule.ActivateModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnSceneChange()
|
||||||
|
{
|
||||||
|
SceneExplorer.Instance?.OnSceneChange();
|
||||||
|
SearchPage.Instance?.OnSceneChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Update()
|
||||||
|
{
|
||||||
|
MainMenu.Instance?.Update();
|
||||||
|
|
||||||
|
if (EventSys && InputModule)
|
||||||
|
{
|
||||||
|
if (EventSystem.current != EventSys)
|
||||||
|
{
|
||||||
|
ForceUnlockCursor.SetEventSystem();
|
||||||
|
//ForceUnlockCursor.Unlock = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix for games which override the InputModule pointer events (eg, VRChat)
|
||||||
|
#if CPP
|
||||||
|
if (InputModule.m_InputPointerEvent != null)
|
||||||
|
{
|
||||||
|
PointerEventData evt = InputModule.m_InputPointerEvent;
|
||||||
|
if (!evt.eligibleForClick && evt.selectedObject)
|
||||||
|
{
|
||||||
|
evt.eligibleForClick = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PanelDragger.Instance != null)
|
||||||
|
{
|
||||||
|
PanelDragger.Instance.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < SliderScrollbar.Instances.Count; i++)
|
||||||
|
{
|
||||||
|
var slider = SliderScrollbar.Instances[i];
|
||||||
|
|
||||||
|
if (slider.CheckDestroyed())
|
||||||
|
i--;
|
||||||
|
else
|
||||||
|
slider.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < InputFieldScroller.Instances.Count; i++)
|
||||||
|
{
|
||||||
|
var input = InputFieldScroller.Instances[i];
|
||||||
|
|
||||||
|
if (input.sliderScroller.CheckDestroyed())
|
||||||
|
i--;
|
||||||
|
else
|
||||||
|
input.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject CreateRootCanvas()
|
||||||
|
{
|
||||||
|
GameObject rootObj = new GameObject("ExplorerCanvas");
|
||||||
|
UnityEngine.Object.DontDestroyOnLoad(rootObj);
|
||||||
|
rootObj.layer = 5;
|
||||||
|
|
||||||
|
CanvasRoot = rootObj;
|
||||||
|
CanvasRoot.transform.position = new Vector3(0f, 0f, 1f);
|
||||||
|
|
||||||
|
EventSys = rootObj.AddComponent<EventSystem>();
|
||||||
|
InputModule = rootObj.AddComponent<StandaloneInputModule>();
|
||||||
|
InputModule.ActivateModule();
|
||||||
|
|
||||||
|
Canvas canvas = rootObj.AddComponent<Canvas>();
|
||||||
|
canvas.renderMode = RenderMode.ScreenSpaceCamera;
|
||||||
|
canvas.referencePixelsPerUnit = 100;
|
||||||
|
canvas.sortingOrder = 999;
|
||||||
|
canvas.pixelPerfect = false;
|
||||||
|
|
||||||
|
CanvasScaler scaler = rootObj.AddComponent<CanvasScaler>();
|
||||||
|
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||||
|
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
|
||||||
|
|
||||||
|
rootObj.AddComponent<GraphicRaycaster>();
|
||||||
|
|
||||||
|
return rootObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Sprite CreateSprite(Texture2D tex, Rect size = default)
|
||||||
|
{
|
||||||
|
#if CPP
|
||||||
|
Vector2 pivot = Vector2.zero;
|
||||||
|
Vector4 border = Vector4.zero;
|
||||||
|
|
||||||
|
if (size == default)
|
||||||
|
{
|
||||||
|
size = new Rect(0, 0, tex.width, tex.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sprite.CreateSprite_Injected(tex, ref size, ref pivot, 100f, 0u, SpriteMeshType.Tight, ref border, false);
|
||||||
|
#else
|
||||||
|
return Sprite.Create(tex, size, Vector2.zero);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Texture2D MakeSolidTexture(Color color, int width, int height)
|
||||||
|
{
|
||||||
|
Color[] pixels = new Color[width * height];
|
||||||
|
for (int i = 0; i < pixels.Length; i++)
|
||||||
|
{
|
||||||
|
pixels[i] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture2D tex = new Texture2D(width, height);
|
||||||
|
tex.SetPixels(pixels);
|
||||||
|
tex.Apply();
|
||||||
|
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
src/UI/UISyntaxHighlight.cs
Normal file
184
src/UI/UISyntaxHighlight.cs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityExplorer.Unstrip;
|
||||||
|
|
||||||
|
namespace UnityExplorer.UI
|
||||||
|
{
|
||||||
|
public class UISyntaxHighlight
|
||||||
|
{
|
||||||
|
public const string Field_Static = "#8d8dc6";
|
||||||
|
public const string Field_Instance = "#c266ff";
|
||||||
|
|
||||||
|
public const string Method_Static = "#b55b02";
|
||||||
|
public const string Method_Instance = "#ff8000";
|
||||||
|
|
||||||
|
public const string Prop_Static = "#588075";
|
||||||
|
public const string Prop_Instance = "#55a38e";
|
||||||
|
|
||||||
|
public const string Class_Static = "#3a8d71";
|
||||||
|
public const string Class_Instance = "#2df7b2";
|
||||||
|
|
||||||
|
public const string Local = "#a6e9e9";
|
||||||
|
|
||||||
|
public const string StructGreen = "#0fba3a";
|
||||||
|
|
||||||
|
public static string Enum = "#92c470";
|
||||||
|
|
||||||
|
internal static readonly Color s_silver = new Color(0.66f, 0.66f, 0.66f);
|
||||||
|
|
||||||
|
internal static string GetClassColor(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsAbstract && type.IsSealed)
|
||||||
|
return Class_Static;
|
||||||
|
else if (type.IsEnum || type.IsGenericParameter)
|
||||||
|
return Enum;
|
||||||
|
else if (type.IsValueType)
|
||||||
|
return StructGreen;
|
||||||
|
else
|
||||||
|
return Class_Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ParseFullSyntax(Type type, bool includeNamespace, MemberInfo memberInfo = null)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
throw new ArgumentNullException("type");
|
||||||
|
|
||||||
|
string ret = "";
|
||||||
|
|
||||||
|
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
|
||||||
|
{
|
||||||
|
ret = $"<color={Enum}>{type.Name}</color>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (includeNamespace && !string.IsNullOrEmpty(type.Namespace))
|
||||||
|
ret += $"<color=#{s_silver.ToHex()}>{type.Namespace}</color>.";
|
||||||
|
|
||||||
|
var declaring = type.DeclaringType;
|
||||||
|
while (declaring != null)
|
||||||
|
{
|
||||||
|
ret += HighlightTypeName(declaring) + ".";
|
||||||
|
declaring = declaring.DeclaringType;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += HighlightTypeName(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberInfo != null)
|
||||||
|
{
|
||||||
|
ret += ".";
|
||||||
|
|
||||||
|
string memberColor = GetMemberInfoColor(memberInfo, out bool isStatic);
|
||||||
|
string memberHighlight = $"<color={memberColor}>{memberInfo.Name}</color>";
|
||||||
|
|
||||||
|
if (isStatic)
|
||||||
|
memberHighlight = $"<i>{memberHighlight}</i>";
|
||||||
|
|
||||||
|
ret += memberHighlight;
|
||||||
|
|
||||||
|
// generic method args
|
||||||
|
if (memberInfo is MethodInfo method)
|
||||||
|
{
|
||||||
|
var gArgs = method.GetGenericArguments();
|
||||||
|
ret += ParseGenericArgs(gArgs, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HighlightTypeName(Type type)
|
||||||
|
{
|
||||||
|
var typeName = type.Name;
|
||||||
|
|
||||||
|
var gArgs = type.GetGenericArguments();
|
||||||
|
|
||||||
|
if (gArgs.Length > 0)
|
||||||
|
{
|
||||||
|
// remove the `N from the end of the type name
|
||||||
|
// this could actually be >9 in some cases, so get the length of the length string and use that.
|
||||||
|
// eg, if it was "List`15", we would remove the ending 3 chars
|
||||||
|
|
||||||
|
int suffixLen = 1 + gArgs.Length.ToString().Length;
|
||||||
|
|
||||||
|
// make sure the typename actually has expected "`N" format.
|
||||||
|
if (typeName[typeName.Length - suffixLen] == '`')
|
||||||
|
typeName = typeName.Substring(0, typeName.Length - suffixLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight the base name itself
|
||||||
|
// do this after removing the `N suffix, so only the name itself is in the color tags.
|
||||||
|
typeName = $"<color={GetClassColor(type)}>{typeName}</color>";
|
||||||
|
|
||||||
|
// parse the generic args, if any
|
||||||
|
if (gArgs.Length > 0)
|
||||||
|
typeName += ParseGenericArgs(gArgs);
|
||||||
|
|
||||||
|
return typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseGenericArgs(Type[] gArgs, bool allGeneric = false)
|
||||||
|
{
|
||||||
|
if (gArgs.Length < 1)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var args = "<";
|
||||||
|
for (int i = 0; i < gArgs.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
args += ", ";
|
||||||
|
|
||||||
|
var arg = gArgs[i];
|
||||||
|
|
||||||
|
if (allGeneric)
|
||||||
|
{
|
||||||
|
args += $"<color={Enum}>{arg.Name}</color>";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// using HighlightTypeName makes it recursive, so we can parse nested generic args.
|
||||||
|
args += HighlightTypeName(arg);
|
||||||
|
}
|
||||||
|
return args + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic)
|
||||||
|
{
|
||||||
|
string memberColor = "";
|
||||||
|
isStatic = false;
|
||||||
|
if (memberInfo is FieldInfo fi)
|
||||||
|
{
|
||||||
|
if (fi.IsStatic)
|
||||||
|
{
|
||||||
|
isStatic = true;
|
||||||
|
memberColor = Field_Static;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
memberColor = Field_Instance;
|
||||||
|
}
|
||||||
|
else if (memberInfo is MethodInfo mi)
|
||||||
|
{
|
||||||
|
if (mi.IsStatic)
|
||||||
|
{
|
||||||
|
isStatic = true;
|
||||||
|
memberColor = Method_Static;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
memberColor = Method_Instance;
|
||||||
|
}
|
||||||
|
else if (memberInfo is PropertyInfo pi)
|
||||||
|
{
|
||||||
|
if (pi.GetAccessors(true)[0].IsStatic)
|
||||||
|
{
|
||||||
|
isStatic = true;
|
||||||
|
memberColor = Prop_Static;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
memberColor = Prop_Instance;
|
||||||
|
}
|
||||||
|
return memberColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
415
src/UIStyles.cs
415
src/UIStyles.cs
@ -1,415 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using Il2CppSystem.Collections;
|
|
||||||
using Il2CppSystem.Reflection;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
using UnityEngine;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class UIStyles
|
|
||||||
{
|
|
||||||
public static GUISkin WindowSkin
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_customSkin == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_customSkin = CreateWindowSkin();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_customSkin = GUI.skin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _customSkin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HorizontalLine(Color color)
|
|
||||||
{
|
|
||||||
var c = GUI.color;
|
|
||||||
GUI.color = color;
|
|
||||||
GUILayout.Box(GUIContent.none, HorizontalBar, null);
|
|
||||||
GUI.color = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GUISkin _customSkin;
|
|
||||||
|
|
||||||
public static Texture2D m_nofocusTex;
|
|
||||||
public static Texture2D m_focusTex;
|
|
||||||
|
|
||||||
private static GUIStyle _horizBarStyle;
|
|
||||||
|
|
||||||
private static GUIStyle HorizontalBar
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_horizBarStyle == null)
|
|
||||||
{
|
|
||||||
_horizBarStyle = new GUIStyle();
|
|
||||||
_horizBarStyle.normal.background = Texture2D.whiteTexture;
|
|
||||||
_horizBarStyle.margin = new RectOffset(0, 0, 4, 4);
|
|
||||||
_horizBarStyle.fixedHeight = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _horizBarStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GUISkin CreateWindowSkin()
|
|
||||||
{
|
|
||||||
var newSkin = Object.Instantiate(GUI.skin);
|
|
||||||
Object.DontDestroyOnLoad(newSkin);
|
|
||||||
|
|
||||||
m_nofocusTex = MakeTex(550, 700, new Color(0.1f, 0.1f, 0.1f, 0.7f));
|
|
||||||
m_focusTex = MakeTex(550, 700, new Color(0.3f, 0.3f, 0.3f, 1f));
|
|
||||||
|
|
||||||
newSkin.window.normal.background = m_nofocusTex;
|
|
||||||
newSkin.window.onNormal.background = m_focusTex;
|
|
||||||
|
|
||||||
newSkin.box.normal.textColor = Color.white;
|
|
||||||
newSkin.window.normal.textColor = Color.white;
|
|
||||||
newSkin.button.normal.textColor = Color.white;
|
|
||||||
newSkin.textField.normal.textColor = Color.white;
|
|
||||||
newSkin.label.normal.textColor = Color.white;
|
|
||||||
|
|
||||||
return newSkin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Texture2D MakeTex(int width, int height, Color col)
|
|
||||||
{
|
|
||||||
Color[] pix = new Color[width * height];
|
|
||||||
for (int i = 0; i < pix.Length; ++i)
|
|
||||||
{
|
|
||||||
pix[i] = col;
|
|
||||||
}
|
|
||||||
Texture2D result = new Texture2D(width, height);
|
|
||||||
result.SetPixels(pix);
|
|
||||||
result.Apply();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// *********************************** METHODS FOR DRAWING VALUES IN GUI ************************************
|
|
||||||
|
|
||||||
// helper for "Instantiate" button on UnityEngine.Objects
|
|
||||||
public static void InstantiateButton(Object obj, float width = 100)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("Instantiate", new GUILayoutOption[] { GUILayout.Width(width) }))
|
|
||||||
{
|
|
||||||
var newobj = Object.Instantiate(obj);
|
|
||||||
|
|
||||||
WindowManager.InspectObject(newobj, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper for drawing a styled button for a GameObject or Transform
|
|
||||||
public static void GameobjButton(GameObject obj, Action<GameObject> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i><color=red>null</color></i>", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool enabled = obj.activeSelf;
|
|
||||||
bool children = obj.transform.childCount > 0;
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUI.skin.button.alignment = TextAnchor.UpperLeft;
|
|
||||||
|
|
||||||
// ------ build name ------
|
|
||||||
|
|
||||||
string label = children ? "[" + obj.transform.childCount + " children] " : "";
|
|
||||||
label += obj.name;
|
|
||||||
|
|
||||||
// ------ Color -------
|
|
||||||
|
|
||||||
if (enabled)
|
|
||||||
{
|
|
||||||
if (children)
|
|
||||||
{
|
|
||||||
GUI.color = Color.green;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.color = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.color = Color.red;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ toggle active button ------
|
|
||||||
|
|
||||||
enabled = GUILayout.Toggle(enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
|
||||||
if (obj.activeSelf != enabled)
|
|
||||||
{
|
|
||||||
obj.SetActive(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------- actual button ---------
|
|
||||||
|
|
||||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Height(22), GUILayout.Width(width) }))
|
|
||||||
{
|
|
||||||
if (specialInspectMethod != null)
|
|
||||||
{
|
|
||||||
specialInspectMethod(obj);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(obj, out bool _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ small "Inspect" button on the right ------
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
GUI.color = Color.white;
|
|
||||||
|
|
||||||
if (showSmallInspectBtn)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("Inspect", null))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(obj, out bool _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DrawMember(ref object value, string valueType, string memberName, Rect rect, object setTarget = null, Action<object> setAction = null, float labelWidth = 180, bool autoSet = false)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<color=cyan>" + memberName + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
|
||||||
|
|
||||||
DrawValue(ref value, rect, valueType, memberName, setTarget, setAction, autoSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DrawValue(ref object value, Rect rect, string nullValueType = null, string memberName = null, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i>null (" + nullValueType + ")</i>", null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var valueType = value.GetType();
|
|
||||||
if (valueType.IsPrimitive || value.GetType() == typeof(string))
|
|
||||||
{
|
|
||||||
DrawPrimitive(ref value, rect, setTarget, setAction);
|
|
||||||
}
|
|
||||||
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
|
|
||||||
{
|
|
||||||
GameObject go;
|
|
||||||
if (value.GetType() == typeof(Transform))
|
|
||||||
{
|
|
||||||
go = (value as Transform).gameObject;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
go = (value as GameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
UIStyles.GameobjButton(go, null, false, rect.width - 250);
|
|
||||||
}
|
|
||||||
else if (valueType.IsEnum)
|
|
||||||
{
|
|
||||||
if (setAction != null)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
|
||||||
{
|
|
||||||
SetEnum(ref value, -1);
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
|
|
||||||
{
|
|
||||||
SetEnum(ref value, 1);
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label(value.ToString(), null);
|
|
||||||
}
|
|
||||||
else if (value is System.Collections.IEnumerable || ReflectionWindow.IsList(valueType))
|
|
||||||
{
|
|
||||||
System.Collections.IEnumerable enumerable;
|
|
||||||
|
|
||||||
if (value is System.Collections.IEnumerable isEnumerable)
|
|
||||||
{
|
|
||||||
enumerable = isEnumerable;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var listValueType = value.GetType().GetGenericArguments()[0];
|
|
||||||
var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType });
|
|
||||||
var method = listType.GetMethod("ToArray");
|
|
||||||
enumerable = (System.Collections.IEnumerable)method.Invoke(value, new object[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = enumerable.Cast<object>().Count();
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
string btnLabel = "<color=yellow>[" + count + "] " + valueType + "</color>";
|
|
||||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(value, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
|
|
||||||
var enumerator = enumerable.GetEnumerator();
|
|
||||||
if (enumerator != null)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
while (enumerator.MoveNext())
|
|
||||||
{
|
|
||||||
var obj = enumerator.Current;
|
|
||||||
|
|
||||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Space(190);
|
|
||||||
|
|
||||||
if (i > CppExplorer.ArrayLimit)
|
|
||||||
{
|
|
||||||
GUILayout.Label($"<i><color=red>{count - CppExplorer.ArrayLimit} results omitted, array is too long!</color></i>", null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i><color=grey>null</color></i>", null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var type = obj.GetType();
|
|
||||||
var lbl = i + ": <color=cyan>" + obj.ToString() + "</color>";
|
|
||||||
|
|
||||||
if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
GUILayout.Label(lbl, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (GUILayout.Button(lbl, null))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(obj, out _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
}
|
|
||||||
//var type = obj.GetType();
|
|
||||||
//DrawMember(ref obj, type.ToString(), i.ToString(), rect, setTarget, setAction, 25, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var label = value.ToString();
|
|
||||||
|
|
||||||
if (valueType == typeof(Object))
|
|
||||||
{
|
|
||||||
label = (value as Object).name;
|
|
||||||
}
|
|
||||||
else if (value is Vector4 vec4)
|
|
||||||
{
|
|
||||||
label = vec4.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Vector3 vec3)
|
|
||||||
{
|
|
||||||
label = vec3.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Vector2 vec2)
|
|
||||||
{
|
|
||||||
label = vec2.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Rect rec)
|
|
||||||
{
|
|
||||||
label = rec.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Matrix4x4 matrix)
|
|
||||||
{
|
|
||||||
label = matrix.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Color col)
|
|
||||||
{
|
|
||||||
label = col.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(value, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for drawing primitive values (with Apply button)
|
|
||||||
|
|
||||||
public static void DrawPrimitive(ref object value, Rect m_rect, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
|
||||||
{
|
|
||||||
bool allowSet = setAction != null;
|
|
||||||
|
|
||||||
if (value.GetType() == typeof(bool))
|
|
||||||
{
|
|
||||||
bool b = (bool)value;
|
|
||||||
var color = "<color=" + (b ? "lime>" : "red>");
|
|
||||||
|
|
||||||
if (allowSet)
|
|
||||||
{
|
|
||||||
value = GUILayout.Toggle((bool)value, color + value.ToString() + "</color>", null);
|
|
||||||
|
|
||||||
if (b != (bool)value)
|
|
||||||
{
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (value.ToString().Length > 37)
|
|
||||||
{
|
|
||||||
value = GUILayout.TextArea(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = GUILayout.TextField(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoSet || (allowSet && GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) })))
|
|
||||||
{
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for setting an enum
|
|
||||||
|
|
||||||
public static void SetEnum(ref object value, int change)
|
|
||||||
{
|
|
||||||
var type = value.GetType();
|
|
||||||
var names = Enum.GetNames(type).ToList();
|
|
||||||
|
|
||||||
int newindex = names.IndexOf(value.ToString()) + change;
|
|
||||||
|
|
||||||
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
|
|
||||||
{
|
|
||||||
value = Enum.Parse(type, names[newindex]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
314
src/UnityExplorer.csproj
Normal file
314
src/UnityExplorer.csproj
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Release_ML_Cpp</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<!--<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>-->
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
<OutputPath>..\Release\Explorer.MelonLoader.Il2Cpp\</OutputPath>
|
||||||
|
<DefineConstants>
|
||||||
|
</DefineConstants>
|
||||||
|
<IsCpp>false</IsCpp>
|
||||||
|
<IsMelonLoader>false</IsMelonLoader>
|
||||||
|
<DebugSymbols>false</DebugSymbols>
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
<RootNamespace>UnityExplorer</RootNamespace>
|
||||||
|
<!-- Set this to the BepInEx Il2Cpp Game folder, without the ending '\' character. -->
|
||||||
|
<BIECppGameFolder>D:\Steam\steamapps\common\Outward</BIECppGameFolder>
|
||||||
|
<!-- Set this to the BepInEx Mono Game folder, without the ending '\' character. -->
|
||||||
|
<BIEMonoGameFolder>D:\source\Unity Projects\Test\_BUILD_MONO</BIEMonoGameFolder>
|
||||||
|
<!-- Set this to the BepInEx Mono Managed folder, without the ending '\' character. -->
|
||||||
|
<BIEMonoManagedFolder>D:\source\Unity Projects\Test\_BUILD_MONO\Test_Data\Managed</BIEMonoManagedFolder>
|
||||||
|
<!-- Set this to the MelonLoader Il2Cpp Game folder, without the ending '\' character. -->
|
||||||
|
<MLCppGameFolder>D:\Steam\steamapps\common\VRChat</MLCppGameFolder>
|
||||||
|
<!-- Set this to the MelonLoader Mono Game folder, without the ending '\' character. -->
|
||||||
|
<MLMonoGameFolder>D:\source\Unity Projects\Test\_BUILD_MONO</MLMonoGameFolder>
|
||||||
|
<!-- Set this to the MelonLoader Mono Managed folder, without the ending '\' character. -->
|
||||||
|
<MLMonoManagedFolder>D:\source\Unity Projects\Test\_BUILD_MONO\Test_Data\Managed</MLMonoManagedFolder>
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_ML_Cpp|AnyCPU' ">
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<OutputPath>..\Release\UnityExplorer.MelonLoader.Il2Cpp\</OutputPath>
|
||||||
|
<DefineConstants>CPP,ML</DefineConstants>
|
||||||
|
<AssemblyName>UnityExplorer.ML.IL2CPP</AssemblyName>
|
||||||
|
<IsCpp>true</IsCpp>
|
||||||
|
<IsMelonLoader>true</IsMelonLoader>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_ML_Mono|AnyCPU' ">
|
||||||
|
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||||
|
<OutputPath>..\Release\UnityExplorer.MelonLoader.Mono\</OutputPath>
|
||||||
|
<DefineConstants>MONO,ML</DefineConstants>
|
||||||
|
<AssemblyName>UnityExplorer.ML.Mono</AssemblyName>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
<IsCpp>false</IsCpp>
|
||||||
|
<IsMelonLoader>true</IsMelonLoader>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Cpp|AnyCPU' ">
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<OutputPath>..\Release\UnityExplorer.BepInEx.Il2Cpp\</OutputPath>
|
||||||
|
<DefineConstants>CPP,BIE</DefineConstants>
|
||||||
|
<AssemblyName>UnityExplorer.BIE.IL2CPP</AssemblyName>
|
||||||
|
<IsCpp>true</IsCpp>
|
||||||
|
<IsMelonLoader>false</IsMelonLoader>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Mono|AnyCPU' ">
|
||||||
|
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||||
|
<OutputPath>..\Release\UnityExplorer.BepInEx.Mono\</OutputPath>
|
||||||
|
<DefineConstants>MONO,BIE</DefineConstants>
|
||||||
|
<AssemblyName>UnityExplorer.BIE.Mono</AssemblyName>
|
||||||
|
<IsCpp>false</IsCpp>
|
||||||
|
<IsMelonLoader>false</IsMelonLoader>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<!-- MCS ref -->
|
||||||
|
<Reference Include="mcs">
|
||||||
|
<HintPath>..\lib\mcs.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- Universal Mono UnityEngine.dll ref (v5.3) -->
|
||||||
|
<ItemGroup Condition="'$(IsCpp)'=='false'">
|
||||||
|
<Reference Include="UnityEngine">
|
||||||
|
<HintPath>..\lib\UnityEngine.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UI">
|
||||||
|
<HintPath>..\lib\UnityEngine.UI.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- MelonLoader Mono refs -->
|
||||||
|
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|false'">
|
||||||
|
<Reference Include="MelonLoader.ModHandler">
|
||||||
|
<HintPath>$(MLMonoGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- BepInEx Mono refs -->
|
||||||
|
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='false|false'">
|
||||||
|
<Reference Include="BepInEx">
|
||||||
|
<HintPath>..\lib\BepInEx.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>..\lib\0Harmony.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- MelonLoader Il2Cpp refs -->
|
||||||
|
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|true'">
|
||||||
|
<Reference Include="MelonLoader.ModHandler">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnhollowerBaseLib">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Il2Cppmscorlib">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Il2CppSystem.Core">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.CoreModule">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.PhysicsModule">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.TextRenderingModule">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UI">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UIModule">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.IMGUIModule">
|
||||||
|
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- BepInEx Il2Cpp refs -->
|
||||||
|
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='false|true'">
|
||||||
|
<Reference Include="BepInEx">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\core\BepInEx.Core.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\core\0Harmony.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="BepInEx.IL2CPP">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\core\BepInEx.IL2CPP.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnhollowerBaseLib">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\core\UnhollowerBaseLib.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Il2Cppmscorlib">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\Il2Cppmscorlib.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Il2CppSystem.Core">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\Il2CppSystem.Core.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.CoreModule">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.CoreModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.PhysicsModule">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.TextRenderingModule">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UI">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.UI.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UIModule">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.UIModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.IMGUIModule">
|
||||||
|
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Helpers\EventHelper.cs" />
|
||||||
|
<Compile Include="Inspectors\MouseInspector.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CacheEnumerated.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CacheField.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CachePaired.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CacheMember.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CacheMethod.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CacheProperty.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\CacheObject\CacheObjectBase.cs" />
|
||||||
|
<Compile Include="Helpers\Texture2DHelpers.cs" />
|
||||||
|
<Compile Include="Config\ModConfig.cs" />
|
||||||
|
<Compile Include="ExplorerCore.cs" />
|
||||||
|
<Compile Include="ExplorerBepInPlugin.cs" />
|
||||||
|
<Compile Include="ExplorerMelonMod.cs" />
|
||||||
|
<Compile Include="Helpers\ReflectionHelpers.cs" />
|
||||||
|
<Compile Include="Helpers\UnityHelpers.cs" />
|
||||||
|
<Compile Include="Inspectors\GameObjects\ChildList.cs" />
|
||||||
|
<Compile Include="Inspectors\GameObjects\ComponentList.cs" />
|
||||||
|
<Compile Include="Inspectors\GameObjects\GameObjectControls.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveBool.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveDictionary.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveEnum.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveEnumerable.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveFlags.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveNumber.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveString.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveUnityStruct.cs" />
|
||||||
|
<Compile Include="UI\ForceUnlockCursor.cs" />
|
||||||
|
<Compile Include="Input\IHandleInput.cs" />
|
||||||
|
<Compile Include="Tests\Tests.cs" />
|
||||||
|
<Compile Include="Input\InputManager.cs" />
|
||||||
|
<Compile Include="Input\InputSystem.cs" />
|
||||||
|
<Compile Include="Input\LegacyInput.cs" />
|
||||||
|
<Compile Include="Input\NoInput.cs" />
|
||||||
|
<Compile Include="UI\Modules\DebugConsole.cs" />
|
||||||
|
<Compile Include="Inspectors\InspectorManager.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\ReflectionInspector.cs" />
|
||||||
|
<Compile Include="UI\MainMenu.cs" />
|
||||||
|
<Compile Include="UI\Modules\CSConsolePage.cs" />
|
||||||
|
<Compile Include="CSConsole\AutoCompleter.cs" />
|
||||||
|
<Compile Include="CSConsole\CodeEditor.cs" />
|
||||||
|
<Compile Include="CSConsole\Lexer\CommentMatch.cs" />
|
||||||
|
<Compile Include="CSConsole\CSharpLexer.cs" />
|
||||||
|
<Compile Include="CSConsole\Lexer\KeywordMatch.cs" />
|
||||||
|
<Compile Include="CSConsole\Lexer\StringMatch.cs" />
|
||||||
|
<Compile Include="CSConsole\Lexer\Matcher.cs" />
|
||||||
|
<Compile Include="CSConsole\Lexer\NumberMatch.cs" />
|
||||||
|
<Compile Include="CSConsole\Lexer\SymbolMatch.cs" />
|
||||||
|
<Compile Include="CSConsole\Suggestion.cs" />
|
||||||
|
<Compile Include="CSConsole\ScriptEvaluator.cs" />
|
||||||
|
<Compile Include="CSConsole\ScriptInteraction.cs" />
|
||||||
|
<Compile Include="UI\Modules\HomePage.cs" />
|
||||||
|
<Compile Include="Inspectors\GameObjects\GameObjectInspector.cs" />
|
||||||
|
<Compile Include="Inspectors\InspectorBase.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InstanceInspector.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\StaticInspector.cs" />
|
||||||
|
<Compile Include="UI\Modules\OptionsPage.cs" />
|
||||||
|
<Compile Include="Inspectors\SceneExplorer.cs" />
|
||||||
|
<Compile Include="UI\Modules\SearchPage.cs" />
|
||||||
|
<Compile Include="UI\PanelDragger.cs" />
|
||||||
|
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveValue.cs" />
|
||||||
|
<Compile Include="UI\Shared\InputFieldScroller.cs" />
|
||||||
|
<Compile Include="UI\Shared\ScrollRectEx.cs" />
|
||||||
|
<Compile Include="UI\Shared\SliderScrollbar.cs" />
|
||||||
|
<Compile Include="UI\Shared\PageHandler.cs" />
|
||||||
|
<Compile Include="UI\UISyntaxHighlight.cs" />
|
||||||
|
<Compile Include="UI\UIManager.cs" />
|
||||||
|
<Compile Include="Unstrip\AssetBundleUnstrip.cs" />
|
||||||
|
<Compile Include="Unstrip\ColorUtilityUnstrip.cs" />
|
||||||
|
<Compile Include="Unstrip\ImageConversionUnstrip.cs" />
|
||||||
|
<Compile Include="Helpers\ICallHelper.cs" />
|
||||||
|
<Compile Include="Unstrip\LayerMaskUnstrip.cs" />
|
||||||
|
<Compile Include="Unstrip\ResourcesUnstrip.cs" />
|
||||||
|
<Compile Include="Unstrip\SceneUnstrip.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="UI\UIFactory.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="ILRepack.targets" />
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Import Project="packages\ILRepack.Lib.MSBuild.Task.2.0.18.1\build\ILRepack.Lib.MSBuild.Task.targets" Condition="Exists('packages\ILRepack.Lib.MSBuild.Task.2.0.18.1\build\ILRepack.Lib.MSBuild.Task.targets')" />
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('packages\ILRepack.Lib.MSBuild.Task.2.0.18.1\build\ILRepack.Lib.MSBuild.Task.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\ILRepack.Lib.MSBuild.Task.2.0.18.1\build\ILRepack.Lib.MSBuild.Task.targets'))" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user