Initial commit

This commit is contained in:
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
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.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
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>.

View File

@@ -0,0 +1,112 @@
# Mantle
🎈 Универсальная библиотека GLua для Garry's Mod: создание интерфейсов и удобные утилиты.
Весь код снабжён комментариями — изучайте и находите примеры прямо в исходниках.
## Возможности
- Кастомные VGUI-элементы
- Быстрый рендеринг через RNDX
- Загрузка материалов по ссылке
- Гибкая система цветовых тем
- Уведомления для игроков на сервера
- Модульная архитектура
- Поддержка кириллицы и UTF-8
- Единое меню с документацией и настройками
## Меню библиотеки
Имеется меню с документацией и настройками. Для открытия используйте консольную команду: `mantle_menu`.
## Примеры компонентов
### Документация и элементы VGUI
<img width="983" height="720" alt="image" src="https://github.com/user-attachments/assets/892290e4-eb7f-4f65-b306-4ed5cc0c0e5b" />
### Лёгкий режим окна
<img width="406" height="314" alt="image" src="https://github.com/user-attachments/assets/31669a2a-f2d3-4e2d-9e82-63a2188fe96e" />
### ComboBox
<img width="1002" height="728" alt="image" src="https://github.com/user-attachments/assets/8abb781b-b055-4178-9ee1-20046fbcc409" />
### SlideBox
<img width="1021" height="750" alt="image" src="https://github.com/user-attachments/assets/1afae892-4d6c-492b-8ded-38915282c8ee" />
### Таблицы
<img width="994" height="729" alt="image" src="https://github.com/user-attachments/assets/bd87ca15-5c25-41a4-9786-034faf71adc9" />
### Поле ввода
<img width="994" height="722" alt="image" src="https://github.com/user-attachments/assets/864f6206-f5b8-4072-bd8f-808d4f6f69df" />
### Всплывающие элементы
<img width="989" height="720" alt="image" src="https://github.com/user-attachments/assets/a050be2a-727d-450c-84f3-2a04b15a0ceb" />
### Круговое меню
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/268fee97-d54a-4143-8c43-3b9a4e8e272f" />
### Опциональное меню
<img width="352" height="323" alt="image" src="https://github.com/user-attachments/assets/2bd0f764-8fcd-4f9e-ba7e-1165c6afb10e" />
### Цветовые темы
<img width="996" height="775" alt="image" src="https://github.com/user-attachments/assets/a0b7c168-b773-48f1-b516-f070b76d34f6" />
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/14eaa457-7475-4fe3-9d4e-177035a75fcc" />
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/b0049430-1c08-4534-8075-3bf03201fd85" />
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/5c1ff333-ea6c-408c-aec6-f5f75921cb5d" />
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/e464c564-b7d1-42f6-bdde-6d5130a31acf" />
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/658fb731-aebc-4ce9-8d4d-2e9575961030" />
### И главное - плавность и магия анимаций
https://github.com/user-attachments/assets/6a813fd1-6da2-4c59-a84b-f78abfc20900
## Сторонние примеры
### Отправка серверных уведомлений
```lua
hook.Add('PlayerSpawn', 'Test', function(pl)
Mantle.notify(pl, Color(75, 0, 0), 'Заголовок', 'Привет, ' .. pl:Name() .. '!')
-- первым аргументом true, в случае отправки всем игрокам
end)
```
### Картинка через ссылку
```lua
http.DownloadMaterial('https://i.imgur.com/eEnGbcp.jpeg', 'dog.png', function(your_mat)
hook.Add('HUDPaint', 'Test', function()
surface.SetDrawColor(255, 255, 255)
surface.SetMaterial(your_mat)
surface.DrawTexturedRect(5, 5, 250, 330)
end)
end)
```
### Преобразование символов кириллицы
```lua
hook.Add('HUDPaint', 'test', function()
local txt = 'ПриВЕТ МИР Hello World'
-- default
draw.SimpleText(string.lower(txt), 'Fated.20', 15, 15, color_black)
-- mantle
draw.SimpleText(utf8.lower(txt), 'Fated.20', 15, 35, color_black)
end)
```
<img width="247" height="75" alt="Сравнение default и mantle функции" src="https://github.com/user-attachments/assets/77e0b791-e970-45da-90b3-1b4960b945fd" />
## Steam Workshop
Для автообновления [подпишитесь и добавьте аддон в серверную коллекцию](https://steamcommunity.com/sharedfiles/filedetails/?id=3126986993). Таким образом сможете всегда получать актуальную версию библиотеки ✅

View File

@@ -0,0 +1,4 @@
RNDX = include('mantle/modules/rndx.lua')
AddCSLuaFile('mantle/init.lua')
include('mantle/init.lua')

View File

@@ -0,0 +1,511 @@
Mantle.color_dark = {
header = Color(40, 40, 40), -- верхняя панель
header_text = Color(100, 100, 100), -- цвет элементов в заголовке
background = Color(25, 25, 25), -- фон
background_alpha = Color(25, 25, 25, 210), -- фон с прозрачностью
background_panelpopup = Color(20, 20, 20, 150), -- фон для DermaMenu
button = Color(54, 54, 54), -- кнопка
button_shadow = Color(0, 0, 0, 25), -- тень кнопки для градиента
button_hovered = Color(60, 60, 62), -- кнопка при наведении
category = Color(50, 50, 50), -- категория
category_opened = Color(50, 50, 50, 0), -- категория открыта
theme = Color(106, 108, 197), -- тема интерфейса
panel = { -- варианты цветов для панели
Color(60, 60, 60),
Color(50, 50, 50),
Color(80, 80, 80)
},
toggle = Color(56, 56, 56), -- тумблер
focus_panel = Color(46, 46, 46), -- универсальный цвет для элементов
hover = Color(60, 65, 80), -- универсальное выделение
window_shadow = Color(0, 0, 0, 100), -- тень окна
gray = Color(150, 150, 150, 220),
text = Color(255, 255, 255)
}
Mantle.color_dark.panel_alpha = { -- прозрачные панели
ColorAlpha(Mantle.color_dark.panel[1], 150),
ColorAlpha(Mantle.color_dark.panel[2], 150),
ColorAlpha(Mantle.color_dark.panel[3], 150)
}
-- Тёмная палитра (монотонная)
Mantle.color_dark_mono = table.Copy(Mantle.color_dark)
Mantle.color_dark_mono.theme = Color(121, 121, 121)
-- Светлая палитра
Mantle.color_light = {
header = Color(240, 240, 240),
header_text = Color(150, 150, 150),
background = Color(255, 255, 255),
background_alpha = Color(255, 255, 255, 170),
background_panelpopup = Color(245, 245, 245, 150),
button = Color(235, 235, 235),
button_shadow = Color(0, 0, 0, 15),
button_hovered = Color(196, 199, 218),
category = Color(240, 240, 245),
category_opened = Color(240, 240, 245, 0),
theme = Color(106, 108, 197),
panel = {
Color(250, 250, 255),
Color(240, 240, 245),
Color(230, 230, 235)
},
toggle = Color(220, 220, 230),
focus_panel = Color(245, 245, 255),
hover = Color(235, 240, 255),
window_shadow = Color(0, 0, 0, 50),
gray = Color(130, 130, 130, 220),
text = Color(20, 20, 20)
}
Mantle.color_light.panel_alpha = {
ColorAlpha(Mantle.color_light.panel[1], 120),
ColorAlpha(Mantle.color_light.panel[2], 120),
ColorAlpha(Mantle.color_light.panel[3], 120)
}
-- Синяя палитра
Mantle.color_blue = {
header = Color(36, 48, 66),
header_text = Color(109, 129, 159),
background = Color(24, 28, 38),
background_alpha = Color(24, 28, 38, 210),
background_panelpopup = Color(20, 24, 32, 150),
button = Color(38, 54, 82),
button_shadow = Color(18, 22, 32, 35),
button_hovered = Color(47, 69, 110),
category = Color(34, 48, 72),
category_opened = Color(34, 48, 72, 0),
theme = Color(80, 160, 220),
panel = {
Color(34, 48, 72),
Color(38, 54, 82),
Color(70, 120, 180)
},
toggle = Color(34, 44, 66),
focus_panel = Color(48, 72, 90),
hover = Color(80, 160, 220, 90),
window_shadow = Color(18, 22, 32, 100),
gray = Color(150, 170, 190, 200),
text = Color(210, 220, 235)
}
Mantle.color_blue.panel_alpha = {
ColorAlpha(Mantle.color_blue.panel[1], 110),
ColorAlpha(Mantle.color_blue.panel[2], 110),
ColorAlpha(Mantle.color_blue.panel[3], 110)
}
-- Красная палитра
Mantle.color_red = {
header = Color(54, 36, 36),
header_text = Color(159, 109, 109),
background = Color(32, 24, 24),
background_alpha = Color(32, 24, 24, 210),
background_panelpopup = Color(28, 20, 20, 150),
button = Color(66, 38, 38),
button_shadow = Color(32, 18, 18, 35),
button_hovered = Color(97, 50, 50),
category = Color(62, 34, 34),
category_opened = Color(62, 34, 34, 0),
theme = Color(180, 80, 80),
panel = {
Color(62, 34, 34),
Color(66, 38, 38),
Color(140, 70, 70)
},
toggle = Color(60, 34, 34),
focus_panel = Color(72, 48, 48),
hover = Color(180, 80, 80, 90),
window_shadow = Color(32, 18, 18, 100),
gray = Color(180, 150, 150, 200),
text = Color(235, 210, 210)
}
Mantle.color_red.panel_alpha = {
ColorAlpha(Mantle.color_red.panel[1], 110),
ColorAlpha(Mantle.color_red.panel[2], 110),
ColorAlpha(Mantle.color_red.panel[3], 110)
}
-- Зелёная палитра
Mantle.color_green = {
header = Color(36, 54, 40),
header_text = Color(109, 159, 109),
background = Color(24, 32, 26),
background_alpha = Color(24, 32, 26, 210),
background_panelpopup = Color(20, 28, 22, 150),
button = Color(38, 66, 48),
button_shadow = Color(18, 32, 22, 35),
button_hovered = Color(48, 88, 62),
category = Color(34, 62, 44),
category_opened = Color(34, 62, 44, 0),
theme = Color(80, 180, 120),
panel = {
Color(34, 62, 44),
Color(38, 66, 48),
Color(70, 140, 90)
},
toggle = Color(34, 60, 44),
focus_panel = Color(48, 72, 58),
hover = Color(80, 180, 120, 90),
window_shadow = Color(18, 32, 22, 100),
gray = Color(150, 180, 150, 200),
text = Color(210, 235, 210)
}
Mantle.color_green.panel_alpha = {
ColorAlpha(Mantle.color_green.panel[1], 110),
ColorAlpha(Mantle.color_green.panel[2], 110),
ColorAlpha(Mantle.color_green.panel[3], 110)
}
-- Оранжевая палитра
Mantle.color_orange = {
header = Color(70, 35, 10),
header_text = Color(250, 230, 210),
background = Color(255, 250, 240),
background_alpha = Color(255, 250, 240, 220),
background_panelpopup = Color(255, 245, 235, 160),
button = Color(184, 122, 64),
button_shadow = Color(20, 10, 0, 30),
button_hovered = Color(197, 129, 65),
category = Color(255, 245, 235),
category_opened = Color(255, 245, 235, 0),
theme = Color(245, 130, 50),
panel = {
Color(255, 250, 240),
Color(250, 220, 180),
Color(235, 150, 90)
},
toggle = Color(143, 121, 104),
focus_panel = Color(255, 240, 225),
hover = Color(255, 165, 80, 90),
window_shadow = Color(20, 8, 0, 100),
gray = Color(180, 161, 150, 200),
text = Color(45, 20, 10)
}
Mantle.color_orange.panel_alpha = {
ColorAlpha(Mantle.color_orange.panel[1], 120),
ColorAlpha(Mantle.color_orange.panel[2], 120),
ColorAlpha(Mantle.color_orange.panel[3], 120)
}
-- Фиолетовая палитра
Mantle.color_purple = {
header = Color(40, 36, 56),
header_text = Color(150, 140, 180),
background = Color(25, 22, 30),
background_alpha = Color(25, 22, 30, 210),
background_panelpopup = Color(28, 24, 40, 150),
button = Color(58, 52, 76),
button_shadow = Color(8, 6, 20, 30),
button_hovered = Color(74, 64, 105),
category = Color(46, 40, 60),
category_opened = Color(46, 40, 60, 0),
theme = Color(138, 114, 219),
panel = {
Color(56, 48, 76),
Color(44, 36, 64),
Color(120, 90, 200)
},
toggle = Color(43, 39, 53),
focus_panel = Color(48, 42, 62),
hover = Color(138, 114, 219, 90),
window_shadow = Color(8, 6, 20, 100),
gray = Color(140, 128, 148, 220),
text = Color(245, 240, 255)
}
Mantle.color_purple.panel_alpha = {
ColorAlpha(Mantle.color_purple.panel[1], 150),
ColorAlpha(Mantle.color_purple.panel[2], 150),
ColorAlpha(Mantle.color_purple.panel[3], 150)
}
-- Кофейная палитра
Mantle.color_coffee = {
header = Color(67, 48, 36),
header_text = Color(210, 190, 170),
background = Color(45, 32, 25),
background_alpha = Color(45, 32, 25, 215),
background_panelpopup = Color(38, 28, 22, 150),
button = Color(84, 60, 45),
button_shadow = Color(20, 10, 5, 40),
button_hovered = Color(100, 75, 55),
category = Color(72, 54, 42),
category_opened = Color(72, 54, 42, 0),
theme = Color(150, 110, 75),
panel = {
Color(68, 50, 40),
Color(90, 65, 50),
Color(150, 110, 75)
},
toggle = Color(53, 40, 31),
focus_panel = Color(70, 55, 40),
hover = Color(150, 110, 75, 90),
window_shadow = Color(15, 10, 5, 100),
gray = Color(180, 150, 130, 200),
text = Color(235, 225, 210)
}
Mantle.color_coffee.panel_alpha = {
ColorAlpha(Mantle.color_coffee.panel[1], 110),
ColorAlpha(Mantle.color_coffee.panel[2], 110),
ColorAlpha(Mantle.color_coffee.panel[3], 110)
}
-- Ледяная палитра
Mantle.color_ice = {
header = Color(190, 225, 250),
header_text = Color(68, 104, 139),
background = Color(235, 245, 255),
background_alpha = Color(235, 245, 255, 200),
background_panelpopup = Color(220, 235, 245, 150),
button = Color(145, 185, 225),
button_shadow = Color(80, 110, 140, 40),
button_hovered = Color(170, 210, 255),
category = Color(200, 225, 245),
category_opened = Color(200, 225, 245, 0),
theme = Color(100, 170, 230),
panel = {
Color(146, 186, 211),
Color(107, 157, 190),
Color(74, 132, 184)
},
toggle = Color(168, 194, 219),
focus_panel = Color(205, 230, 245),
hover = Color(100, 170, 230, 80),
window_shadow = Color(60, 100, 140, 100),
gray = Color(92, 112, 133, 200),
text = Color(20, 35, 50)
}
Mantle.color_ice.panel_alpha = {
ColorAlpha(Mantle.color_ice.panel[1], 120),
ColorAlpha(Mantle.color_ice.panel[2], 120),
ColorAlpha(Mantle.color_ice.panel[3], 120)
}
-- Винная палитра
Mantle.color_wine = {
header = Color(59, 42, 53),
header_text = Color(246, 242, 246),
background = Color(31, 23, 22),
background_alpha = Color(31, 23, 22, 210),
background_panelpopup = Color(36, 28, 28, 150),
button = Color(79, 50, 60),
button_shadow = Color(10, 6, 18, 30),
button_hovered = Color(90, 52, 65),
category = Color(79, 50, 60),
category_opened = Color(79, 50, 60, 0),
theme = Color(148, 61, 91),
panel = {
Color(79, 50, 60),
Color(63, 44, 48),
Color(160, 85, 143)
},
toggle = Color(63, 40, 47),
focus_panel = Color(70, 48, 58),
hover = Color(192, 122, 217, 90),
window_shadow = Color(10, 6, 20, 100),
gray = Color(170, 150, 160, 200),
text = Color(246, 242, 246)
}
Mantle.color_wine.panel_alpha = {
ColorAlpha(Mantle.color_wine.panel[1], 150),
ColorAlpha(Mantle.color_wine.panel[2], 150),
ColorAlpha(Mantle.color_wine.panel[3], 150)
}
-- Фиалковая палитра
Mantle.color_violet = {
header = Color(49, 50, 68),
header_text = Color(238, 244, 255),
background = Color(22, 24, 35),
background_alpha = Color(22, 24, 35, 210),
background_panelpopup = Color(36, 40, 56, 150),
button = Color(58, 64, 84),
button_shadow = Color(8, 6, 18, 30),
button_hovered = Color(64, 74, 104),
category = Color(58, 64, 84),
category_opened = Color(58, 64, 84, 0),
theme = Color(159, 180, 255),
panel = {
Color(58, 64, 84),
Color(48, 52, 72),
Color(109, 136, 255)
},
toggle = Color(46, 51, 66),
focus_panel = Color(56, 62, 86),
hover = Color(159, 180, 255, 90),
window_shadow = Color(8, 6, 20, 100),
gray = Color(147, 147, 184, 200),
text = Color(238, 244, 255)
}
Mantle.color_violet.panel_alpha = {
ColorAlpha(Mantle.color_violet.panel[1], 150),
ColorAlpha(Mantle.color_violet.panel[2], 150),
ColorAlpha(Mantle.color_violet.panel[3], 150)
}
-- Моховая палитра
Mantle.color_moss = {
header = Color(42, 50, 36),
header_text = Color(232, 244, 235),
background = Color(14, 16, 12),
background_alpha = Color(14, 16, 12, 210),
background_panelpopup = Color(24, 28, 22, 150),
button = Color(64, 82, 60),
button_shadow = Color(6, 8, 6, 30),
button_hovered = Color(74, 99, 68),
category = Color(46, 64, 44),
category_opened = Color(46, 64, 44, 0),
theme = Color(110, 160, 90),
panel = {
Color(40, 56, 40),
Color(66, 86, 66),
Color(110, 160, 90)
},
toggle = Color(35, 44, 34),
focus_panel = Color(46, 58, 44),
hover = Color(110, 160, 90, 90),
window_shadow = Color(0, 0, 0, 100),
gray = Color(148, 165, 140, 220),
text = Color(232, 244, 235)
}
Mantle.color_moss.panel_alpha = {
ColorAlpha(Mantle.color_moss.panel[1], 150),
ColorAlpha(Mantle.color_moss.panel[2], 150),
ColorAlpha(Mantle.color_moss.panel[3], 150)
}
-- Коралловая палитра
Mantle.color_coral = {
header = Color(52, 32, 36),
header_text = Color(255, 243, 242),
background = Color(18, 14, 16),
background_alpha = Color(18, 14, 16, 210),
background_panelpopup = Color(30, 22, 24, 150),
button = Color(116, 66, 61),
button_shadow = Color(8, 4, 6, 30),
button_hovered = Color(134, 73, 68),
category = Color(74, 40, 42),
category_opened = Color(74, 40, 42, 0),
theme = Color(255, 120, 90),
panel = {
Color(66, 38, 40),
Color(120, 60, 56),
Color(240, 120, 90)
},
toggle = Color(58, 39, 37),
focus_panel = Color(72, 42, 44),
hover = Color(255, 120, 90, 90),
window_shadow = Color(0, 0, 0, 100),
gray = Color(167, 136, 136, 220),
text = Color(255, 243, 242)
}
Mantle.color_coral.panel_alpha = {
ColorAlpha(Mantle.color_coral.panel[1], 150),
ColorAlpha(Mantle.color_coral.panel[2], 150),
ColorAlpha(Mantle.color_coral.panel[3], 150)
}

View File

@@ -0,0 +1,326 @@
Mantle.func = {
sw = ScrW(),
sh = ScrH(),
ents_scales = {},
}
local function CreateFonts()
local function CreateFont(name, font_name, size)
surface.CreateFont(name, {
font = font_name,
size = size,
extended = true
})
end
local old_surface_SetFont = surface.SetFont
local createdFonts = {
['Fated.16'] = true
}
CreateFont('Fated.16', 'Montserrat Medium', 16)
function surface.SetFont(font)
if type(font) != 'string' then
if font == nil then
ErrorNoHalt('surface.SetFont called with nil! Using fallback font')
old_surface_SetFont('DermaDefault')
return
end
old_surface_SetFont(font)
return
end
if !createdFonts[font] and font:match('^Fated%.') then
local size, isBold = font:match('^Fated%.(%d+)(b?)$')
if size then
size = tonumber(size)
local fontFamily = isBold == 'b' and 'Montserrat Bold' or 'Montserrat Medium'
CreateFont(font, fontFamily, size)
createdFonts[font] = true
end
end
old_surface_SetFont(font)
end
end
local math_sin = math.sin
local math_clamp = math.Clamp
local math_abs = math.abs
local function CreateFunc()
local mat_blur = Material('pp/blurscreen')
--[[
Отрисовка размытия у панели.
Применяется в функциях отрисовки (например Paint)
]]--
function Mantle.func.blur(panel)
local x, y = panel:LocalToScreen(0, 0)
surface.SetDrawColor(color_white)
surface.SetMaterial(mat_blur)
for i = 1, 6 do
if !mat_blur:GetFloat('$blur') then
mat_blur:SetFloat('$blur', i)
mat_blur:Recompute()
end
render.UpdateScreenEffectTexture()
surface.DrawTexturedRect(-x, -y, Mantle.func.sw, Mantle.func.sh)
end
end
local listGradients = {
Material('vgui/gradient_up'),
Material('vgui/gradient_down'),
Material('vgui/gradient-l'),
Material('vgui/gradient-r')
}
--[[
Отрисовка градиента
]]--
function Mantle.func.gradient(_x, _y, _w, _h, direction, color_shadow, radius, flags)
radius = radius and radius or 0
RNDX.DrawMaterial(radius, _x, _y, _w, _h, color_shadow, listGradients[direction], flags)
end
function Mantle.func.sound(path)
surface.PlaySound(path or 'mantle/btn_click.ogg')
end
Mantle.func.w_save = {}
Mantle.func.h_save = {}
--[[
Получение относительной ширины (на основе 1920)
При указании Mantle.func.w(20), 20 будет менять в меньшую сторону или большую в зависимости от ширины экрана
]]--
function Mantle.func.w(px)
if !Mantle.func.w_save[px] then
Mantle.func.w_save[px] = px / 1920 * Mantle.func.sw
end
return Mantle.func.w_save[px]
end
--[[
Получение относительной высоты (на основе 1080)
]]--
function Mantle.func.h(px)
if !Mantle.func.h_save[px] then
Mantle.func.h_save[px] = px / 1080 * Mantle.func.sh
end
return Mantle.func.h_save[px]
end
local function EntText(text, y)
surface.SetFont('Fated.40')
local tw, th = surface.GetTextSize(text)
local bx, by = -tw * 0.5 - 18, y - 12
local bw, bh = tw + 36, th + 24
RNDX().Rect(bx, by, bw, bh - 6)
:Radii(16, 16, 0, 0)
:Blur()
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(bx, by, bw, bh - 6)
:Radii(16, 16, 0, 0)
:Color(Mantle.color.background_alpha)
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(bx, by + bh - 6, bw, 6)
:Radii(0, 0, 16, 16)
:Color(Mantle.color.text)
:Draw()
draw.SimpleText(text, 'Fated.40', 0, y - 2, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
--[[
Отрисовка текста на Entity.
Применяется в функциях отрисовки (например ENT:Draw)
]]--
function Mantle.func.draw_ent_text(ent, text, posY)
local distSqr = EyePos():DistToSqr(ent:GetPos())
local maxDist = 380
if distSqr > maxDist * maxDist then return end
local dist = math.sqrt(distSqr)
local minDist = 20
local idx = ent:EntIndex()
local prev = Mantle.func.ents_scales[idx] or 0
local normalized = math.Clamp((maxDist - dist) / math.max(1, (maxDist - minDist)), 0, 1)
local appearThreshold = 0.8
local disappearThreshold = 0.01
local target
if normalized <= disappearThreshold then
target = 0
elseif normalized >= appearThreshold then
target = 1
else
target = (normalized - disappearThreshold) / (appearThreshold - disappearThreshold)
end
local dt = FrameTime() or 0.016
local appearSpeed = 18
local disappearSpeed = 12
local speed = (target > prev) and appearSpeed or disappearSpeed
local cur = Mantle.func.approachExp(prev, target, speed, dt)
if math.abs(cur - target) < 0.0005 then cur = target end
Mantle.func.ents_scales[idx] = cur
local eased = Mantle.func.easeInOutCubic(cur)
local alpha = eased
local baseScale = 0.13
local camScale = baseScale * math.max(1e-4, eased)
if eased < 0.01 then
surface.SetAlphaMultiplier(1)
return
end
local _, max = ent:GetRotatedAABB(ent:OBBMins(), ent:OBBMaxs())
local rot = (ent:GetPos() - EyePos()):Angle().yaw - 90
local bob = math.sin(CurTime() + idx) / 3 + 0.5
local center = ent:LocalToWorld(ent:OBBCenter())
surface.SetAlphaMultiplier(alpha)
cam.Start3D2D(center + Vector(0, 0, math.abs(max.z / 2) + 12 + bob), Angle(0, rot, 90), camScale)
EntText(text, posY)
cam.End3D2D()
surface.SetAlphaMultiplier(1)
end
local scaleFactor = 0.8
function Mantle.func.animate_appearance(panel, target_w, target_h, duration, alpha_dur, callback, scale_factor)
if not IsValid(panel) then return end
duration = (duration and duration > 0) and duration or 0.18
alpha_dur = (alpha_dur and alpha_dur > 0) and alpha_dur or duration
local startTime = SysTime()
local targetX, targetY = panel:GetPos()
local initialW = target_w * (scale_factor and scale_factor or scaleFactor)
local initialH = target_h * (scale_factor and scale_factor or scaleFactor)
local initialX = targetX + (target_w - initialW) / 2
local initialY = targetY + (target_h - initialH) / 2
panel:SetSize(initialW, initialH)
panel:SetPos(initialX, initialY)
panel:SetAlpha(0)
local curW, curH = initialW, initialH
local curX, curY = initialX, initialY
local curA = 0
local eps = 0.5
local alpha_eps = 1
local speedSize = 3 / math.max(0.0001, duration)
local speedAlpha = 3 / math.max(0.0001, alpha_dur)
panel.Think = function()
if not IsValid(panel) then return end
local dt = FrameTime()
curW = Mantle.func.approachExp(curW, target_w, speedSize, dt)
curH = Mantle.func.approachExp(curH, target_h, speedSize, dt)
curX = Mantle.func.approachExp(curX, targetX, speedSize, dt)
curY = Mantle.func.approachExp(curY, targetY, speedSize, dt)
curA = Mantle.func.approachExp(curA, 255, speedAlpha, dt)
panel:SetSize(curW, curH)
panel:SetPos(curX, curY)
panel:SetAlpha(math.floor(curA + 0.5))
local doneSize = math.abs(curW - target_w) <= eps and math.abs(curH - target_h) <= eps
local donePos = math.abs(curX - targetX) <= eps and math.abs(curY - targetY) <= eps
local doneAlpha = math.abs(curA - 255) <= alpha_eps
if doneSize and donePos and doneAlpha then
panel:SetSize(target_w, target_h)
panel:SetPos(targetX, targetY)
panel:SetAlpha(255)
panel.Think = nil
if callback then callback(panel) end
end
end
end
--[[
Плавное изменение цвета с одного на другой
]]--
function Mantle.func.LerpColor(frac, col1, col2)
local ft = FrameTime() * frac
return Color(
Lerp(ft, col1.r, col2.r),
Lerp(ft, col1.g, col2.g),
Lerp(ft, col1.b, col2.b),
Lerp(ft, col1.a, col2.a)
)
end
--[[
Функции анимации
]]--
function Mantle.func.approachExp(current, target, speed, dt)
local t = 1 - math.exp(-speed * dt)
return current + (target - current) * t
end
function Mantle.func.easeOutCubic(t)
return 1 - (1 - t) * (1 - t) * (1 - t)
end
function Mantle.func.easeInOutCubic(t)
if t < 0.5 then
return 4 * t * t * t
else
return 1 - math.pow(-2 * t + 2, 3) / 2
end
end
--[[
Умное позиционирование панели относительно экрана
]]--
function Mantle.func.ClampMenuPosition(panel)
if not IsValid(panel) then return end
local x, y = panel:GetPos()
local w, h = panel:GetSize()
local sw, sh = Mantle.func.sw, Mantle.func.sh
if x < 5 then x = 5 elseif x + w > sw - 5 then x = sw - 5 - w end
if y < 5 then y = 5 elseif y + h > sh - 5 then y = sh - 5 - h end
panel:SetPos(x, y)
end
end
CreateFunc()
CreateFonts()
hook.Add('OnScreenSizeChanged', 'Mantle', function()
local newW, newH = ScrW(), ScrH()
if newW != Mantle.func.sw and newH != Mantle.func.sh then
Mantle.func.sw, Mantle.func.sh = newW, newH
Mantle.func.w_save = {}
Mantle.func.h_save = {}
CreateFunc()
-- CreateFonts()
end
end)

View File

@@ -0,0 +1,25 @@
function Mantle.lang.get(addon, key)
if !Mantle.lang.list[addon] then
print('Mantle.lang.get: addon "' .. addon .. '" not found!')
end
local lang = GetConVar('gmod_language'):GetString()
local langTable = Mantle.lang.list[addon][lang]
if !Mantle.lang.list[addon][lang] then
langTable = Mantle.lang.list[addon][Mantle.lang.default]
end
if !langTable then
for _, v in pairs(Mantle.lang.list[addon]) do
langTable = v
break
end
if !langTable then
print('Mantle.lang.get: addon "' .. addon .. '" has no language tables!')
return key
end
end
return langTable[key] or key
end

View File

@@ -0,0 +1,314 @@
--[[
Старые функции отрисовки ui-элементов
]]--
local color_gray = Color(200, 200, 200)
local color_red = Color(255, 50, 50)
local mat_close = Material('mantle/close_btn.png')
function Mantle.ui.frame(s, title, width, height, close_bool, anim_bool)
s:SetSize(width, height)
s:SetTitle('')
s:ShowCloseButton(false)
s:DockPadding(6, 30, 6, 6)
s.f_title = title
s.center_title = ''
s.background_alpha = true
s.Paint = function(self, w, h)
local x, y = self:LocalToScreen()
BShadows.BeginShadow()
draw.RoundedBoxEx(6, x, y, w, 24, Mantle.color.header, true, true)
draw.RoundedBoxEx(6, x, y + 24, w, h - 24, s.background_alpha and Mantle.color.background_alpha or Mantle.color.background, false, false, true, true)
draw.SimpleText(self.f_title, 'Fated.16', x + 6, y + 4, Mantle.color.text)
if self.center_title then
draw.SimpleText(s.center_title, 'Fated.20b', x + w * 0.5, y + 11, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
BShadows.EndShadow(1, 2, 2, 255, 0, 0)
end
if anim_bool then
Mantle.func.animate_appearance(s, width, height, 0.1, 0.2)
end
if close_bool then
s.cls = vgui.Create('DButton', s)
s.cls:SetSize(20, 20)
s.cls:SetPos(width - 22, 2)
s.cls:SetText('')
s.cls.Paint = function(_, w, h)
surface.SetDrawColor(color_white)
surface.SetMaterial(mat_close)
surface.DrawTexturedRect(0, 0, w, h)
end
s.cls.DoClick = function()
s:AlphaTo(0, 0.1, 0, function()
s:Remove()
end)
end
s.cls.DoRightClick = function()
local DM = Mantle.ui.derma_menu()
DM:AddOption('Закрыть окно', function()
s:Remove()
end, 'icon16/cross.png')
end
end
end
function Mantle.ui.sp(s)
local vbar = s:GetVBar()
vbar:SetWide(12)
vbar:SetHideButtons(true)
vbar.Paint = nil
vbar.btnGrip.Paint = function(self, w, h)
if self.Depressed then
self:SetCursor('sizens')
end
draw.RoundedBox(6, 6, 0, w - 6, h, Mantle.color.theme)
end
end
function Mantle.ui.btn(s, icon, icon_size, col, rad, off_grad_bool, hov_color, off_hov_bool)
s:SetTall(32)
s.hoverStatus = 0
s.btn_font = 'Fated.18'
s.Paint = function(self, w, h)
if !self.btn_text then
self.btn_text = self:GetText()
self:SetText('')
end
if self:IsHovered() then
self.hoverStatus = math.Clamp(self.hoverStatus + 4 * FrameTime(), 0, 255)
else
self.hoverStatus = math.Clamp(self.hoverStatus - 8 * FrameTime(), 0, 255)
end
draw.RoundedBox(rad and rad or 6, 0, 0, w, h, col and col or Mantle.color.button)
if !off_hov_bool then
local color_hover = hov_color and hov_color or Mantle.color.button_hovered
color_hover = Color(color_hover.r, color_hover.g, color_hover.b, 255 * self.hoverStatus)
draw.RoundedBox(rad and rad or 6, 0, 0, w, h, color_hover)
end
if !off_grad_bool then
Mantle.func.gradient(0, 0, w, h, 1, Mantle.color.button_shadow)
end
draw.SimpleText(self.btn_text, self.btn_font, w * 0.5 + (icon and icon_size * 0.5 - 2 or 0), h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if icon then
surface.SetDrawColor(color_white)
surface.SetMaterial(icon)
local indent = (h - icon_size) * 0.5
surface.DrawTexturedRect(indent, indent, icon_size, icon_size)
end
end
end
function Mantle.ui.slidebox(parent, label, min_value, max_value, convar, decimals)
local slider = vgui.Create('DButton', parent)
slider:Dock(TOP)
slider:DockMargin(0, 6, 0, 0)
slider:SetTall(40)
slider:SetText('')
local value = GetConVar(convar):GetFloat()
local sections = max_value - min_value
local smoothPos = 0
local targetPos = 0
local function UpdateSliderPosition(new_value)
local progress = (new_value - min_value) / sections
targetPos = (slider:GetWide() - 16) * progress
LocalPlayer():ConCommand(convar .. ' ' .. new_value)
value = new_value
end
UpdateSliderPosition(value)
slider.Paint = function(self, w, h)
draw.RoundedBox(4, 0, h - 16, w, 6, Mantle.color.panel_alpha[1])
smoothPos = Lerp(FrameTime() * 10, smoothPos, targetPos)
draw.RoundedBox(16, smoothPos, 18, 16, 16, Mantle.color.theme)
draw.SimpleText(label, 'Fated.18', 4, 0, Mantle.color.text)
draw.SimpleText(math.Round(value, decimals), 'Fated.18', w - 4, 0, Mantle.color.text, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
end
local function UpdateSliderByCursorPos(x)
local progress = math.Clamp(x / (slider:GetWide() - 16), 0, 1)
local new_value = math.Round(min_value + (progress * sections), decimals)
UpdateSliderPosition(new_value)
end
slider.OnMousePressed = function(_, mcode)
if mcode == MOUSE_LEFT then
UpdateSliderByCursorPos(slider:CursorPos())
slider:MouseCapture(true)
end
end
slider.OnMouseReleased = function(_, mcode)
if mcode == MOUSE_LEFT then
slider:MouseCapture(false)
end
end
slider.OnCursorMoved = function(_, x, _)
if input.IsMouseDown(MOUSE_LEFT) then
UpdateSliderByCursorPos(x)
end
end
return slider
end
function Mantle.ui.desc_entry(parent, title, placeholder, off_title_bool)
if !off_title_bool and title then
local label = vgui.Create('DLabel', parent)
label:Dock(TOP)
label:DockMargin(4, 0, 4, 0)
label:SetText(title)
label:SetFont('Fated.16')
end
local entry_background = vgui.Create('DPanel', parent)
entry_background:Dock(TOP)
entry_background:DockMargin(4, 4, 4, 0)
entry_background:SetTall(24)
local entry = vgui.Create('DTextEntry', entry_background)
entry:Dock(FILL)
entry:DockMargin(2, 4, 2, 4)
entry:SetPlaceholderText(placeholder)
entry:SetFont('Fated.16')
entry:SetDrawLanguageID(false)
entry:SetPaintBackground(false)
return entry, entry_background
end
function Mantle.ui.checkbox(parent, text, convar)
local panel = vgui.Create('DPanel', parent)
panel:Dock(TOP)
panel:DockMargin(4, 0, 4, 0)
panel:SetTall(28)
panel.Paint = function(_, w, h)
draw.RoundedBox(6, 0, 0, w, h, Mantle.color.panel_alpha[2])
draw.SimpleText(text, 'Fated.18', 8, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
local option = vgui.Create('DButton', panel)
option:Dock(RIGHT)
option:SetWide(56)
option:SetText('')
option.enabled = convar and GetConVar(convar):GetBool() or false
option.Paint = function(self, w, h)
draw.RoundedBoxEx(6, 0, 0, w, h, Mantle.color.panel_alpha[1], false, true, false, true)
draw.SimpleText(self.enabled and 'ВКЛ' or 'ВЫКЛ', 'Fated.19', w * 0.5 - 1, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
option.DoClick = function()
if convar then
RunConsoleCommand(convar, option.enabled and 0 or 1)
end
option.enabled = !option.enabled
end
return panel, option
end
function Mantle.ui.panel_tabs(parent)
local panel_tabs = vgui.Create('DPanel', parent)
panel_tabs:Dock(FILL)
panel_tabs.Paint = nil
panel_tabs.content = {}
panel_tabs.active_tab = ''
panel_tabs.sp = vgui.Create('DHorizontalScroller', panel_tabs)
panel_tabs.sp:Dock(TOP)
panel_tabs.sp:DockMargin(0, 0, 0, 6)
panel_tabs.sp:SetTall(24)
panel_tabs.sp:SetOverlap(-6)
panel_tabs.panel_content = vgui.Create('DPanel', panel_tabs)
panel_tabs.panel_content:Dock(FILL)
panel_tabs.panel_content.Paint = function(_, w, h)
if panel_tabs.active_tab == '' then
draw.SimpleText('Выберете вкладку', 'Fated.16', w * 0.5, h * 0.5 - panel_tabs.sp:GetTall() - 7, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
function panel_tabs:AddTab(title, panel, icon, col, col_hov)
panel_tabs.content[title] = panel
panel_tabs.content[title]:SetParent(panel_tabs.panel_content)
panel_tabs.content[title]:Dock(FILL)
panel_tabs.content[title]:SetVisible(false)
local btn_tab = vgui.Create('DButton', panel_tabs.sp)
surface.SetFont('Fated.20')
btn_tab:SetSize(surface.GetTextSize(title) + 10 + (icon and 18 or 0), 20)
btn_tab:SetText('')
if icon then
btn_tab.icon = Material(icon)
panel_tabs.content[title].icon = icon
end
btn_tab.Paint = function(self, w, h)
draw.RoundedBox(6, 0, 0, w, h, panel_tabs.active_tab == title and (col_hov and col_hov or Mantle.color.panel[2]) or (col and col or Mantle.color.theme))
if self:IsHovered() then
draw.RoundedBox(6, 0, 0, w, h, Mantle.color.button_shadow)
end
draw.SimpleText(title, 'Fated.20', w * 0.5 + (self.icon and 9 or 0), 11, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if self.icon then
surface.SetDrawColor(color_white)
surface.SetMaterial(self.icon)
surface.DrawTexturedRect(4, 4, 16, 16)
end
end
btn_tab.DoClick = function()
panel_tabs:ActiveTab(title)
end
btn_tab.DoRightClick = function()
local DM = Mantle.ui.derma_menu()
for tab_name, tab in pairs(panel_tabs.content) do
DM:AddOption(tab_name, function()
panel_tabs:ActiveTab(tab_name)
end, tab.icon)
end
end
panel_tabs.sp:AddPanel(btn_tab)
end
function panel_tabs:ActiveTab(title)
if title == panel_tabs.active_tab then
return
end
for tab_title, tab in pairs(panel_tabs.content) do
if tab_title != title then
tab:SetVisible(false)
else
tab:SetVisible(true)
panel_tabs.active_tab = title
end
end
end
return panel_tabs
end

View File

@@ -0,0 +1,909 @@
local function CreateMenu()
if IsValid(menuMantle) then
menuMantle:Remove()
end
menuMantle = vgui.Create('MantleFrame')
menuMantle:SetSize(920, 640)
menuMantle:Center()
menuMantle:MakePopup()
menuMantle:SetTitle('Mantle')
menuMantle:SetCenterTitle('Основное меню библиотеки')
menuMantle:ShowAnimation()
local tabs = vgui.Create('MantleTabs', menuMantle)
tabs:Dock(FILL)
local function CreateTabHeader(title, subtitle, icon, pan)
local header = vgui.Create('Panel', pan)
header:Dock(TOP)
header:DockMargin(0, 0, 0, 8)
header:SetTall(56)
header.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(8)
:Color(Mantle.color.panel_alpha[2])
:Draw()
RNDX().Rect(12, h * 0.5 - 12, 24, 24)
:Color(255, 255, 255)
:Material(icon)
:Draw()
draw.SimpleText(title, 'Fated.20', 48, 10, Mantle.color.text)
draw.SimpleText(subtitle, 'Fated.16', 48, h - 10, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
end
return header
end
local function CreateCopyButton(parent, snippet)
local b = vgui.Create('DButton', parent)
b:SetText('')
b:SetWide(110)
b.Paint = function(me, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(6)
:Color(Mantle.color.panel_alpha[1])
:Draw()
draw.SimpleText('Скопировать', 'Fated.16', w / 2, h / 2, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
b.DoClick = function()
SetClipboardText(snippet)
menuMantle:Notify(snippet)
Mantle.func.sound()
end
return b
end
local function CreateInfo(info, pan)
local panelInfo = vgui.Create('Panel')
panelInfo:Dock(TOP)
panelInfo:DockMargin(0, 0, 0, 6)
panelInfo:SetTall(50)
panelInfo.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(6)
:Color(Mantle.color.panel_alpha[2])
:Draw()
Mantle.func.gradient(0, 0, 6, h, 3, Mantle.color.theme, 6)
draw.SimpleText(info[1], 'Fated.20', 16, 7, Mantle.color.text)
draw.SimpleText(info[2], 'Fated.16', 16, h - 7, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
end
local copyBtn = CreateCopyButton(panelInfo, info[1])
copyBtn:Dock(RIGHT)
copyBtn:DockMargin(0, 10, 10, 10)
pan:AddItem(panelInfo)
end
local function CreateCategory(name, info_table, pan, ui_element, is_active)
local panel = vgui.Create('MantleCategory', pan)
panel:Dock(TOP)
panel:DockMargin(0, 0, 0, 6)
panel:SetText(name)
if is_active then
panel:SetActive(true)
end
for _, info in ipairs(info_table) do
CreateInfo(info, panel)
end
if ui_element then
panel:AddItem(ui_element)
end
end
local function CreateTabElements()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('UI Элементы', 'Демонстрация всех компонентов Mantle. Клик по элементу открывает пример.', Material('icon16/chart_pie.png'), panel)
local menuWide = menuMantle:GetWide()
-- Кнопка
local panelBtns = vgui.Create('Panel')
panelBtns:Dock(TOP)
panelBtns:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
panelBtns:SetTall(132)
local btn1 = vgui.Create('MantleBtn', panelBtns)
btn1:Dock(TOP)
btn1:SetTall(40)
btn1:SetTxt('Стандартная кнопка')
local btn2 = vgui.Create('MantleBtn', panelBtns)
btn2:Dock(TOP)
btn2:DockMargin(0, 6, 0, 0)
btn2:SetTall(40)
btn2:SetTxt('Эффект волны')
btn2:SetRipple(true)
local btn3 = vgui.Create('MantleBtn', panelBtns)
btn3:Dock(TOP)
btn3:DockMargin(0, 6, 0, 0)
btn3:SetTall(40)
btn3:SetTxt('Кастомный цвет')
btn3:SetColor(Color(182, 65, 65))
btn3:SetColorHover(Color(143, 57, 57))
btn3:SetIcon(Material('icon16/delete.png'), 16)
CreateCategory('Кнопка (MantleBtn)', {
{':SetHover(bool is_hover)', 'Включить/выключить цвет наведения (дефолт - true)'},
{':SetFont(string font)', 'Установить шрифт'},
{':SetRadius(int rad)', 'Установить размер закругления'},
{':SetIcon(string icon, int icon_size)', 'Установить иконку'},
{':SetTxt(string text)', 'Установить текст'},
{':SetColor(color col)', 'Установить цвет кнопки'},
{':SetColorHover(color col)', 'Установить цвет наведения'},
{':SetGradient(bool is_grad)', 'Включить/выключить градиент (дефолт - true)'},
{':SetRipple(bool is_ripple)', 'Включить/выключить эффект волн (дефолт - false)'}
}, panel, panelBtns)
-- Чекбокс
local checkbox = vgui.Create('MantleCheckBox')
checkbox:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
checkbox:Dock(TOP)
checkbox:SetTxt('Отображение HUD')
checkbox:SetConvar('cl_drawhud')
CreateCategory('Тумблер (MantleCheckBox)', {
{':SetTxt(string text)', 'Установить текст'},
{':SetValue(bool value)', 'Установить bool-значение тумблера'},
{':GetBool()', 'Получить bool-значение тумблера'},
{':SetConvar(string convar)', 'Установить ConVar'},
{':SetDescription(string desc)', 'Установить описание для тумблера'},
{':OnChange(bool new_value)', 'Вызывается при изменении значения тумблера'}
}, panel, checkbox)
-- Ввод текста
local entry = vgui.Create('MantleEntry')
entry:Dock(TOP)
entry:DockMargin(menuWide * 0.35, 6, menuWide * 0.35, 0)
entry:SetTitle('Никнейм')
entry:SetPlaceholder('darkf')
CreateCategory('Ввод текста (MantleEntry)', {
{':SetTitle(string text)', 'Установить заголовок'},
{':SetPlaceholder(string text)', 'Установить фоновый текст (появляется при пустом поле)'},
{':GetValue()', 'Получить string-значение поля'}
}, panel, entry)
-- Окно
local panelFrames = vgui.Create('Panel')
panelFrames:Dock(TOP)
panelFrames:SetTall(92)
local btnFrame1 = vgui.Create('MantleBtn', panelFrames)
btnFrame1:Dock(TOP)
btnFrame1:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
btnFrame1:SetTxt('Обычное окно')
btnFrame1:SetTall(40)
btnFrame1.DoClick = function()
local frame = vgui.Create('MantleFrame')
frame:SetSize(400, 300)
frame:Center()
frame:MakePopup()
frame:SetCenterTitle('Центр')
end
local btnFrame2 = vgui.Create('MantleBtn', panelFrames)
btnFrame2:Dock(TOP)
btnFrame2:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
btnFrame2:SetTxt('Lite-режим')
btnFrame2:SetTall(40)
btnFrame2.DoClick = function()
local frame = vgui.Create('MantleFrame')
frame:SetSize(400, 300)
frame:Center()
frame:MakePopup()
frame:LiteMode()
end
CreateCategory('Окно (MantleFrame)', {
{':SetAlphaBackground(bool is_alpha)', 'Включить/выключить прозрачность окна (дефолт - false)'},
{':SetTitle(string title)', 'Установить заголовок'},
{':SetCenterTitle(string title)', 'Установить центральный заголовок'},
{':ShowAnimation()', 'Активировать анимацию при появлении меню'},
{':DisableCloseBtn()', 'Скрыть кнопку закрытия'},
{':SetDraggable(bool is_draggable)', 'Включить/выключить перемещение окна'},
{':LiteMode()', 'Активировать режим Lite (без верхней панели)'},
{':Notify(string text, number duration, color col)', 'Показать уведомление внизу окна (дефолт времени - 2 сек., цвета - Mantle.color.theme)'}
}, panel, panelFrames)
-- ScrollPanel
local sp = vgui.Create('MantleScrollPanel')
sp:Dock(TOP)
sp:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
sp:SetTall(150)
for spK = 1, 10 do
local spPanel = vgui.Create('DPanel', sp)
spPanel:Dock(TOP)
spPanel:DockMargin(0, 0, 0, 6)
spPanel:SetTall(24)
spPanel.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
end
CreateCategory('Панель прокрутки (MantleScrollPanel)', {
{':SetScroll(number offset)', 'Установить смещение прокрутки'},
{':GetScroll()', 'Получить текущее смещение прокрутки'},
{':AddItem(object panel)', 'Добавить элемент в панель'},
{':Clear()', 'Очистить панель от всего'},
{':DisableVBarPadding()', 'Отключить отступ справа для скроллбара (по умолчанию имеется)'}
}, panel, sp)
-- Вкладки
local panelTabs = vgui.Create('Panel')
panelTabs:Dock(TOP)
panelTabs:SetTall(280)
local testTabs = vgui.Create('MantleTabs', panelTabs) -- modern стиль
testTabs:Dock(TOP)
testTabs:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
testTabs:SetTall(150)
local testTab1 = vgui.Create('DPanel')
testTab1.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(53, 98, 40)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs:AddTab('Test1', testTab1)
local testTab2 = vgui.Create('DPanel')
testTab2.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(108, 41, 45)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs:AddTab('Test2', testTab2)
local testTabs2 = vgui.Create('MantleTabs', panelTabs) -- classic стиль
testTabs2:Dock(FILL)
testTabs2:DockMargin(menuWide * 0.3, 10, menuWide * 0.3, 0)
testTabs2:SetTabStyle('classic')
local testTab3 = vgui.Create('DPanel')
testTab3.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(51, 61, 116)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs2:AddTab('Test3', testTab3)
local testTab4 = vgui.Create('DPanel')
testTab4.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(138, 89, 43)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs2:AddTab('Test4', testTab4)
local testTab5 = vgui.Create('DPanel')
testTab5.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(43, 138, 133)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs2:AddTab('С иконкой', testTab5, Material('icon16/folder.png'))
CreateCategory('Вкладки (MantleTabs)', {
{':SetTabStyle(string style)', 'Установить стиль вкладок (modern или classic)'},
{':SetTabHeight(int height)', 'Установить высоту вкладок'},
{':SetIndicatorHeight(int height)', 'Установить высоту индикатора вкладок'},
{':AddTab(string name, object panel, string icon)', 'Добавить вкладку'}
}, panel, panelTabs)
-- Выбор варианта
local combo = vgui.Create('MantleComboBox')
combo:SetPlaceholder('Выберите вариант')
combo:AddChoice('Вариант 1', 'value1')
combo:AddChoice('Вариант 2', 'value2')
combo:AddChoice('Вариант 3', 'value3')
combo:AddChoice('Вариант 4', 'value4')
combo:AddChoice('Вариант 5', 'value5')
combo:AddChoice('Вариант 6', 'value6')
combo:AddChoice('Вариант 7', 'value7')
combo:AddChoice('Вариант 8', 'value8')
combo.OnSelect = function(idx, text, data)
chat.AddText(color_white, 'Вы выбрали: ', Mantle.color.theme, text, color_white, ' (', tostring(data), ')')
end
combo:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
combo:Dock(TOP)
CreateCategory('Выпадающий список (MantleComboBox)', {
{':AddChoice(string text, any data)', 'Добавить вариант в список (data — любое значение, связанное с пунктом)'},
{':SetValue(string text)', 'Установить выбранное значение по тексту'},
{':GetValue()', 'Получить выбранное значение (текст)'},
{':SetPlaceholder(string text)', 'Установить текст-заполнитель (placeholder)'},
{':OnSelect(idx, text, data)', 'Вызывается при выборе варианта: idx — индекс, text — текст, data — значение'}
}, panel, combo)
-- Таблица
local tableExample = vgui.Create('MantleTable')
tableExample:Dock(TOP)
tableExample:DockMargin(menuWide * 0.2, 6, menuWide * 0.2, 0)
tableExample:SetTall(250)
tableExample:AddColumn('Название', 200, TEXT_ALIGN_LEFT, true)
tableExample:AddColumn('Тип', 120, TEXT_ALIGN_CENTER, true)
tableExample:AddColumn('Качество', 100, TEXT_ALIGN_CENTER, true)
tableExample:AddColumn('Цена', 110, TEXT_ALIGN_RIGHT, true)
local products = {
{'Молоко "Домик в деревне"', 'Молочка', 'Высшее', 89},
{'Хлеб "Бородинский"', 'Выпечка', 'Стандарт', 45},
{'Сок "Добрый"', 'Напитки', 'Премиум', 120},
{'Шоколад "Аленка"', 'Конфеты', 'Высшее', 95},
{'Йогурт "Активиа"', 'Молочка', 'Премиум', 65},
{'Пельмени "Сибирские"', 'Заморозка', 'Стандарт', 350},
{'Колбаса "Докторская"', 'Мясо', 'Высшее', 450},
{'Сыр "Российский"', 'Молочка', 'Стандарт', 380},
{'Пицца "Пепперони"', 'Заморозка', 'Премиум', 450},
{'Чай "Липтон"', 'Напитки', 'Стандарт', 180},
{'Печенье "Юбилейное"', 'Выпечка', 'Стандарт', 85},
{'Масло "Крестьянское"', 'Молочка', 'Высшее', 120},
{'Сметана "Простоквашино"', 'Молочка', 'Стандарт', 65},
{'Курица "Бройлер"', 'Мясо', 'Стандарт', 280},
{'Рыба "Минтай"', 'Морепродукты', 'Стандарт', 320},
{'Яблоки "Голден"', 'Фрукты', 'Высшее', 180},
{'Картофель', 'Овощи', 'Стандарт', 45},
{'Морковь', 'Овощи', 'Стандарт', 35},
{'Бананы', 'Фрукты', 'Стандарт', 120},
{'Апельсины', 'Фрукты', 'Премиум', 180}
}
for _, product in ipairs(products) do
tableExample:AddItem(unpack(product))
end
tableExample:SetAction(function(row_data)
chat.AddText(color_white, 'Выбран продукт: ', Mantle.color.theme, row_data[1], color_white, ' (', row_data[2], ')')
end)
CreateCategory('Таблица (MantleTable)', {
{':AddColumn(string name, number width, number align, bool sortable)', 'Добавить колонку'},
{':AddItem(...)', 'Добавить строку. Количество аргументов должно соответствовать количеству колонок'},
{':SetAction(function(table row_data))', 'Установить функцию, вызываемую при клике на строку. row_data — массив значений строки'},
{':SetRightClickAction(function(table row_data))', 'Установить функцию, вызываемую при правом клике на строку'},
{':Clear()', 'Очистить таблицу от всех строк'},
{':GetSelectedRow()', 'Получить данные выбранной строки (массив значений)'},
{':GetRowCount()', 'Получить количество строк в таблице'},
{':RemoveRow(number index)', 'Удалить строку по индексу (начиная с 1)'}
}, panel, tableExample)
-- Категория
local panelCat = vgui.Create('Panel')
panelCat:Dock(TOP)
panelCat:DockMargin(0, 6, 0, 0)
panelCat:SetTall(142)
panelCat.Paint = nil
local cat = vgui.Create('MantleCategory', panelCat)
cat:Dock(TOP)
cat:SetCenterText(true)
cat:SetActive(true)
local panGreen = vgui.Create('DPanel')
panGreen:Dock(TOP)
panGreen:SetTall(50)
panGreen.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(93, 179, 101)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
cat:AddItem(panGreen)
local panRed = vgui.Create('DPanel')
panRed:Dock(TOP)
panRed:DockMargin(0, 6, 0, 0)
panRed:SetTall(50)
panRed.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(179, 110, 93)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
cat:AddItem(panRed)
CreateCategory('Категория (MantleCategory)', {
{':SetText(string name)', 'Установить название'},
{':AddItem(object panel)', 'Добавить в категорию элемент'},
{':SetColor(color col)', 'Установить кастомный цвет категории'},
{':SetCenterText(bool is_centered)', 'Установить центрирование названия'},
{':SetActive(bool is_active)', 'Установить активность категории (дефолт - false)'}
}, panel, panelCat)
-- Слайдер
local slider = vgui.Create('MantleSlideBox')
slider:Dock(TOP)
slider:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
slider:SetRange(0, 4)
slider:SetConvar('net_graph')
slider:SetText('График')
CreateCategory('Слайдер (MantleSlideBox)', {
{':SetRange(int min_value, int max_value, int decimals)', 'Сделать диапазон слайдера с точностью (дефолт точность - 0)'},
{':SetConvar(string convar)', 'Установить ConVar'},
{':SetText(string text)', 'Установить текстовое обозначение'},
{':SetValue(string val)', 'Установить значение'},
{':GetValue()', 'Получить выбранное значение (число)'},
{':OnValueChanged(string new_value)', 'Вызывается при изменении значения слайдера'}
}, panel, slider)
local panelTexts = vgui.Create('Panel')
panelTexts:Dock(TOP)
panelTexts:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
panelTexts:DockPadding(8, 8, 8, 8)
panelTexts:SetTall(344)
panelTexts.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[2])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local panelText1 = vgui.Create('DPanel', panelTexts)
panelText1:Dock(TOP)
panelText1:DockMargin(0, 0, 0, 6)
panelText1:SetTall(74)
panelText1.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local text1 = vgui.Create('MantleText', panelText1)
text1:Dock(FILL)
text1:SetPadding(10)
text1:SetText('MantleText — компонент для аккуратного вывода многострочного текста. Текст автоматически переносится по ширине и сокращается троеточием')
local panelText2 = vgui.Create('DPanel', panelTexts)
panelText2:Dock(TOP)
panelText2:DockMargin(0, 0, 0, 6)
panelText2:SetTall(100)
panelText2.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local text2 = vgui.Create('MantleText', panelText2)
text2:Dock(FILL)
text2:SetPadding(12)
text2:SetFont('Fated.20')
text2:SetText('Центрирование: горизонталь + вертикаль. Текст выровнен по центру блока.')
text2:SetAlign(TEXT_ALIGN_CENTER)
text2:SetVAlign('center')
local panelText3 = vgui.Create('DPanel', panelTexts)
panelText3:Dock(TOP)
panelText3:DockMargin(0, 0, 0, 6)
panelText3:SetTall(54)
panelText3.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local text3 = vgui.Create('MantleText', panelText3)
text3:Dock(FILL)
text3:SetPadding(8)
text3:SetText('ОченьДлинноеСловоБезПробеловКотороеНужноОтделитьЧтобыНеПорвалосьОформление')
local panelText4 = vgui.Create('DPanel', panelTexts)
panelText4:Dock(TOP)
panelText4:SetTall(82)
panelText4.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local longText = [[
Это длинный пример текста, который занимает несколько строк. Если блок небольшой по высоте — последняя видимая строка будет усечена с троеточием, чтобы не порвать верстку и не выходить за пределы панели нашего меню.
]]
local text4 = vgui.Create('MantleText', panelText4)
text4:Dock(FILL)
text4:SetPadding(8)
text4:SetFont('Fated.16')
text4:SetText(longText)
CreateCategory('Текст (MantleText)', {
{':SetText(string text)', 'Установить текст для отображения'},
{':SetFont(string font)', 'Установить шрифт'},
{':SetColor(color col)', 'Установить цвет текста'},
{':SetAlign(number align)', 'Горизонтальное выравнивание (TEXT_ALIGN_*)'},
{':SetVAlign(string valign)', 'Вертикальное выравнивание: top, center, bottom'},
{':SetPadding(number px)', 'Внутренний отступ от краёв'}
}, panel, panelTexts)
return panel
end
tabs:AddTab('UI Элементы', CreateTabElements(), Material('icon16/chart_pie.png'))
local function CreateShowMenus()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Всплывающие', 'Палитра, derma-меню, radial и другие утилиты.', Material('icon16/application_double.png'), panel)
local listMenus = {
{'Выбор цвета через палитру', function()
Mantle.ui.color_picker(function(col)
chat.AddText('Вы выбрали цвет: ', col, tostring(col))
end, Color(25, 59, 102))
end},
{'Опциональное меню (Derma Menu)', function()
local DM = Mantle.ui.derma_menu()
for i = 1, 5 do
DM:AddOption('Опция ' .. i, function()
chat.AddText('Привет всем! ' .. i)
end)
end
DM:AddSpacer()
DM:AddOption('Узнать свою привилегию', function()
chat.AddText(LocalPlayer():GetUserGroup())
end, 'icon16/status_online.png')
end},
{'Опциональное с подменю (Derma Menu)', function()
local DM = Mantle.ui.derma_menu()
local clothes = DM:AddOption('Одежда')
local subClothes = clothes:AddSubMenu()
subClothes:AddOption('Шапка', function()
chat.AddText('Вы выбрали: Шапка')
end)
subClothes:AddOption('Свитер', function()
chat.AddText('Вы выбрали: Свитер')
end)
local food = DM:AddOption('Еда')
local subFood = food:AddSubMenu()
subFood:AddOption('Морковь', function()
chat.AddText('Вы выбрали: Морковь')
end)
subFood:AddOption('Яблоко', function()
chat.AddText('Вы выбрали: Яблоко')
end)
end},
{'Выбор игрока', function()
Mantle.ui.player_selector(function(pl)
chat.AddText('Вы выбрали игрока: ', color_white, pl:Name())
end)
end},
{'Круговое меню', function()
--[[
Имеется возможность настроить радиальное меню
local configRadial = {
disable_background = true, -- отключает фон
hover_sound = 'buttons/button14.wav', -- звук при наведении
scale_animation = false, -- отключает анимацию масштабирования
radius = 300, -- радиус меню
inner_radius = 100 -- радиус внутреннего круга
}
local rm = Mantle.ui.radial_menu(configRadial)
--]]
local rm = Mantle.ui.radial_menu()
rm:SetCenterText('Действия', 'Выберите действие')
local weaponsMenu = rm:CreateSubMenu('Оружие', 'Выберите оружие')
weaponsMenu:AddOption('Пистолет', function()
chat.AddText(Mantle.color.theme, 'Выбран пистолет')
end, 'icon16/gun.png', 'Обычный пистолет')
weaponsMenu:AddOption('Винтовка', function()
chat.AddText(Mantle.color.theme, 'Выбрана винтовка')
end, 'icon16/gun.png', 'Мощная винтовка')
rm:AddSubMenuOption('Оружие', weaponsMenu, 'icon16/gun.png', 'Выберите оружие')
-- Обычные опции
rm:AddOption('Выбросить', function()
chat.AddText('Выбросить оружие')
end, 'icon16/gun.png', 'Выбросить оружие')
rm:AddOption('Кинуть кубик', function()
chat.AddText('Действие выполнено')
end, 'icon16/controller.png', 'Рандом кубика')
rm:AddOption('Погибнуть', function()
chat.AddText('Действие выполнено')
end, 'icon16/world.png', 'Попрощаться с миром')
rm:AddOption('Хакнуть', function()
chat.AddText('Действие выполнено')
end, 'icon16/server.png', 'Взломать сервер')
rm:AddOption('Посмотреть баланс', function()
chat.AddText('Действие выполнено')
end, 'icon16/money.png', 'Сколько у вас денег')
rm:AddOption('Нет иконки', function()
chat.AddText('Действие выполнено')
end, nil, 'Где иконка?')
end},
{'Написание текста', function()
Mantle.ui.text_box('Заголовок', 'Описание того, что вводиться', function(s)
chat.AddText('Вы ввели: ', color_white, s)
end)
end},
{'Вызов сообщения в Окне', function()
menuMantle:Notify('Тестовое сообщение!')
end}
}
for _, elem in ipairs(listMenus) do
local btn = vgui.Create('MantleBtn', panel)
btn:Dock(TOP)
btn:DockMargin(0, 0, 0, 6)
btn:SetTall(30)
btn:SetTxt(elem[1])
btn.DoClick = function()
elem[2]()
Mantle.func.sound()
end
end
return panel
end
tabs:AddTab('Всплывающие', CreateShowMenus(), Material('icon16/application_double.png'))
local function CreateTabFunctions()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Функции', 'Полный список утилитарных функций Mantle.func и других вспомогательных функций', Material('icon16/cog.png'), panel)
local menuWide = menuMantle:GetWide()
CreateCategory('Размытие панели', {
{'Mantle.func.blur(object panel)', 'Отрисовка размытия панели в Paint'}
}, panel)
CreateCategory('Градиент', {
{'Mantle.func.gradient(int x, int y, int w, int h, int dir, color color_shadow, int radius, flags)', 'Отрисовка градиента (dir: 1 - вверх, 2 - вниз, 3 - влево, 4 - вправо)'}
}, panel)
CreateCategory('Создание звука', {
{'Mantle.func.sound(string path)', 'Проигрывает звук (дефолт - mantle/btn_click.ogg)'}
}, panel)
CreateCategory('Относительные единицы для адаптивного интерфейса', {
{'Mantle.func.w(int px)', 'Относительная ширина (от 1920)'},
{'Mantle.func.h(int px)', 'Относительная высота (от 1080)'}
}, panel)
CreateCategory('Отрисовка текста над энтити', {
{'Mantle.func.draw_ent_text(object ent, string text, int posY)', 'Рисует текст над энтити с плавным появлением (3D2D)'}
}, panel)
CreateCategory('Анимация размера панели', {
{'Mantle.func.animate_appearance(object panel, int w, int h, int duration, int alpha_dur, func callback, int scale_factor)', 'Плавное изменение панели до нужного размера'}
}, panel)
CreateCategory('Плавное изменение цвета', {
{'Mantle.func.LerpColor(int frac, color col1, color col2)', 'Плавный переход цвета от col1 → col2'}
}, panel)
CreateCategory('Загрузка картинки', {
{'http.DownloadMaterial(string url, string path, func callback, int retry_count)', 'Скачивает материал по URL и кэширует его. Повторяет попытку при ошибке, возвращает через callback материал'}
}, panel)
CreateCategory('Серверное уведомление', {
{'Mantle.notify(object pl, color header_color, string header, string text)', 'Отправка сообщений в чат игроку или всем (вместо pl указать true - тогда всем)'}
}, panel)
CreateCategory('Изменение регистра букв', {
{'utf8.lower(string text)', 'Преобразует строку в нижний регистр с поддержкой русских букв'},
{'utf8.upper(string text)', 'Преобразует строку в верхний регистр с поддержкой русских букв'}
}, panel)
return panel
end
tabs:AddTab('Функции', CreateTabFunctions(), Material('icon16/error.png'))
local function CreateLegacyTest()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Legacy UI', 'Набор legacy-утилит (Mantle.ui.*). Для совместимости и примеров.', Material('icon16/exclamation.png'), panel)
local menuWide = menuMantle:GetWide()
local btnFrame = vgui.Create('MantleBtn')
btnFrame:SetTxt('Открыть Legacy Frame')
btnFrame:SetTall(40)
btnFrame:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
btnFrame:Dock(TOP)
btnFrame.DoClick = function()
local frame = vgui.Create('DFrame')
frame:SetSize(400, 300)
frame:Center()
frame:MakePopup()
Mantle.ui.frame(frame, 'Legacy Frame', 400, 300, true, true)
local scroll = vgui.Create('DScrollPanel', frame)
scroll:Dock(FILL)
Mantle.ui.sp(scroll)
-- Тест кнопок с разными параметрами
local btn1 = vgui.Create('DButton', scroll)
btn1:Dock(TOP)
btn1:DockMargin(10, 10, 10, 0)
btn1:SetText('Обычная кнопка')
Mantle.ui.btn(btn1)
local btn2 = vgui.Create('DButton', scroll)
btn2:Dock(TOP)
btn2:DockMargin(10, 10, 10, 0)
btn2:SetText('Кнопка с иконкой')
Mantle.ui.btn(btn2, Material('icon16/accept.png'), 16)
local btn3 = vgui.Create('DButton', scroll)
btn3:Dock(TOP)
btn3:DockMargin(10, 10, 10, 0)
btn3:SetText('Кнопка без градиента')
Mantle.ui.btn(btn3, nil, nil, nil, nil, true)
local btn4 = vgui.Create('DButton', scroll)
btn4:Dock(TOP)
btn4:DockMargin(10, 10, 10, 0)
btn4:SetText('Кнопка без ховера')
Mantle.ui.btn(btn4, nil, nil, nil, nil, nil, nil, true)
-- Тест слайдеров
local slider1 = Mantle.ui.slidebox(scroll, 'Слайдер (0-100)', 0, 100, 'net_graph', 0)
slider1:DockMargin(10, 20, 10, 0)
local slider2 = Mantle.ui.slidebox(scroll, 'Слайдер (0-1)', 0, 1, 'cl_drawhud', 2)
slider2:DockMargin(10, 20, 10, 0)
-- Тест полей ввода
local entry1, entry_bg1 = Mantle.ui.desc_entry(scroll, 'Поле с заголовком', 'Введите текст...')
entry_bg1:DockMargin(10, 20, 10, 0)
local entry2, entry_bg2 = Mantle.ui.desc_entry(scroll, nil, 'Поле без заголовка')
entry_bg2:DockMargin(10, 20, 10, 0)
-- Тест чекбоксов
local checkbox1, checkbox_btn1 = Mantle.ui.checkbox(scroll, 'Чекбокс с ConVar', 'cl_drawhud')
checkbox1:DockMargin(10, 20, 10, 0)
local checkbox2, checkbox_btn2 = Mantle.ui.checkbox(scroll, 'Чекбокс без ConVar')
checkbox2:DockMargin(10, 20, 10, 0)
-- Тест вкладок
local panelTabs = vgui.Create('DPanel', scroll)
panelTabs:Dock(TOP)
panelTabs:SetTall(250)
panelTabs.Paint = nil
local tabs = Mantle.ui.panel_tabs(panelTabs)
tabs:DockMargin(10, 20, 10, 0)
-- Добавляем вкладки с разными стилями
local tab1 = vgui.Create('DPanel')
tab1.Paint = function(_, w, h)
draw.SimpleText('Вкладка 1', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
tabs:AddTab('Вкладка 1', tab1, 'icon16/page_white.png')
local tab2 = vgui.Create('DPanel')
tab2.Paint = function(_, w, h)
draw.SimpleText('Вкладка 2', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
tabs:AddTab('Вкладка 2', tab2, 'icon16/page_white_edit.png', Color(100, 200, 100))
local tab3 = vgui.Create('DPanel')
tab3.Paint = function(_, w, h)
draw.SimpleText('Вкладка 3', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
tabs:AddTab('Вкладка 3', tab3, 'icon16/page_white_gear.png', nil, Color(200, 100, 100))
tabs:ActiveTab('Вкладка 1')
end
CreateCategory('Legacy Frame (не стоит использовать)', {
{'Mantle.ui.frame(object frame, string title, int w, int h, bool cls_btn, bool open_anim)', 'Оформление стандартное окна стилем Mantle'},
{'Mantle.ui.sp(object scroll)', 'Оформление панели прокрутки элементов'},
{'Mantle.ui.btn(object btn, mat icon, int icon_size, color col, int rad, bool off_grad, color hov, bool off_hov)', 'Оформление кнопки'},
{'Mantle.ui.slidebox(object parent, string label, int min_value, int max_value, string convar, int decimals)', 'Создание слайдера на родительном элементе'},
{'Mantle.ui.desc_entry(object parent, string title, string placeholder, bool off_title)', 'Создание поля ввода'},
{'Mantle.ui.checkbox(object parent, string text, string convar)', 'Создание чекбокса'},
{'Mantle.ui.panel_tabs(object parent)', 'Создание панели с вкладками. В дальнейшем использовать :AddTab() и :ActiveTab() для настройки'}
}, panel, btnFrame, true)
return panel
end
tabs:AddTab('Legacy UI', CreateLegacyTest(), Material('icon16/exclamation.png'))
local function CreateSettings()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Настройки', 'Глобальные настройки Mantle: темы, эффекты и глубины элементов.', Material('icon16/cog.png'), panel)
local menuWide = menuMantle:GetWide()
local checkboxDepth = vgui.Create('MantleCheckBox', panel)
checkboxDepth:Dock(TOP)
checkboxDepth:SetTxt('Глубины элементов')
checkboxDepth:SetConvar('mantle_depth_ui')
local checkboxBlur = vgui.Create('MantleCheckBox', panel)
checkboxBlur:Dock(TOP)
checkboxBlur:DockMargin(0, 6, 0, 0)
checkboxBlur:SetTxt('Размытие фона')
checkboxBlur:SetConvar('mantle_blur')
local categoryTheme = vgui.Create('MantleCategory', panel)
categoryTheme:Dock(TOP)
categoryTheme:DockMargin(0, 6, 0, 0)
categoryTheme:SetText('Изменение цветовой темы')
categoryTheme:SetActive(true)
local comboboxTheme = vgui.Create('MantleComboBox')
comboboxTheme:Dock(TOP)
comboboxTheme:SetPlaceholder('Выберите тему интерфейса')
comboboxTheme:AddChoice('Тёмная (dark)', 'dark')
comboboxTheme:AddChoice('Тёмная монотонная (dark_mono)', 'dark_mono')
comboboxTheme:AddChoice('Светлая (light)', 'light')
comboboxTheme:AddChoice('Синяя (blue)', 'blue')
comboboxTheme:AddChoice('Красная (red)', 'red')
comboboxTheme:AddChoice('Зелёная (green)', 'green')
comboboxTheme:AddChoice('Оранжевая (orange)', 'orange')
comboboxTheme:AddChoice('Фиолетовый (purple)', 'purple')
comboboxTheme:AddChoice('Кофейная (coffee)', 'coffee')
comboboxTheme:AddChoice('Ледяная (ice)', 'ice')
comboboxTheme:AddChoice('Винная (wine)', 'wine')
comboboxTheme:AddChoice('Фиалковая (violet)', 'violet')
comboboxTheme:AddChoice('Моховая (moss)', 'moss')
comboboxTheme:AddChoice('Коралловая (coral)', 'coral')
comboboxTheme.OnSelect = function(_, _, data)
RunConsoleCommand('mantle_theme', data)
end
categoryTheme:AddItem(comboboxTheme)
local listThemeColors = vgui.Create('DIconLayout')
listThemeColors:Dock(TOP)
listThemeColors:DockMargin(6, 8, 6, 0)
listThemeColors:SetTall(164)
listThemeColors:SetSpaceX(8)
listThemeColors:SetSpaceY(8)
categoryTheme:AddItem(listThemeColors)
for colId, _ in pairs(Mantle.color) do
local panCol = vgui.Create('DPanel', listThemeColors)
panCol:SetSize(80, 80)
panCol.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color[colId])
:Shape(RNDX.SHAPE_IOS)
:Draw()
draw.SimpleText(colId, 'Fated.12', w * 0.5, h * 0.5, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
return panel
end
tabs:AddTab('Настройки', CreateSettings(), Material('icon16/cog.png'))
end
concommand.Add('mantle_menu', CreateMenu)

View File

@@ -0,0 +1,106 @@
CreateClientConVar('mantle_depth_ui', 1, true, false)
CreateClientConVar('mantle_theme', 'dark', true, false)
CreateClientConVar('mantle_blur', 1, true, false)
Mantle.ui = {
convar = {
depth_ui = GetConVar('mantle_depth_ui'):GetBool(),
theme = GetConVar('mantle_theme'):GetString(),
blur = GetConVar('mantle_blur'):GetBool()
}
}
local themeMap = {
dark = Mantle.color_dark,
dark_mono = Mantle.color_dark_mono,
graphite = Mantle.color_graphite,
light = Mantle.color_light,
blue = Mantle.color_blue,
red = Mantle.color_red,
green = Mantle.color_green,
orange = Mantle.color_orange,
purple = Mantle.color_purple,
coffee = Mantle.color_coffee,
ice = Mantle.color_ice,
wine = Mantle.color_wine,
violet = Mantle.color_violet,
moss = Mantle.color_moss,
coral = Mantle.color_coral
}
local function isColor(v)
return type(v) == 'table' and type(v.r) == 'number'
end
local transition = {
active = false,
to = nil,
progress = 0,
speed = 3,
colorBlend = 8
}
local function startThemeTransition(name)
transition.to = table.Copy(themeMap[name] or Mantle.color_dark)
transition.active = true
transition.progress = 0
if !hook.GetTable().MantleThemeTransition then
hook.Add('Think', 'MantleThemeTransition', function()
if !transition.active then return end
local dt = FrameTime()
transition.progress = Mantle.func.approachExp(transition.progress, 1, transition.speed, dt)
local eased = Mantle.func.easeOutCubic(transition.progress)
local to = transition.to
if !to then
transition.active = false
hook.Remove('Think', 'MantleThemeTransition')
return
end
for k, v in pairs(to) do
if isColor(v) then
Mantle.color[k] = Mantle.func.LerpColor(transition.colorBlend, Mantle.color[k] or v, v)
elseif type(v) == 'table' and #v > 0 then
Mantle.color[k] = Mantle.color[k] or {}
for i = 1, #v do
local vi = v[i]
if isColor(vi) then
Mantle.color[k][i] = Mantle.func.LerpColor(transition.colorBlend, (Mantle.color[k] and Mantle.color[k][i]) or vi, vi)
else
Mantle.color[k][i] = vi
end
end
end
end
if transition.progress >= 0.999 then
Mantle.color = table.Copy(transition.to)
transition.active = false
hook.Remove('Think', 'MantleThemeTransition')
end
end)
end
end
local function ApplyInitialTheme()
local theme = Mantle.ui.convar.theme
Mantle.color = table.Copy(themeMap[theme] or Mantle.color_dark)
end
ApplyInitialTheme()
cvars.AddChangeCallback('mantle_depth_ui', function(_, _, newValue)
Mantle.ui.convar.depth_ui = newValue == '1'
end)
cvars.AddChangeCallback('mantle_theme', function(_, _, newValue)
Mantle.ui.convar.theme = newValue
startThemeTransition(newValue)
end)
cvars.AddChangeCallback('mantle_blur', function(_, _, newValue)
Mantle.ui.convar.blur = newValue == '1'
end)

View File

@@ -0,0 +1,191 @@
local PANEL = {}
function PANEL:Init()
self._activeShadowTimer = 0
self._activeShadowMinTime = 0.03 -- минимальная длительность (сек)
self._activeShadowLerp = 0
self.hover_status = 0
self.bool_hover = true
self.font = 'Fated.18'
self.radius = 16
self.icon = ''
self.icon_size = 16
self.text = Mantle.lang.get('mantle', 'btn_default')
self.col = Mantle.color.button
self.col_hov = Mantle.color.button_hovered
self.bool_gradient = true
self.click_alpha = 0
self.click_x = 0
self.click_y = 0
self.ripple_speed = 4
self.enable_ripple = false
self.ripple_color = Color(255, 255, 255, 30)
--[[
TODO: тень, которая не вылезает за окно при прокрутке
]]--
-- local parent = self:GetParent()
-- local grandParent = IsValid(parent:GetParent()) and parent:GetParent() or parent
-- self.clipParent = IsValid(parent) and grandParent or nil
self:SetText('')
end
function PANEL:SetHover(is_hover)
self.bool_hover = is_hover
end
function PANEL:SetFont(font)
self.font = font
end
function PANEL:SetRadius(rad)
self.radius = rad
end
function PANEL:SetIcon(icon, icon_size)
self.icon = type(icon) == 'IMaterial' and icon or Material(icon)
self.icon_size = icon_size
end
function PANEL:SetTxt(text)
self.text = text
end
function PANEL:SetColor(col)
self.col = col
end
function PANEL:SetColorHover(col)
self.col_hov = col
end
function PANEL:SetGradient(is_grad)
self.bool_gradient = is_grad
end
function PANEL:SetRipple(enable)
self.enable_ripple = enable
end
function PANEL:OnMousePressed(mousecode)
self.BaseClass.OnMousePressed(self, mousecode)
if self.enable_ripple and mousecode == MOUSE_LEFT then
self.click_alpha = 1
self.click_x, self.click_y = self:CursorPos()
end
end
local math_clamp = math.Clamp
local btnFlags = RNDX.SHAPE_IOS
function PANEL:Paint(w, h)
if self:IsHovered() then
self.hover_status = math_clamp(self.hover_status + 4 * FrameTime(), 0, 1)
else
self.hover_status = math_clamp(self.hover_status - 8 * FrameTime(), 0, 1)
end
-- Минимальный порог длительности для активной тени
local isActive = (self:IsDown() or self.Depressed) and self.hover_status > 0.8
if isActive then
self._activeShadowTimer = SysTime() + self._activeShadowMinTime
end
local showActiveShadow = isActive or (self._activeShadowTimer > SysTime())
-- Плавная анимация дополнительной тени при зажатии
local activeTarget = showActiveShadow and 10 or 0
local activeSpeed = (activeTarget > 0) and 7 or 3 -- скорость появления/затухания
self._activeShadowLerp = Lerp(FrameTime() * activeSpeed, self._activeShadowLerp, activeTarget)
-- Обычная тень
-- if Mantle.ui.convar.depth_ui then
-- RNDX().Rect(0, 0, w, h)
-- :Rad(self.radius)
-- :Color(Mantle.color.window_shadow)
-- :Shape(RNDX.SHAPE_IOS)
-- :Shadow(5, 20)
-- :Clip(self.clipParent)
-- :Draw()
-- end
-- Дополнительная тень при зажатии
if self._activeShadowLerp > 0 and Mantle.ui.convar.depth_ui then
local col = Color(self.col_hov.r, self.col_hov.g, self.col_hov.b, math.Clamp(self.col_hov.a * 1.5, 0, 255))
RNDX().Rect(0, 0, w, h)
:Rad(self.radius)
:Color(col)
:Shape(btnFlags)
:Shadow(self._activeShadowLerp * 1.5, 24)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(self.radius)
:Color(self.col)
:Shape(btnFlags)
:Draw()
if self.bool_gradient then
Mantle.func.gradient(0, 0, w, h, 1, Mantle.color.button_shadow, self.radius, btnFlags)
end
if self.bool_hover then
RNDX().Rect(0, 0, w, h)
:Rad(self.radius)
:Color(Color(self.col_hov.r, self.col_hov.g, self.col_hov.b, self.hover_status * 255))
:Shape(btnFlags)
:Draw()
end
if self.click_alpha > 0 then
self.click_alpha = math_clamp(self.click_alpha - FrameTime() * self.ripple_speed, 0, 1)
local ripple_size = (1 - self.click_alpha) * math.max(w, h) * 2
local ripple_color = Color(
self.ripple_color.r,
self.ripple_color.g,
self.ripple_color.b,
self.ripple_color.a * self.click_alpha
)
RNDX().Rect(self.click_x - ripple_size * 0.5, self.click_y - ripple_size * 0.5, ripple_size, ripple_size)
:Rad(100)
:Color(ripple_color)
:Shape(btnFlags)
:Draw()
end
if self.text != '' then
draw.SimpleText(
self.text,
self.font,
w * 0.5 + (self.icon ~= '' and self.icon_size * 0.5 + 2 or 0),
h * 0.5,
Mantle.color.text,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_CENTER
)
if self.icon != '' then
surface.SetFont(self.font)
local posX = (w - surface.GetTextSize(self.text) - self.icon_size) * 0.5 - 2
local posY = (h - self.icon_size) * 0.5
RNDX().Rect(posX, posY, self.icon_size, self.icon_size)
:Material(self.icon)
:Color(color_white)
:Shape(btnFlags)
:Draw()
end
elseif self.icon != '' then
local posX = (w - self.icon_size) * 0.5
local posY = (h - self.icon_size) * 0.5
RNDX().Rect(posX, posY, self.icon_size, self.icon_size)
:Material(self.icon)
:Color(color_white)
:Shape(btnFlags)
:Draw()
end
end
vgui.Register('MantleBtn', PANEL, 'Button')

View File

@@ -0,0 +1,130 @@
local PANEL = {}
function PANEL:Init()
self:SetTall(30)
self:DockPadding(0, 36, 0, 0)
self.name = 'Категория'
self.bool_opened = false
self.bool_header_centered = false
self.content_size = 0
self.header_color = Mantle.color.category
self.header_color_standard = self.header_color
self.header_color_opened = Mantle.color.category_opened
self._childHeights = {}
self._anim = 0
self._animTarget = 0
self._animSpeed = 12
self._animEased = 0
self.header = vgui.Create('Button', self)
self.header:SetText('')
self.header.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(self.header_color)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local posX = self.bool_header_centered and w * 0.5 or 8
local alignX = self.bool_header_centered and TEXT_ALIGN_CENTER or TEXT_ALIGN_LEFT
draw.SimpleText(self.name, 'Fated.20', posX, 4, Mantle.color.text, alignX)
self.header_color = Mantle.func.LerpColor(8, self.header_color, self.bool_opened and self.header_color_opened or self.header_color_standard)
end
self.header.DoClick = function()
self.bool_opened = !self.bool_opened
self._animTarget = self.bool_opened and 1 or 0
end
end
function PANEL:SetText(name)
self.name = name
end
function PANEL:SetCenterText(is_centered)
self.bool_header_centered = is_centered
end
local function getTopBottomMargin(pnl)
if !pnl.GetDockMargin then return 0, 0 end
local ok, l, t, r, b = pcall(function()
return pnl:GetDockMargin()
end)
if !ok or !l then return 0, 0 end
return t or 0, b or 0
end
function PANEL:AddItem(panel)
panel:SetParent(self)
local top, bottom = getTopBottomMargin(panel)
local contribution = (panel.GetTall and panel:GetTall() or 0) + top + bottom
self._childHeights[panel] = contribution
self.content_size = (self.content_size or 0) + contribution
if self.bool_opened then
self:SetTall(30 + self.content_size + 12)
end
local old = panel.OnSizeChanged
panel.OnSizeChanged = function(...)
if old then pcall(old, ...) end
if !IsValid(self) then return end
local nt, nb = getTopBottomMargin(panel)
local newContribution = (panel.GetTall and panel:GetTall() or 0) + nt + nb
local oldContribution = self._childHeights[panel] or 0
local delta = newContribution - oldContribution
if delta != 0 then
self._childHeights[panel] = newContribution
self.content_size = math.max(0, (self.content_size or 0) + delta)
end
end
return panel
end
function PANEL:SetColor(col)
self.header_color_standard = col
if !self.bool_opened then
self.header_color = self.header_color_standard
end
end
function PANEL:SetActive(is_active)
is_active = tobool(is_active)
if self.bool_opened == is_active then return end
self.bool_opened = is_active
self._animTarget = is_active and 1 or 0
self.header_color = is_active and self.header_color_opened or self.header_color_standard
end
function PANEL:PerformLayout(w, h)
self.header:SetSize(w, 30)
end
function PANEL:Think()
local ft = FrameTime()
self._anim = Mantle.func.approachExp(self._anim, self._animTarget, self._animSpeed, ft)
self._animEased = Mantle.func.easeOutCubic(self._anim)
local currentContentTall = (self.content_size or 0) * self._animEased
local padded = 12 * self._animEased
local totalTall = 30 + currentContentTall + padded
self:SetTall(math.max(30, math.floor(totalTall + 0.5)))
local alphaVal = math.floor(255 * self._animEased + 0.5)
for _, c in ipairs(self:GetChildren()) do
if IsValid(c) and c != self.header then
if c.SetAlpha then c:SetAlpha(alphaVal) end
end
end
end
vgui.Register('MantleCategory', PANEL, 'Panel')

View File

@@ -0,0 +1,136 @@
local PANEL = {}
function PANEL:Init()
self.text = ''
self.convar = ''
self.value = false
self:SetText('')
self:SetCursor('hand')
self:SetTall(36)
self._circle = 0
self._circleEased = 0
self._circleColor = table.Copy(Mantle.color.gray)
self.toggle = vgui.Create('Button', self)
self.toggle:Dock(RIGHT)
self.toggle:SetWide(48)
self.toggle:DockMargin(0, 0, 14, 0)
self.toggle:SetText('')
self.toggle:SetCursor('hand')
self.toggle.Paint = nil
self.toggle.DoClick = function()
if self.convar ~= '' then
LocalPlayer():ConCommand(self.convar .. ' ' .. (self.value and 0 or 1))
end
self:SetValue(not self.value)
self:OnChange(self.value)
Mantle.func.sound()
end
end
function PANEL:OnMousePressed(mcode)
if mcode == MOUSE_LEFT then
self.toggle:DoClick()
end
end
function PANEL:SetTxt(text)
self.text = text
end
function PANEL:SetValue(val)
self.value = tobool(val)
end
function PANEL:GetBool()
return self.value
end
function PANEL:SetConvar(convar)
local c = GetConVar(convar)
if c then self.value = c:GetBool() end
self.convar = convar
end
function PANEL:OnChange(new_value)
end
function PANEL:Paint(w, h)
if Mantle.ui and Mantle.ui.convar and Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Rad(12)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(6, 22)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(12)
:Color(Mantle.color.focus_panel)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local textX = 14
draw.SimpleText(self.text, 'Fated.18', textX, h * 0.5, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
function PANEL:PaintOver(w, h)
local tw, th = self.toggle:GetWide(), self.toggle:GetTall()
local tx, ty = self.toggle:GetPos()
local ft = FrameTime()
local target = self.value and 1 or 0
local circleSpeed = 8
self._circle = Mantle.func.approachExp(self._circle, target, circleSpeed, ft)
if math.abs(self._circle - target) < 0.001 then self._circle = target end
self._circleEased = Mantle.func.easeInOutCubic(self._circle)
local trackW = tw - 10
local trackH = 18
local trackX = tx + (tw - trackW) / 2
local trackY = ty + (th - trackH) / 2
RNDX().Rect(trackX, trackY + 1, trackW, trackH - 2)
:Rad(trackH / 2)
:Color(Mantle.color.toggle)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local circleSize = 20
local pad = 0
local textMargin = 14
local x0_base = trackX + pad - (circleSize * 0.5) + 0.5
local x1 = trackX + trackW - pad - (circleSize * 0.5) - 0.5
local x0_align = textMargin - (circleSize * 0.5)
local x0 = math.max(x0_base, x0_align)
local circleXPrec = x0 + (x1 - x0) * self._circleEased
local circleCenterX = circleXPrec + circleSize * 0.5
local circleCenterY = trackY + trackH * 0.5
local baseCircle = self.value and Mantle.color.theme or Mantle.color.gray
local circleCol = table.Copy(baseCircle)
circleCol.a = 255
self._circleColor = Mantle.func.LerpColor(12, self._circleColor, circleCol)
RNDX().Circle(circleCenterX, circleCenterY, circleSize)
:Color(self._circleColor)
:Draw()
RNDX().Circle(circleCenterX, circleCenterY + 2, circleSize * 1.05)
:Color(Color(0, 0, 0, 30))
:Draw()
end
function PANEL:PerformLayout(w, h)
self.toggle:SetWide(48)
self.toggle:DockMargin(0, 0, 14, 0)
end
vgui.Register('MantleCheckBox', PANEL, 'Panel')

View File

@@ -0,0 +1,222 @@
local color_close = Color(210, 65, 65)
local color_accept = Color(44, 124, 62)
local color_outline = Color(30, 30, 30)
local color_target = Color(255, 255, 255, 200)
function Mantle.ui.color_picker(func, color_standart)
if IsValid(Mantle.ui.menu_color_picker) then
Mantle.ui.menu_color_picker:Remove()
end
local selected_color = color_standart or Color(255, 255, 255)
local hue = 0
local saturation = 1
local value = 1
if color_standart then
local r, g, b = color_standart.r / 255, color_standart.g / 255, color_standart.b / 255
local h, s, v = ColorToHSV(Color(r * 255, g * 255, b * 255))
hue = h
saturation = s
value = v
end
Mantle.ui.menu_color_picker = vgui.Create('MantleFrame')
Mantle.ui.menu_color_picker:SetSize(300, 378)
Mantle.ui.menu_color_picker:Center()
Mantle.ui.menu_color_picker:MakePopup()
Mantle.ui.menu_color_picker:SetTitle('')
Mantle.ui.menu_color_picker:SetCenterTitle(Mantle.lang.get('mantle', 'color_title'))
local container = vgui.Create('Panel', Mantle.ui.menu_color_picker)
container:Dock(FILL)
container:DockMargin(10, 10, 10, 10)
container.Paint = nil
local preview = vgui.Create('Panel', container)
preview:Dock(TOP)
preview:SetTall(40)
preview:DockMargin(0, 0, 0, 10)
preview.Paint = function(self, w, h)
if Mantle.ui.convar.depth_ui then
RNDX().Rect(2, 2, w - 4, h - 4)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
RNDX.Draw(16, 2, 2, w - 4, h - 4, selected_color, RNDX.SHAPE_IOS)
end
local colorField = vgui.Create('Panel', container)
colorField:Dock(TOP)
colorField:SetTall(200)
colorField:DockMargin(0, 0, 0, 10)
local colorCursor = { x = 0, y = 0 }
local isDraggingColor = false
colorField.OnMousePressed = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingColor = true
self:OnCursorMoved(self:CursorPos())
Mantle.func.sound()
end
end
colorField.OnMouseReleased = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingColor = false
end
end
colorField.OnCursorMoved = function(self, x, y)
if isDraggingColor then
local w, h = self:GetSize()
x = math.Clamp(x, 0, w)
y = math.Clamp(y, 0, h)
colorCursor.x = x
colorCursor.y = y
saturation = x / w
value = 1 - (y / h)
selected_color = HSVToColor(hue, saturation, value)
end
end
colorField.Paint = function(self, w, h)
local segments = 80
local segmentSize = w / segments
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
for x = 0, segments do
for y = 0, segments do
local s = x / segments
local v = 1 - (y / segments)
local segX = x * segmentSize
local segY = y * segmentSize
surface.SetDrawColor(HSVToColor(hue, s, v))
surface.DrawRect(segX, segY, segmentSize + 1, segmentSize + 1)
end
end
RNDX().Circle(colorCursor.x, colorCursor.y, 12)
:Outline(2)
:Color(color_target)
:Draw()
end
local hueSlider = vgui.Create('Panel', container)
hueSlider:Dock(TOP)
hueSlider:SetTall(20)
hueSlider:DockMargin(0, 0, 0, 10)
local huePos = 0
local isDraggingHue = false
hueSlider.OnMousePressed = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingHue = true
self:OnCursorMoved(self:CursorPos())
Mantle.func.sound()
end
end
hueSlider.OnMouseReleased = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingHue = false
end
end
hueSlider.OnCursorMoved = function(self, x, y)
if isDraggingHue then
local w = self:GetWide()
x = math.Clamp(x, 0, w)
huePos = x
hue = (x / w) * 360
selected_color = HSVToColor(hue, saturation, value)
end
end
hueSlider.Paint = function(self, w, h)
local segments = 100
local segmentWidth = w / segments
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
for i = 0, segments - 1 do
local hueVal = (i / segments) * 360
local x = i * segmentWidth
surface.SetDrawColor(HSVToColor(hueVal, 1, 1))
surface.DrawRect(x, 1, segmentWidth + 1, h - 2)
end
RNDX().Rect(huePos - 2, 0, 4, h)
:Color(color_target)
:Draw()
end
local rgbContainer = vgui.Create('Panel', container)
rgbContainer:Dock(TOP)
rgbContainer:SetTall(60)
rgbContainer:DockMargin(0, 0, 0, 10)
rgbContainer.Paint = nil
local btnContainer = vgui.Create('Panel', container)
btnContainer:Dock(BOTTOM)
btnContainer:SetTall(30)
btnContainer.Paint = nil
local btnClose = vgui.Create('MantleBtn', btnContainer)
btnClose:Dock(LEFT)
btnClose:SetWide(90)
btnClose:SetTxt(Mantle.lang.get('mantle', 'color_cancel'))
btnClose:SetColorHover(color_close)
btnClose.DoClick = function()
Mantle.ui.menu_color_picker:Remove()
Mantle.func.sound()
end
local btnSelect = vgui.Create('MantleBtn', btnContainer)
btnSelect:Dock(RIGHT)
btnSelect:SetWide(90)
btnSelect:SetTxt(Mantle.lang.get('mantle', 'color_select'))
btnSelect:SetColorHover(color_accept)
btnSelect.DoClick = function()
Mantle.func.sound()
func(selected_color)
Mantle.ui.menu_color_picker:Remove()
end
timer.Simple(0, function()
if IsValid(colorField) and IsValid(hueSlider) then
colorCursor.x = saturation * colorField:GetWide()
colorCursor.y = (1 - value) * colorField:GetTall()
huePos = (hue / 360) * hueSlider:GetWide()
end
end)
timer.Simple(0.1, function()
Mantle.ui.menu_color_picker:SetAlpha(255)
end)
end

View File

@@ -0,0 +1,267 @@
local PANEL = {}
function PANEL:Init()
self.choices = {}
self.selected = nil
self.opened = false
self:SetTall(26)
self:SetText('')
self.font = 'Fated.18'
self.hoverAnim = 0
self.OnSelect = function(_, _, _) end
self.btn = vgui.Create('DButton', self)
self.btn:Dock(FILL)
self.btn:SetText('')
self.btn:SetCursor('hand')
self.btn.Paint = function(_, w, h)
if self.btn:IsHovered() then
self.hoverAnim = math.Clamp(self.hoverAnim + FrameTime() * 4, 0, 1)
else
self.hoverAnim = math.Clamp(self.hoverAnim - FrameTime() * 8, 0, 1)
end
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
RNDX.Draw(16, 0, 0, w, h, Mantle.color.focus_panel, RNDX.SHAPE_IOS)
if self.hoverAnim > 0 then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.button_hovered.r, Mantle.color.button_hovered.g, Mantle.color.button_hovered.b, self.hoverAnim * 255))
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
draw.SimpleText(
self.selected or self.placeholder or 'Выберите...',
self.font,
12,
h * 0.5,
Mantle.color.text,
TEXT_ALIGN_LEFT,
TEXT_ALIGN_CENTER
)
local arrowSize = 6
local arrowX = w - 16
local arrowY = h / 2
local arrowColor = ColorAlpha(Mantle.color.text, 180 + self.hoverAnim * 75)
surface.SetDrawColor(arrowColor)
draw.NoTexture()
if !self.opened then
surface.DrawPoly({
{x = arrowX - arrowSize, y = arrowY - arrowSize/2},
{x = arrowX + arrowSize, y = arrowY - arrowSize/2},
{x = arrowX, y = arrowY + arrowSize/2}
})
end
end
self.btn.DoClick = function()
if self.opened then
self:CloseMenu()
else
self:OpenMenu()
Mantle.func.sound()
end
end
end
function PANEL:AddChoice(text, data)
table.insert(self.choices, {text = text, data = data})
end
function PANEL:SetValue(val)
self.selected = val
end
function PANEL:GetValue()
return self.selected
end
function PANEL:SetPlaceholder(text)
self.placeholder = text
end
function PANEL:OpenMenu()
if IsValid(self.menu) then
self.menu:Remove()
end
local menuPadding = 6
local itemHeight = 26
local menuHeight = (#self.choices * (itemHeight + 2)) + (menuPadding * 2) + 2
local x, y = self:LocalToScreen(0, self:GetTall())
self.menu = vgui.Create('DPanel')
self.menu:SetSize(self:GetWide(), menuHeight)
if y + menuHeight > ScrH() - 10 then
y = y - menuHeight - self:GetTall()
end
self.menu:SetPos(x, y)
self.menu:SetDrawOnTop(true)
self.menu:MakePopup()
self.menu:SetKeyboardInputEnabled(false)
self.menu:DockPadding(menuPadding, menuPadding, menuPadding, menuPadding)
self.menu._anim = 0
self.menu._animTarget = 1
self.menu._animSpeed = 18
self.menu._animEased = 0
self.menu._closing = false
self.menu._disableBlur = false
self.menu:SetAlpha(0)
self.menu.Paint = function(s, w, h)
local aMul = s._animEased or ((s:GetAlpha() or 255) / 255)
local blurMul
if s._closing or s._disableBlur or s._animTarget == 0 then
blurMul = 0
else
local fadeStart = 0.3
blurMul = math.Clamp((aMul - fadeStart) / (1 - fadeStart), 0, 1)
end
local shadowSpread = math.max(0, math.floor(10 * blurMul))
local shadowIntensity = math.max(0, math.floor(16 * blurMul))
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(shadowSpread, shadowIntensity)
:Draw()
if not s._disableBlur then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Shape(RNDX.SHAPE_IOS)
:Blur(blurMul)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.background_panelpopup)
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.background_panelpopup)
:Shape(RNDX.SHAPE_IOS)
:Outline(1)
:Draw()
end
surface.SetFont(self.font)
for i, choice in ipairs(self.choices) do
local option = vgui.Create('DButton', self.menu)
option:SetText('')
option:Dock(TOP)
option:DockMargin(2, 2, 2, 0)
option:SetTall(itemHeight)
option:SetCursor('hand')
option.Paint = function(s, w, h)
if s:IsHovered() then
RNDX.Draw(16, 0, 0, w, h, Mantle.color.hover, RNDX.SHAPE_IOS)
end
draw.SimpleText(
choice.text,
'Fated.18',
14,
h * 0.5,
Mantle.color.text,
TEXT_ALIGN_LEFT,
TEXT_ALIGN_CENTER
)
if self.selected == choice.text then
RNDX.Draw(0, 4, h * 0.5 - 1, 4, 2, Mantle.color.theme)
end
end
option.DoClick = function()
self.selected = choice.text
if self.menu and IsValid(self.menu) then
self.menu._closing = true
self.menu._animTarget = 0
self.menu._disableBlur = true
end
if self.OnSelect then
self.OnSelect(i, choice.text, choice.data)
end
Mantle.func.sound()
end
end
self.opened = true
local oldMouseDown = false
self.menu.Think = function(s)
local ft = FrameTime()
s._anim = Mantle.func.approachExp(s._anim or 0, s._animTarget or 1, s._animSpeed or 18, ft)
s._animEased = s._anim
s:SetAlpha(math.floor(255 * s._animEased + 0.5))
if s._targetX and s._targetY then
local offsetY = 6 * (1 - s._animEased)
s:SetPos(s._targetX, s._targetY + offsetY)
end
if s._closing and s._animEased <= 0.005 then
s:Remove()
return
end
if not s:IsVisible() then return end
local mouseDown = input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT)
if mouseDown and not oldMouseDown then
local mx, my = gui.MousePos()
local x, y = s:LocalToScreen(0, 0)
if not (mx >= x and mx <= x + s:GetWide() and my >= y and my <= y + s:GetTall()) then
s._closing = true
s._animTarget = 0
s._disableBlur = true
end
end
oldMouseDown = mouseDown
end
self.menu.OnRemove = function()
if IsValid(self) then
self.opened = false
end
end
Mantle.func.ClampMenuPosition(self.menu)
self.menu._targetX, self.menu._targetY = self.menu:GetPos()
if not self.menu._initPosSet then
self.menu:SetPos(self.menu._targetX, self.menu._targetY + 6)
self.menu._initPosSet = true
end
end
function PANEL:CloseMenu()
if IsValid(self.menu) then
if not self.menu._closing then
self.menu._closing = true
self.menu._animTarget = 0
self.menu._disableBlur = true
end
end
self.opened = false
end
function PANEL:OnRemove()
self:CloseMenu()
end
vgui.Register('MantleComboBox', PANEL, 'Panel')

View File

@@ -0,0 +1,335 @@
local PANEL = {}
function PANEL:Init()
self.Items = {}
self:SetSize(160, 0)
self:DockPadding(4, 5, 4, 5)
self:MakePopup()
self:SetKeyboardInputEnabled(false)
self:SetDrawOnTop(true)
self.MaxTextWidth = 0
self._anim = 0
self._animTarget = 1
self._animSpeed = 18
self._animEased = 0
self._initPosSet = false
self._closing = false
self._disableBlur = false
self._openTime = CurTime()
self:SetAlpha(0)
self.Think = function()
local ft = FrameTime()
if not self._initPosSet then
local tx, ty = self:GetPos()
Mantle.func.ClampMenuPosition(self)
self._targetX, self._targetY = self:GetPos()
self:SetPos(self._targetX, self._targetY + 6)
self._initPosSet = true
end
if CurTime() - self._openTime >= 0.08 then
if input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT) then
if not self:IsChildHovered() then
self:CloseMenu()
end
end
end
self._anim = Mantle.func.approachExp(self._anim, self._animTarget, self._animSpeed, ft)
self._animEased = self._anim
local a = math.floor(255 * self._animEased + 0.5)
self:SetAlpha(a)
if self._targetX and self._targetY then
local offsetY = 6 * (1 - self._animEased)
self:SetPos(self._targetX, self._targetY + offsetY)
end
if self._closing and self._animEased <= 0.005 then
return self:Remove()
end
end
end
function PANEL:Paint(w, h)
local aMul = (self._animEased ~= nil) and self._animEased or ((self:GetAlpha() or 255) / 255)
local blurMul
if self._closing or self._disableBlur or self._animTarget == 0 then
blurMul = 0
else
local fadeStart = 0.3
blurMul = math.Clamp((aMul - fadeStart) / (1 - fadeStart), 0, 1)
end
local shadowSpread = math.max(0, math.floor(10 * blurMul))
local shadowIntensity = math.max(0, math.floor(16 * blurMul))
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.window_shadow.r, Mantle.color.window_shadow.g, Mantle.color.window_shadow.b, math.floor(100 * aMul)))
:Shape(RNDX.SHAPE_IOS)
:Shadow(shadowSpread, shadowIntensity)
:Draw()
if !self._disableBlur then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Shape(RNDX.SHAPE_IOS)
:Blur(blurMul)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math.floor(150 * aMul)))
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math.floor(150 * aMul)))
:Shape(RNDX.SHAPE_IOS)
:Outline(1)
:Draw()
end
function PANEL:AddOption(text, func, icon, optData)
surface.SetFont('Fated.18')
local textW = select(1, surface.GetTextSize(text))
self.MaxTextWidth = math.max(self.MaxTextWidth or 0, textW)
local option = vgui.Create('DButton', self)
option:SetText('')
option:Dock(TOP)
option:DockMargin(2, 2, 2, 0)
option:SetTall(26)
option.sumTall = 28
option.Icon = icon
option.Text = text
option._submenu = nil
option._submenu_open = false
option.DoClick = function()
if option._submenu then
if option._submenu_open then
option:CloseSubMenu()
else
option:OpenSubMenu()
end
return
end
if func then func() end
Mantle.func.sound()
local function closeAllMenus(panel)
while IsValid(panel) do
if panel.GetName and panel:GetName() == 'MantleDermaMenu' then
local parent = panel:GetParent()
panel:CloseMenu()
panel = parent
else
panel = panel:GetParent()
end
end
end
closeAllMenus(option)
end
function option:AddSubMenu()
if IsValid(option._submenu) then option._submenu:Remove() end
local submenu = vgui.Create('MantleDermaMenu')
submenu:SetDrawOnTop(true)
submenu:SetParent(self:GetParent())
submenu:SetVisible(false)
option._submenu = submenu
option._submenu_open = false
option.OnRemove = function()
if IsValid(submenu) then submenu:Remove() end
end
function option:OpenSubMenu()
if not IsValid(submenu) then return end
for _, sibling in ipairs(self:GetParent().Items or {}) do
if sibling ~= self and sibling.CloseSubMenu then sibling:CloseSubMenu() end
end
local x, y = self:LocalToScreen(self:GetWide(), 0)
submenu:SetPos(x, y)
Mantle.func.ClampMenuPosition(submenu)
submenu._targetX, submenu._targetY = submenu:GetPos()
submenu:SetVisible(true)
submenu:MakePopup()
submenu:SetKeyboardInputEnabled(false)
option._submenu_open = true
end
function option:CloseSubMenu()
if IsValid(submenu) then submenu:SetVisible(false) end
option._submenu_open = false
if submenu.Items then
for _, item in ipairs(submenu.Items) do
if item.CloseSubMenu then item:CloseSubMenu() end
end
end
end
local function isAnySubmenuHovered(opt)
if not IsValid(opt) then return false end
if opt:IsHovered() then return true end
if opt._submenu and IsValid(opt._submenu) and opt._submenu:IsVisible() then
if isAnySubmenuHovered(opt._submenu) then return true end
for _, item in ipairs(opt._submenu.Items or {}) do
if isAnySubmenuHovered(item) then return true end
end
end
return false
end
option.OnCursorExited = function(pnl)
timer.Simple(0.15, function()
if not isAnySubmenuHovered(pnl) then
if IsValid(pnl) then
pnl:CloseSubMenu()
end
end
end)
end
submenu.OnCursorExited = function(pnl)
timer.Simple(0.15, function()
if not isAnySubmenuHovered(option) then
if IsValid(pnl) then
option:CloseSubMenu()
end
end
end)
end
return submenu
end
option.AddSubMenu = option.AddSubMenu
if optData then
for k, v in pairs(optData) do
option[k] = v
end
end
local iconMat
if option.Icon then
iconMat = type(option.Icon) == 'IMaterial' and option.Icon or Material(option.Icon)
end
option.Paint = function(pnl, w, h)
w = w or pnl:GetWide()
h = h or pnl:GetTall()
if pnl:IsHovered() then
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
RNDX.Draw(16, 0, 0, w, h, Mantle.color.hover, RNDX.SHAPE_IOS)
if pnl._submenu and not pnl._submenu_open then
pnl:OpenSubMenu()
end
end
if iconMat then
local iconSize = 16
RNDX.DrawMaterial(0, 10, (h - iconSize) / 2, iconSize, iconSize, color_white, iconMat)
end
draw.SimpleText(pnl.Text, 'Fated.18', pnl.Icon and 32 or 14, h * 0.5, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
table.insert(self.Items, option)
self:UpdateSize()
return option
end
function PANEL:AddSpacer()
local spacer = vgui.Create('DPanel', self)
spacer:Dock(TOP)
spacer:DockMargin(8, 6, 8, 6)
spacer:SetTall(1)
spacer.sumTall = 13
spacer.Paint = function(_, w, h)
RNDX.Draw(0, 0, 0, w, h, Mantle.color.focus_panel)
end
table.insert(self.Items, spacer)
self:UpdateSize()
return spacer
end
function PANEL:UpdateSize()
local height = 12
for _, item in ipairs(self.Items) do
if IsValid(item) then
height = height + (item.sumTall or item:GetTall())
end
end
local maxWidth = math.max(160, self.MaxTextWidth + 56)
self:SetSize(maxWidth, math.min(height, ScrH() * 0.8))
if not self._targetX or not self._targetY then
Mantle.func.ClampMenuPosition(self)
self._targetX, self._targetY = self:GetPos()
if not self._initPosSet then
self:SetPos(self._targetX, self._targetY + 6)
end
else
Mantle.func.ClampMenuPosition(self)
self._targetX, self._targetY = self:GetPos()
end
end
function PANEL:Open()
-- Clear
end
function PANEL:CloseMenu()
if self._closing then return end
self._closing = true
self._disableBlur = true
self._animTarget = 0
end
function PANEL:GetDeleteSelf()
return true
end
vgui.Register('MantleDermaMenu', PANEL, 'DPanel')
function Mantle.ui.derma_menu()
if IsValid(Mantle.ui.menu_derma_menu) then
Mantle.ui.menu_derma_menu:CloseMenu()
end
local mouseX, mouseY = input.GetCursorPos()
local m = vgui.Create('MantleDermaMenu')
m:SetPos(mouseX, mouseY)
Mantle.func.ClampMenuPosition(m)
m._targetX, m._targetY = m:GetPos()
Mantle.ui.menu_derma_menu = m
return m
end

View File

@@ -0,0 +1,98 @@
local PANEL = {}
function PANEL:Init()
self.title = nil
self.placeholder = Mantle.lang.get('mantle', 'entry_default_placeholder')
self:SetTall(26)
self.action = function() end
local font = 'Fated.18'
self.textEntry = vgui.Create('DTextEntry', self)
self.textEntry:Dock(FILL)
self.textEntry:SetText('')
self.textEntry.OnCloseFocus = function()
self.action(self:GetValue())
end
self._text_offset = 0
self._shadowLerp = 5
self.textEntry.Paint = nil
self.textEntry.PaintOver = function(s, w, h)
local ft = FrameTime()
if Mantle.ui.convar.depth_ui then
local target = s:IsEditing() and 10 or 5
self._shadowLerp = Mantle.func.approachExp(self._shadowLerp, target, 12, ft)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(self._shadowLerp, 20)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.focus_panel)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local value = self:GetValue() or ''
surface.SetFont(font)
local padding = 6
local available_w = w - padding * 2
local caret = #value
local before_caret = string.sub(value, 1, caret)
local caret_x = surface.GetTextSize(before_caret)
local text_w = surface.GetTextSize(value)
local desired_offset = 0
if caret_x > available_w then
desired_offset = caret_x - available_w
end
if text_w - desired_offset < available_w then
desired_offset = math.max(0, text_w - available_w)
end
self._text_offset = Mantle.func.approachExp(self._text_offset or 0, desired_offset, 24, ft)
local text = self.placeholder
local col = Mantle.color.gray
if value != '' then
text = value
col = Mantle.color.text
end
draw.SimpleText(text, font, padding - self._text_offset, h * 0.5, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
end
function PANEL:SetTitle(title)
self.title = title
self:SetTall(52)
if IsValid(self.titlePanel) then
self.titlePanel:Remove()
end
self.titlePanel = vgui.Create('DPanel', self)
self.titlePanel:Dock(TOP)
self.titlePanel:DockMargin(0, 0, 0, 6)
self.titlePanel:SetTall(18)
self.titlePanel.Paint = function(_, w, h)
draw.SimpleText(self.title, 'Fated.18', 0, 0, Mantle.color.text)
end
end
function PANEL:SetPlaceholder(placeholder)
self.placeholder = placeholder
end
function PANEL:GetValue()
return self.textEntry:GetText()
end
vgui.Register('MantleEntry', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,194 @@
local PANEL = {}
local mat_close = Material('mantle/close_btn_new.png')
function PANEL:Init()
self.bool_alpha = true
self.bool_lite = false
self.title = Mantle.lang.get('mantle', 'frame_title')
self.center_title = ''
self:DockPadding(6, 30, 6, 6)
self.top_panel = vgui.Create('DButton', self)
self.top_panel:SetText('')
self.top_panel:SetCursor('sizeall')
self.top_panel.Paint = nil
self.top_panel.OnMousePressed = function(s, key)
if key == MOUSE_LEFT then
self.Dragging = {gui.MouseX() - self.x, gui.MouseY() - self.y}
s:MouseCapture(true)
self:SetAlpha(200)
end
end
self.top_panel.OnMouseReleased = function(s, key)
if key == MOUSE_LEFT then
self.Dragging = nil
s:MouseCapture(false)
self:SetAlpha(255)
end
end
self.top_panel.Think = function(s)
if self.Dragging then
local mouseX, mouseY = gui.MousePos()
local newPosX, newPosY = mouseX - self.Dragging[1], mouseY - self.Dragging[2]
self:SetPos(newPosX, newPosY)
end
end
self.cls = vgui.Create('Button', self)
self.cls:SetText('')
self.cls.Paint = function(_, w, h)
RNDX().Rect(2, 2, w - 4, h - 4)
:Color(Mantle.color.header_text)
:Material(mat_close)
:Draw()
end
self.cls.DoClick = function()
self:AlphaTo(0, 0.1, 0, function()
self:Remove()
end)
Mantle.func.sound()
end
self.cls.DoRightClick = function()
local DM = Mantle.ui.derma_menu()
DM:AddOption(Mantle.lang.get('mantle', 'frame_alpha'), function()
self.bool_alpha = !self.bool_alpha
end, self.bool_alpha and 'icon16/bullet_green.png' or 'icon16/bullet_red.png')
local boolInput = self:IsKeyboardInputEnabled()
DM:AddOption(Mantle.lang.get('mantle', 'frame_move_from_menu'), function()
self:SetKeyBoardInputEnabled(!boolInput)
end, !boolInput and 'icon16/bullet_green.png' or 'icon16/bullet_red.png')
DM:AddOption(Mantle.lang.get('mantle', 'frame_close_window'), function()
self:Remove()
end, 'icon16/cross.png')
end
end
function PANEL:SetAlphaBackground(is_alpha)
self.bool_alpha = is_alpha
end
function PANEL:SetTitle(title)
self.title = title
end
function PANEL:SetCenterTitle(center_title)
self.center_title = center_title
end
function PANEL:ShowAnimation()
Mantle.func.animate_appearance(self, self:GetWide(), self:GetTall(), 0.3, 0.2)
end
function PANEL:DisableCloseBtn()
self.cls:SetVisible(false)
end
function PANEL:SetDraggable(is_draggable)
self.top_panel:SetVisible(is_draggable)
end
function PANEL:LiteMode()
self.bool_lite = true
self:DockPadding(6, 6, 6, 6)
self.cls:SetZPos(2)
end
function PANEL:Notify(text, duration, col)
if IsValid(self.messagePanel) then self.messagePanel:Remove() end
duration = duration or 2
col = col or Mantle.color.theme
surface.SetFont('Fated.20')
local tw, th = surface.GetTextSize(text)
local mp = vgui.Create('DPanel', self)
mp:SetSize(tw + 16, th + 8)
mp:SetMouseInputEnabled(false)
local startY = self:GetTall() + mp:GetTall()
local endY = self:GetTall() - mp:GetTall() - 16
mp:SetPos((self:GetWide() - mp:GetWide()) * 0.5, startY)
mp:SetAlpha(0)
mp.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(col)
:Shadow(7, 20)
:Outline(3)
:Clip(self)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(col)
:Draw()
draw.SimpleText(text, 'Fated.20', w * 0.5, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
mp:MoveTo(mp.x, endY, 0.3, 0, 0.7)
mp:AlphaTo(255, 0.3, 0, function()
timer.Simple(duration, function()
if !IsValid(mp) then return end
mp:AlphaTo(0, 0.25, 0, function()
if IsValid(mp) then
mp:Remove()
end
end)
end)
end)
self.messagePanel = mp
end
local flagsHeader = RNDX.NO_BL + RNDX.NO_BR
local flagsBackground = RNDX.NO_TL + RNDX.NO_TR
function PANEL:Paint(w, h)
RNDX().Rect(0, 0, w, h)
:Rad(6)
:Color(Mantle.color.window_shadow)
:Shadow(10, 16)
:Shape(RNDX.SHAPE_IOS)
:Draw()
if !self.bool_lite then
RNDX().Rect(0, 0, w, 24)
:Radii(6, 6, 0, 0)
:Color(Mantle.color.header)
:Draw()
end
local headerTall = self.bool_lite and 0 or 24
if self.bool_alpha and Mantle.ui.convar.blur then
RNDX().Rect(0, headerTall, w, h - headerTall)
:Radii(self.bool_lite and 6 or 0, self.bool_lite and 6 or 0, 6, 6)
:Blur()
:Draw()
end
RNDX().Rect(0, headerTall, w, h - headerTall)
:Radii(self.bool_lite and 6 or 0, self.bool_lite and 6 or 0, 6, 6)
:Color((self.bool_alpha and Mantle.ui.convar.blur) and Mantle.color.background_alpha or Mantle.color.background)
:Draw()
if !self.bool_lite then
if self.center_title != '' then
draw.SimpleText(self.center_title, 'Fated.20b', w * 0.5, 12, Mantle.color.header_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
draw.SimpleText(self.title, 'Fated.16', 6, 4, Mantle.color.header_text)
end
end
function PANEL:PerformLayout(w, h)
self.top_panel:SetSize(w, 24)
self.cls:SetSize(20, 20)
self.cls:SetPos(w - 22, 2)
end
vgui.Register('MantleFrame', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,115 @@
local color_disconnect = Color(210, 65, 65)
local color_bot = Color(70, 150, 220)
local color_online = Color(120, 180, 70)
function Mantle.ui.player_selector(do_click, func_check)
if IsValid(Mantle.ui.menu_player_selector) then
Mantle.ui.menu_player_selector:Remove()
end
Mantle.ui.menu_player_selector = vgui.Create('MantleFrame')
Mantle.ui.menu_player_selector:SetSize(340, 398)
Mantle.ui.menu_player_selector:Center()
Mantle.ui.menu_player_selector:MakePopup()
Mantle.ui.menu_player_selector:SetTitle('')
Mantle.ui.menu_player_selector:SetCenterTitle(Mantle.lang.get('mantle', 'player_title'))
Mantle.ui.menu_player_selector:ShowAnimation()
local contentPanel = vgui.Create('Panel', Mantle.ui.menu_player_selector)
contentPanel:Dock(FILL)
contentPanel:DockMargin(8, 0, 8, 8)
Mantle.ui.menu_player_selector.sp = vgui.Create('MantleScrollPanel', contentPanel)
Mantle.ui.menu_player_selector.sp:Dock(FILL)
local CARD_HEIGHT = 44
local AVATAR_SIZE = 32
local AVATAR_X = 14
local function CreatePlayerCard(pl)
local card = vgui.Create('DButton', Mantle.ui.menu_player_selector.sp)
card:Dock(TOP)
card:DockMargin(0, 5, 0, 0)
card:SetTall(CARD_HEIGHT)
card:SetText('')
card.hover_status = 0
card.OnCursorEntered = function(self)
self:SetCursor('hand')
end
card.OnCursorExited = function(self)
self:SetCursor('arrow')
end
card.Think = function(self)
local target = self:IsHovered() and 1 or 0
self.hover_status = Mantle.func.approachExp(self.hover_status, target, 8, FrameTime())
end
card.DoClick = function()
if IsValid(pl) then
Mantle.func.sound()
do_click(pl)
end
Mantle.ui.menu_player_selector:Remove()
end
card.pl_color = team.GetColor(pl:Team()) or color_online
card.Paint = function(self, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(10)
:Color(Mantle.color.panel[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
if self.hover_status > 0 then
RNDX().Rect(0, 0, w, h)
:Rad(10)
:Color(Color(0, 0, 0, 40 * self.hover_status))
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local infoX = AVATAR_X + AVATAR_SIZE + 10
if !IsValid(pl) then
draw.SimpleText(Mantle.lang.get('mantle', 'player_offline'), 'Fated.18', infoX, h * 0.5, color_disconnect, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
return
end
draw.SimpleText(pl:Name(), 'Fated.18', infoX, 6, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
local group = pl:GetUserGroup() or 'user'
group = string.upper(string.sub(group, 1, 1)) .. string.sub(group, 2)
draw.SimpleText(group, 'Fated.14', infoX, h - 6, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
draw.SimpleText(pl:Ping() .. ' ' .. Mantle.lang.get('mantle', 'player_ping'), 'Fated.16', w - 20, h - 6, Mantle.color.gray, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM)
local statusColor = color_disconnect
if pl:IsBot() then
statusColor = color_bot
else
statusColor = self.pl_color
end
RNDX.DrawCircle(w - 24, 14, 12, statusColor)
end
local avatarImg = vgui.Create('AvatarImage', card)
avatarImg:SetSize(AVATAR_SIZE, AVATAR_SIZE)
avatarImg:SetPos(AVATAR_X, (CARD_HEIGHT - AVATAR_SIZE) * 0.5)
avatarImg:SetSteamID(pl:SteamID64(), 64)
avatarImg:SetMouseInputEnabled(false)
avatarImg:SetKeyboardInputEnabled(false)
avatarImg.PaintOver = function() end
avatarImg:SetPos(AVATAR_X, (card:GetTall() - AVATAR_SIZE) * 0.5)
return card
end
for _, pl in player.Iterator() do
CreatePlayerCard(pl)
end
Mantle.ui.menu_player_selector.btn_close = vgui.Create('MantleBtn', Mantle.ui.menu_player_selector)
Mantle.ui.menu_player_selector.btn_close:Dock(BOTTOM)
Mantle.ui.menu_player_selector.btn_close:DockMargin(16, 8, 16, 12)
Mantle.ui.menu_player_selector.btn_close:SetTall(36)
Mantle.ui.menu_player_selector.btn_close:SetTxt(Mantle.lang.get('mantle', 'player_close'))
Mantle.ui.menu_player_selector.btn_close:SetColorHover(color_disconnect)
Mantle.ui.menu_player_selector.btn_close.DoClick = function()
Mantle.ui.menu_player_selector:Remove()
end
end

View File

@@ -0,0 +1,458 @@
local PANEL = {}
local pi = math.pi
local math_cos = math.cos
local math_sin = math.sin
local math_atan2 = math.atan2
local math_sqrt = math.sqrt
local math_floor = math.floor
local math_min = math.min
local math_max = math.max
local FrameTime = FrameTime
local SysTime = SysTime
local CurTime = CurTime
local Lerp = Lerp
local EPS = 1e-6
local EPS_ANGLE = 1e-4
local function GetSectorIndexFromAngle(angle, cnt)
if !angle or cnt <= 0 then return nil end
local sector = (2 * pi) / cnt
local raw = angle / sector
local idx = (math_floor(raw + EPS) % cnt) + 1
return idx
end
local function ClampEndAngle(a)
if a >= 360 then
return 360 - EPS_ANGLE
end
return a
end
function PANEL:Init(options)
options = options or {}
self.options = {}
self.rootMenu = { title = 'Меню', desc = 'Выберите опцию', options = self.options }
self.menuStack = {}
self.currentMenu = self.rootMenu
local baseRadius = options.radius or 320
local baseInner = options.inner_radius or 110
local minW, minH = 1366, 768
local scale = 1
if Mantle.func.sw > minW and Mantle.func.sh > minH then
scale = math_min(math_min(Mantle.func.sw / 1920, Mantle.func.sh / 1080), 1.15)
end
self.radius = Mantle.func.w(baseRadius) * scale
self.innerRadius = Mantle.func.w(baseInner) * scale
self.scale = scale
self.titleFont = 'Fated.28'
self.font = 'Fated.20'
self.descFont = 'Fated.14'
self.fadeInTime = 0.18
self.openTime = SysTime()
self.currentAlpha = 0
self.scaleAnim = 0.96
self.scale_animation = options.scale_animation != false
self.disable_background = options.disable_background or false
self.hover_sound = options.hover_sound or 'mantle/ratio_btn.ogg'
self.hoverOption = nil
self.hoverAnim = 0
self.selectedOption = nil
self._hotkeyCooldown = {}
self.optionHover = {}
self:SetSize(Mantle.func.sw, Mantle.func.sh)
self:SetPos(0, 0)
self:MakePopup()
self:SetKeyboardInputEnabled(true)
self:SetDrawOnTop(true)
self:SetMouseInputEnabled(true)
self._mouseWasDown = false
self.Think = function()
if self.currentAlpha < 255 then
self.currentAlpha = math.Clamp(255 * ((SysTime() - self.openTime) / self.fadeInTime), 0, 255)
if self.scale_animation then
local t = math.Clamp((SysTime() - self.openTime) / self.fadeInTime, 0, 1)
self.scaleAnim = 0.96 + (1 - (1 - t)^2) * 0.04
else
self.scaleAnim = 1
end
end
local curOuter = self.radius * self.scaleAnim
local curInner = self.innerRadius * self.scaleAnim
local mouseDown = input.IsMouseDown(MOUSE_LEFT)
if mouseDown and !self._mouseWasDown then
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local dist = math_sqrt((mx - cx)^2 + (my - cy)^2)
if dist > curInner and dist < curOuter then
local ang = math_atan2(my - cy, mx - cx)
if ang < 0 then ang = ang + 2 * pi end
local opts = self:GetCurrentOptions()
local cnt = #opts
if cnt > 0 then
local idx = GetSectorIndexFromAngle(ang, cnt)
if idx and opts[idx] then
self:SelectOption(idx)
if self.hover_sound then surface.PlaySound(self.hover_sound) end
end
end
elseif dist <= curInner then
if #self.menuStack > 0 then
self:GoBack()
if self.hover_sound then surface.PlaySound(self.hover_sound) end
else
self:Remove()
end
else
if dist >= curOuter then
self:Remove()
end
end
end
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local dist = math_sqrt((mx - cx)^2 + (my - cy)^2)
local hovered = nil
if dist > curInner and dist < curOuter then
local ang = math_atan2(my - cy, mx - cx)
if ang < 0 then ang = ang + 2 * pi end
local opts = self:GetCurrentOptions()
local cnt = #opts
if cnt > 0 then
hovered = GetSectorIndexFromAngle(ang, cnt)
end
end
if self.hoverOption != hovered and hovered and self.hover_sound then
surface.PlaySound(self.hover_sound)
end
self.hoverOption = hovered
self.hoverAnim = math.Clamp(self.hoverAnim + (self.hoverOption and 10 or -20) * FrameTime(), 0, 1)
self._mouseWasDown = mouseDown
local dt = FrameTime()
local opts = self:GetCurrentOptions()
for i = 1, #opts do
local target = (self.hoverOption == i) and 1 or 0
self.optionHover[i] = Mantle.func.approachExp(self.optionHover[i] or 0, target, 18, dt)
end
for i = 1, math_min(9, #self:GetCurrentOptions()) do
local k = KEY_1 + (i-1)
if input.IsKeyDown(k) then
local last = self._hotkeyCooldown[k] or 0
if CurTime() - last > 0.18 then
self._hotkeyCooldown[k] = CurTime()
self:SelectOption(i)
if self.hover_sound then surface.PlaySound(self.hover_sound) end
end
end
end
end
end
function PANEL:OnMousePressed(k)
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local curOuter = self.radius * self.scaleAnim
local dist = math_sqrt((mx - cx)^2 + (my - cy)^2)
if dist <= curOuter then return self:MouseCapture(true) end
self:Remove()
return true
end
function PANEL:OnMouseReleased(k) self:MouseCapture(false) end
function PANEL:CreateSubMenu(title, desc)
local submenu = { title = title or 'Подменю', desc = desc or '', options = {} }
function submenu:AddOption(text, func, icon, desc)
table.insert(submenu.options, { text = text, func = func, icon = icon, desc = desc })
return #submenu.options
end
return submenu
end
function PANEL:AddSubMenuOption(text, submenu, icon, desc)
return self:AddOption(text, nil, icon, desc, submenu)
end
function PANEL:AddOption(text, func, icon, desc, submenu)
table.insert(self.options, { text = text, func = func, icon = icon, desc = desc, submenu = submenu })
return #self.options
end
function PANEL:GetCurrentOptions()
if self.currentMenu and self.currentMenu.options then
return self.currentMenu.options
end
return self.options
end
function PANEL:SelectOption(index)
local opts = self:GetCurrentOptions()
if !opts or !opts[index] then return end
local opt = opts[index]
if opt.submenu then
table.insert(self.menuStack, self.currentMenu)
self.currentMenu = opt.submenu
self:UpdateCenterText()
return
end
self.selectedOption = opt
if opt.func then
local ok, err = pcall(opt.func)
if !ok then ErrorNoHalt(tostring(err) .. '\n') end
end
self:Remove()
end
function PANEL:GoBack()
if #self.menuStack > 0 then
self.currentMenu = table.remove(self.menuStack)
self:UpdateCenterText()
end
end
function PANEL:SetCenterText(title, desc)
self.rootMenu.title = title or self.rootMenu.title
self.rootMenu.desc = desc or self.rootMenu.desc
self:UpdateCenterText()
end
function PANEL:UpdateCenterText()
if self.currentMenu then
self.centerText = self.currentMenu.title or self.rootMenu.title
self.centerDesc = self.currentMenu.desc or self.rootMenu.desc
else
self.centerText = self.rootMenu.title
self.centerDesc = self.rootMenu.desc
end
end
function PANEL:IsMouseOver()
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local curOuter = self.radius * self.scaleAnim
return math_sqrt((mx - cx)^2 + (my - cy)^2) <= curOuter
end
function PANEL:OnCursorMoved(x,y)
if !self:IsMouseOver() then self.hoverOption = nil end
end
function PANEL:OnRemove()
if Mantle.ui.menu_radial == self then Mantle.ui.menu_radial = nil end
end
function PANEL:Paint(w,h)
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local alpha = math.Clamp(self.currentAlpha / 255, 0, 1)
local opts = self:GetCurrentOptions()
local cnt = #opts
if !self.disable_background then
RNDX().Rect(0, 0, w, h)
:Radii(0, 0, 0, 0)
:Color(Color(0, 0, 0, 140 * alpha))
:Draw()
end
local outerR = self.radius * self.scaleAnim
local innerR = self.innerRadius * self.scaleAnim
local outerD = outerR * 2
local innerD = innerR * 2
RNDX().Circle(cx, cy, outerD + 12)
:Color(Mantle.color.window_shadow)
:Shadow(8, 24)
:Draw()
RNDX().Circle(cx, cy, outerD)
:Color(Color(Mantle.color.background.r, Mantle.color.background.g, Mantle.color.background.b, math_floor(240 * alpha)))
:Draw()
RNDX().Circle(cx, cy, outerD)
:Outline(2)
:Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(160 * alpha)))
:Draw()
if cnt > 0 then
local sectorDeg = 360 / cnt
local baseCol = Mantle.color.background_panelpopup
local baseSectorCol = Color(baseCol.r, baseCol.g, baseCol.b, math_floor(255 * alpha))
for i = 1, cnt do
local startDeg = (i-1) * sectorDeg
local endDeg = i * sectorDeg
if startDeg < 0 then startDeg = 0 end
endDeg = ClampEndAngle(endDeg)
if endDeg > startDeg then
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Color(baseSectorCol)
:Draw()
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Outline(2)
:Color(Color(Mantle.color.panel[1].r, Mantle.color.panel[1].g, Mantle.color.panel[1].b, math_floor(160 * alpha)))
:Draw()
end
end
if self.hoverOption and opts[self.hoverOption] then
local i = self.hoverOption
local startDeg = (i-1) * sectorDeg
local endDeg = i * sectorDeg
if startDeg < 0 then startDeg = 0 end
endDeg = ClampEndAngle(endDeg)
if endDeg > startDeg then
local th = Mantle.color.theme
local hoverAlpha = math_floor(200 * self.hoverAnim * alpha)
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Color(Color(th.r, th.g, th.b, math_floor(22 * self.hoverAnim * alpha)))
:Draw()
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Outline(2)
:Color(Color(th.r, th.g, th.b, hoverAlpha))
:Draw()
end
end
RNDX().Circle(cx, cy, innerD)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math_floor(255 * alpha)))
:Draw()
local tintA = math_floor(36 * alpha)
RNDX().Circle(cx, cy, innerD - 8)
:Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, tintA))
:Draw()
RNDX().Circle(cx, cy, innerD)
:Outline(2)
:Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(80 * alpha)))
:Draw()
local sectorRad = (2 * pi) / cnt
for i, option in ipairs(opts) do
local startA = (i - 1) * sectorRad
local midA = startA + sectorRad * 0.5
local hv = self.optionHover[i] or 0
local eased = Mantle.func.easeOutCubic(math.Clamp(hv, 0, 1))
local labelR = innerR + (outerR - innerR) * (0.5 + 0.06 * eased)
local numberR = innerR + (labelR - innerR) * 0.35
local lx = cx + labelR * math_cos(midA)
local ly = cy + labelR * math_sin(midA)
local nx = cx + numberR * math_cos(midA)
local ny = cy + numberR * math_sin(midA)
local isHovered = (self.hoverOption == i)
local txtAlpha = math_floor((isHovered and 255 or 220) * alpha)
local txtCol = Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, txtAlpha)
if option.icon and option.icon != false and option.icon != nil then
local iconSize = Mantle.func.w(28) * self.scale * (1 + 0.06 * eased)
local iconX = lx - iconSize * 0.5
local iconY = ly - iconSize * 0.5 - Mantle.func.h(6) * self.scale
local mat = Material(option.icon)
if mat and !mat:IsError() then
surface.SetDrawColor(255,255,255, math_floor(230 * alpha))
surface.SetMaterial(mat)
surface.DrawTexturedRect(iconX, iconY, iconSize, iconSize)
end
draw.SimpleText(option.text or '', self.font, lx, ly + iconSize*0.5 - Mantle.func.h(4)*self.scale, txtCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
if option.desc and isHovered then
draw.SimpleText(option.desc, self.descFont, lx, ly + iconSize*0.5 + Mantle.func.h(16)*self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
if i <= 9 then
draw.SimpleText(tostring(i), 'Fated.14', nx, ny, Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(200 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
else
draw.SimpleText(option.text or '', self.font, lx, ly - Mantle.func.h(4) * self.scale, txtCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if option.desc and isHovered then
draw.SimpleText(option.desc, self.descFont, lx, ly + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
if i <= 9 then
draw.SimpleText(tostring(i), 'Fated.14', nx, ny, Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(200 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
end
else
RNDX().Circle(cx, cy, outerD)
:Color(Color(Mantle.color.background.r, Mantle.color.background.g, Mantle.color.background.b, math_floor(240 * alpha)))
:Draw()
RNDX().Circle(cx, cy, innerD)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math_floor(255 * alpha)))
:Draw()
end
if self.selectedOption then
local opt = self.selectedOption
if opt.icon and opt.icon != false and opt.icon != nil then
local isz = Mantle.func.w(48) * self.scale
local mat = Material(opt.icon)
if mat and !mat:IsError() then
surface.SetDrawColor(255, 255, 255, math_floor(255 * alpha))
surface.SetMaterial(mat)
surface.DrawTexturedRect(cx - isz/2, cy - isz/2 - Mantle.func.h(6)*self.scale, isz, isz)
end
draw.SimpleText(opt.text or '', self.titleFont, cx + isz*0.6, cy - Mantle.func.h(6) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
if opt.desc then
draw.SimpleText(opt.desc, self.descFont, cx + isz*0.6, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
else
draw.SimpleText(opt.text or '', self.titleFont, cx, cy - Mantle.func.h(6) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if opt.desc then
draw.SimpleText(opt.desc, self.descFont, cx, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
else
draw.SimpleText(self.centerText or self.rootMenu.title, self.titleFont, cx, cy - Mantle.func.h(8) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(self.centerDesc or self.rootMenu.desc, self.descFont, cx, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(160 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
vgui.Register('MantleRadialPanel', PANEL, 'DPanel')
function Mantle.ui.radial_menu(options)
if IsValid(Mantle.ui.menu_radial) then
Mantle.ui.menu_radial:Remove()
end
local m = vgui.Create('MantleRadialPanel')
m:Init(options)
Mantle.ui.menu_radial = m
return m
end

View File

@@ -0,0 +1,498 @@
local PANEL = {}
function PANEL:Init()
self._vbarPadRight = 6
self.content = vgui.Create('Panel', self)
self.content:SetMouseInputEnabled(true)
self.vbar = vgui.Create('Panel', self)
self.vbar:SetMouseInputEnabled(true)
self.vbarDefaultWidth = 4
self.vbarExpandedWidth = 6
self.vbarWidthSpeed = 12
self.vbarReserveWidth = self.vbarExpandedWidth
self.vbar:SetWide(self.vbarDefaultWidth)
self.vbar.Dragging = false
self.vbar._press_off = 0
self.vbar:Dock(RIGHT)
self.vbar:DockMargin(6, 0, 0, 0)
self.vbar.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.focus_panel)
:Draw()
end
self.vbarHoverDelay = 1
self.vbarUnhoverDelay = 0.5
self.vbar._hoverEnter = 0
self.vbar._hoverExit = 0
self.vbar._expanded = false
self.vbar.btnGrip = vgui.Create('MantleBtn', self.vbar)
self.vbar.btnGrip:SetText('')
self.vbar.btnGrip._ShadowLerp = 0
self.vbar.btnGrip.Paint = function(s, w, h)
s._ShadowLerp = Lerp(FrameTime() * 10, s._ShadowLerp, self.vbar.Dragging and 7 or 0)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.theme)
:Shadow(s._ShadowLerp, 20)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.theme)
:Draw()
end
self.vbar.btnGrip.OnMousePressed = function(s)
local _, my = s:GetParent():CursorPos()
s:GetParent().Dragging = true
s:GetParent()._press_off = my - s.y
s:MouseCapture(true)
s:GetParent()._springing = false
s:GetParent()._expanded = true
end
self.vbar.btnGrip.OnMouseReleased = function(s)
s:GetParent().Dragging = false
s:MouseCapture(false)
if !(s:GetParent():IsHovered() or s:IsHovered()) then
s:GetParent()._hoverExit = CurTime()
end
end
self.vbar.OnMousePressed = function(pnl)
local _, my = pnl:CursorPos()
local gy, gh = pnl.btnGrip.y, pnl.btnGrip:GetTall()
if my < gy then
self:_nudge(-self:GetTall())
elseif my > gy + gh then
self:_nudge(self:GetTall())
end
self.lastInput = CurTime()
self._springing = false
end
function self.vbar:AnimateTo(yPos)
self:GetParent():SetScroll(yPos)
end
function self.vbar:GetScroll()
return self:GetParent():GetScroll()
end
self.padL, self.padT, self.padR, self.padB = 0, 0, 0, 0
self.offset = 0
self.vel = 0
self.drag = false
self.dragLast = 0
self.lastInput = 0
self.scrollStep = 500
self.overscroll = 90
self.overscrollThreshold = 50
self.friction = 8
self.spring = 5
self.dragRes = 0.35
self.gripMin = 28
self.vbarSmooth = 3
self._vb_gripH = nil
self._vb_gripY = nil
self._vb_width = nil
self._needLayout = true
self:SetMouseInputEnabled(true)
self._springing = false
self._springTarget = 0
end
function PANEL:DockPadding(l,t,r,b)
self.padL, self.padT, self.padR, self.padB = l or 0, t or 0, r or 0, b or 0
self:_markDirty()
end
function PANEL:_markDirty()
self._needLayout = true
end
function PANEL:GetCanvas()
return self.content
end
function PANEL:GetVBar()
return self.vbar
end
function PANEL:DisableVBarPadding()
if !IsValid(self.vbar) then return end
self._vbarPadRight = 0
self.vbar:DockMargin(self._vbarPadRight, 0, 0, 0)
self:_markDirty()
self:InvalidateLayout(true)
self.content:InvalidateLayout(true)
end
function PANEL:AddItem(pnl)
pnl:SetParent(self.content)
local old = pnl.OnSizeChanged
pnl.OnSizeChanged = function(...)
if old then pcall(old, ...) end
if IsValid(self) then
self:_markDirty()
self:InvalidateLayout(true)
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
end
end
self:_markDirty()
return pnl
end
function PANEL:Add(pnl)
return self:AddItem(pnl)
end
function PANEL:OnChildAdded(child)
timer.Simple(0, function()
if child == self.content or child == self.vbar or child == self.vbar.btnGrip then return end
if !IsValid(child) or !IsValid(self) then return end
if child:GetParent() == self then
child:SetParent(self.content)
local old = child.OnSizeChanged
child.OnSizeChanged = function(...)
if old then pcall(old, ...) end
if IsValid(self) then
self:_markDirty()
self:InvalidateLayout(true)
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
end
end
self:_markDirty()
end
end)
end
function PANEL:Clear()
for _, c in ipairs(self.content:GetChildren()) do c:Remove() end
self.offset = 0
self.vel = 0
self:_markDirty()
end
function PANEL:SetScroll(y)
self.offset = y or 0
end
function PANEL:GetScroll()
return self.offset
end
function PANEL:_range()
if self._needLayout then
local w, h = self:GetWide(), self:GetTall()
local vbw = self.vbar:GetWide()
self.content:DockPadding(0, 0, 0, 0)
local vbReserve = self.vbarReserveWidth or self.vbarExpandedWidth
self.content:SetPos(self.padL, self.padT - self.offset)
local contentW = math.max(0, w - self.padL - self.padR - vbReserve - self._vbarPadRight)
self.content:SetWide(contentW)
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
local viewH = math.max(0, h - self.padT - self.padB)
local contentH = self.content:GetTall()
if contentH <= viewH then
self.vbar:SetVisible(false)
self.content:SetWide(math.max(0, w - self.padL - self.padR))
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
contentH = self.content:GetTall()
else
self.vbar:SetVisible(true)
end
self._needLayout = false
end
local viewH = math.max(0, self:GetTall() - self.padT - self.padB)
local contentH = self.content:GetTall()
return math.max(0, contentH - viewH), viewH, contentH
end
function PANEL:_nudge(px)
self.vel = self.vel + px * 10
self.lastInput = CurTime()
end
function PANEL:OnMouseWheeled(delta)
local _, _, contentH = self:_range()
if contentH <= 0 then return end
self._springing = false
self.vel = self.vel - delta * self.scrollStep
self.lastInput = CurTime()
return true
end
function PANEL:OnMousePressed(mc)
if mc != MOUSE_LEFT then return end
local hovered = vgui.GetHoveredPanel()
if IsValid(hovered) and hovered != self and hovered:IsDescendantOf(self.content) then return end
self.drag = true
self.dragLast = select(2, self:CursorPos())
self.vel = 0
self.lastInput = CurTime()
self:MouseCapture(true)
self._springing = false
end
function PANEL:OnMouseReleased(mc)
if mc != MOUSE_LEFT then return end
self.drag = false
self:MouseCapture(false)
local maxScrollDF = select(1, self:_range()) or 0
local extraTop = math.max(0, -self.offset)
local extraBottom = math.max(0, self.offset - maxScrollDF)
if extraTop > self.overscrollThreshold then
self:_startSpring(0)
elseif extraBottom > self.overscrollThreshold then
self:_startSpring(maxScrollDF)
end
end
function PANEL:OnCursorMoved(_, y)
if !self.drag then return end
local dy = y - self.dragLast
self.dragLast = y
local maxScrollDF = self:_range()
local next = self.offset - dy
if next < 0 then
self.offset = self.offset - dy * self.dragRes
elseif next > maxScrollDF then
self.offset = self.offset - dy * self.dragRes
else
self.offset = next
end
self.lastInput = CurTime()
end
function PANEL:SetVBarPaddingRight(enabled)
if !IsValid(self.vbar) then return end
self.vbar:DockMargin(enabled and 6 or 0, 0, 0, 0)
self:_markDirty()
end
function PANEL:PerformLayout(w, h)
self:_markDirty()
end
function PANEL:_startSpring(target)
self._springing = true
self._springTarget = target
self.vel = 0
end
function PANEL:Think()
local ft = FrameTime()
local maxScrollDF, viewH, contentH = self:_range()
local extraTop = math.max(0, -self.offset)
local extraBottom = math.max(0, self.offset - maxScrollDF)
if self._springing then
if CurTime() - self.lastInput < 0.02 then
self._springing = false
end
end
if self._springing then
local t = math.min(1, ft * self.spring)
self.offset = Lerp(t, self.offset, self._springTarget)
self.vel = 0
if math.abs(self.offset - self._springTarget) < 0.5 then
self.offset = self._springTarget
self._springing = false
end
else
if !self.drag then
self.offset = self.offset + self.vel * ft
if self.offset < -self.overscroll then
self.offset = -self.overscroll
self.vel = 0
elseif self.offset > maxScrollDF + self.overscroll then
self.offset = maxScrollDF + self.overscroll
self.vel = 0
else
self.vel = self.vel * math.max(0, 1 - ft * self.friction)
if math.abs(self.vel) < 2 then self.vel = 0 end
end
if CurTime() - self.lastInput > 0.09 and self.vel == 0 then
if extraTop > self.overscrollThreshold then
self:_startSpring(0)
elseif extraBottom > self.overscrollThreshold then
self:_startSpring(maxScrollDF)
end
end
end
end
self.content:SetPos(self.padL, self.padT - math.floor(self.offset))
local vb = self.vbar
if !vb:IsVisible() then return end
local hoveredNow = vb:IsHovered() or vb.btnGrip:IsHovered()
if hoveredNow then
if vb._hoverEnter == 0 then vb._hoverEnter = CurTime() end
vb._hoverExit = 0
else
if vb._hoverExit == 0 then vb._hoverExit = CurTime() end
vb._hoverEnter = 0
end
if vb.Dragging then vb._expanded = true end
if vb._hoverEnter > 0 and CurTime() - vb._hoverEnter >= self.vbarHoverDelay then
vb._expanded = true
end
if vb._hoverExit > 0 and CurTime() - vb._hoverExit >= self.vbarUnhoverDelay and !vb.Dragging then
vb._expanded = false
end
local targetW = (vb._expanded and self.vbarExpandedWidth) or self.vbarDefaultWidth
if vb.Dragging then targetW = self.vbarExpandedWidth end
if self._vb_width == nil then
self._vb_width = targetW
else
self._vb_width = Mantle.func.approachExp(self._vb_width, targetW, self.vbarWidthSpeed, ft)
if math.abs(self._vb_width - targetW) < 0.25 then self._vb_width = targetW end
end
local newW = math.max(1, math.floor(self._vb_width))
if vb:GetWide() != newW then
vb:SetWide(newW)
self:_markDirty()
end
local trackH = vb:GetTall()
local clampedOffset = math.Clamp(self.offset, 0, maxScrollDF)
local ratio = (contentH <= 0) and 1 or math.min(1, viewH / contentH)
local gripH = math.max(self.gripMin, math.floor(trackH * ratio))
local scroll01 = (maxScrollDF <= 0) and 0 or (clampedOffset / maxScrollDF)
local topFrac = math.Clamp(extraTop / self.overscroll, 0, 1)
local bottomFrac = math.Clamp(extraBottom / self.overscroll, 0, 1)
local maxFrac = math.max(topFrac, bottomFrac)
local overscrollFrac = math.Clamp(math.max(extraTop, extraBottom) / self.overscroll, 0, 1)
local gripRatio = gripH / math.max(1, trackH)
local weight = math.Clamp((1 - gripRatio) * 1.5, 0, 1)
local contentToTrack = trackH / math.max(1, contentH)
local extraShift = 0
if extraTop > 0 then extraShift = -extraTop * contentToTrack
elseif extraBottom > 0 then extraShift = extraBottom * contentToTrack end
local proportionalY = (trackH - gripH) * scroll01
local desiredY = proportionalY + extraShift * weight * overscrollFrac
if clampedOffset <= 0.001 then
desiredY = 0
elseif maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then
desiredY = trackH - gripH
end
local maxShrink = 0.7
local visualGripH = gripH * (1 - maxShrink * overscrollFrac * weight)
visualGripH = math.max(6, visualGripH)
local gripSpeed = 14
if vb.Dragging then
local _, my = vb:CursorPos()
local newY = math.Clamp(my - vb._press_off, 0, trackH - visualGripH)
local s01 = (trackH - visualGripH) <= 0 and 0 or (newY / (trackH - visualGripH))
self.offset = s01 * maxScrollDF
self.vel = 0
self._vb_gripH = visualGripH
self._vb_gripY = newY
else
if self._vb_gripH == nil then
self._vb_gripH = visualGripH
else
self._vb_gripH = Mantle.func.approachExp(self._vb_gripH, visualGripH, gripSpeed, ft)
if math.abs(self._vb_gripH - visualGripH) < 0.25 then self._vb_gripH = visualGripH end
end
if self._vb_gripY == nil then
self._vb_gripY = desiredY
else
local speedY = gripSpeed * (1 + maxFrac * 0.5)
self._vb_gripY = Mantle.func.approachExp(self._vb_gripY, desiredY, speedY, ft)
if math.abs(self._vb_gripY - desiredY) < 0.25 then self._vb_gripY = desiredY end
end
local maxY = math.max(0, trackH - self._vb_gripH)
if self._vb_gripY < 0 then self._vb_gripY = 0 end
if self._vb_gripY > maxY then self._vb_gripY = maxY end
if clampedOffset <= 0.001 then
self._vb_gripY = 0
elseif maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then
self._vb_gripY = trackH - self._vb_gripH
end
if math.abs(self._vb_gripH - visualGripH) < 0.25 then self._vb_gripH = visualGripH end
if math.abs((self._vb_gripY or 0) - desiredY) < 0.25 then self._vb_gripY = desiredY end
end
local finalH = math.max(1, math.floor(self._vb_gripH))
local finalY = math.floor(math.Clamp(self._vb_gripY or 0, 0, math.max(0, trackH - finalH)))
if clampedOffset <= 0.001 then finalY = 0 end
if maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then finalY = trackH - finalH end
vb.btnGrip:SetSize(vb:GetWide(), finalH)
vb.btnGrip:SetPos(0, finalY)
end
vgui.Register('MantleScrollPanel', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,206 @@
local PANEL = {}
function PANEL:Init()
self.text = ''
self.min_value = 0
self.max_value = 1
self.decimals = 0
self.convar = nil
self.value = 0
self.smoothPos = 0
self.targetPos = 0
self.dragging = false
self.hover = false
self:SetTall(60)
self.OnValueChanged = function() end
self._convar_last = nil
self._convar_timer = self:CreateConVarSyncTimer()
self._dragAlpha = 255
end
function PANEL:CreateConVarSyncTimer()
local name = 'MantleSlideBoxSync' .. tostring(self)
timer.Create(name, 0.1, 0, function()
if not IsValid(self) or not self.convar then return end
local cvar = GetConVar(self.convar)
if not cvar then return end
local val = cvar:GetFloat()
if self._convar_last ~= val then
self._convar_last = val
self:SetValue(val, true)
end
end)
return name
end
function PANEL:OnRemove()
if self._convar_timer then
timer.Remove(self._convar_timer)
self._convar_timer = nil
end
end
function PANEL:SetRange(min_value, max_value, decimals)
self.min_value = min_value
self.max_value = max_value
self.decimals = decimals or 0
self:SetValue(self.value or min_value)
end
function PANEL:SetConvar(convar)
self.convar = convar
local cvar = GetConVar(convar)
if cvar then
self:SetValue(cvar:GetFloat(), true)
self._convar_last = cvar:GetFloat()
end
end
function PANEL:SetText(text)
self.text = text
end
function PANEL:SetValue(val, fromConVar)
if self.max_value == self.min_value then
val = self.min_value
else
val = math.Clamp(val, self.min_value, self.max_value)
end
if self.decimals > 0 then
val = tonumber(string.format('%.' .. tostring(self.decimals) .. 'f', val)) or val
else
val = math.Round(val)
end
self.value = val
local denom = (self.max_value - self.min_value)
local progress = denom == 0 and 0 or (val - self.min_value) / denom
local w = math.max(0, self:GetWide() - 32)
self.targetPos = math.Clamp(w * progress, 0, w)
if self.convar and not fromConVar then
RunConsoleCommand(self.convar, tostring(val))
self._convar_last = val
end
if self.OnValueChanged then self:OnValueChanged(val) end
end
function PANEL:GetValue()
return self.value
end
function PANEL:UpdateSliderByCursorPos(x)
local w = math.max(0, self:GetWide() - 32)
local progress = math.Clamp(x / w, 0, 1)
local new_value = self.min_value + (progress * (self.max_value - self.min_value))
if self.decimals > 0 then
new_value = tonumber(string.format('%.' .. tostring(self.decimals) .. 'f', new_value))
else
new_value = math.Round(new_value)
end
self:SetValue(new_value)
end
function PANEL:Paint(w, h)
local ft = FrameTime()
local padX = 16
local padTop = 2
local barY = 32
local barH = 6
local barR = barH / 2
local handleW, handleH = 14, 14
local handleR = handleH / 2
local textFont = 'Fated.18'
local minmaxFont = 'Fated.14'
local valueFont = 'Fated.16'
local minmaxPadY = 12
-- Текст сверху
draw.SimpleText(self.text, textFont, padX, padTop, Mantle.color.text)
-- Линия
local barStart = padX + handleW / 2
local barEnd = w - padX - handleW / 2
local barW = math.max(0, barEnd - barStart)
local denom = (self.max_value - self.min_value)
local progress = denom == 0 and 0 or (self.value - self.min_value) / denom
progress = math.Clamp(progress, 0, 1)
local activeW = barW * progress
-- Тень под линией
if Mantle.ui.convar.depth_ui then
RNDX().Rect(barStart, barY, barW, barH)
:Rad(barR)
:Color(Mantle.color.window_shadow)
:Shadow(5, 20)
:Draw()
end
-- Фон линии
RNDX.Draw(barR, barStart, barY, barW, barH, Mantle.color.focus_panel)
RNDX.Draw(barR, barStart, barY, barW, barH, Mantle.color.button_shadow)
-- Активная линия
self.smoothPos = Mantle.func.approachExp(self.smoothPos or 0, activeW, 14, ft)
if math.abs(self.smoothPos - activeW) < 0.5 then self.smoothPos = activeW end
RNDX.Draw(barR, barStart, barY, self.smoothPos, barH, Mantle.color.theme)
local handleX = barStart + self.smoothPos
local handleY = barY + barH / 2
-- Тень под ручкой
RNDX.DrawShadows(handleR, handleX - handleW / 2, handleY - handleH / 2, handleW, handleH, Mantle.color.window_shadow, 3, 10)
local targetAlpha = self.dragging and 100 or 255
self._dragAlpha = Mantle.func.approachExp(self._dragAlpha or 255, targetAlpha, 24, ft)
if math.abs(self._dragAlpha - targetAlpha) < 1 then self._dragAlpha = targetAlpha end
local colorText = Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math.floor(self._dragAlpha))
-- Ручка
RNDX.Draw(handleR, handleX - handleW / 2, handleY - handleH / 2, handleW, handleH, colorText)
-- Значение справа от линии
local valText = (self.decimals > 0) and tostring(self.value) or tostring(self.value)
draw.SimpleText(valText, valueFont, barEnd + handleW / 2 + 4, barY + barH / 2, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- min/max под линией
draw.SimpleText(self.min_value, minmaxFont, barStart, barY + barH + minmaxPadY - 4, Mantle.color.gray, TEXT_ALIGN_LEFT)
draw.SimpleText(self.max_value, minmaxFont, barEnd, barY + barH + minmaxPadY - 4, Mantle.color.gray, TEXT_ALIGN_RIGHT)
end
function PANEL:OnMousePressed(mcode)
if mcode == MOUSE_LEFT then
local x = self:CursorPos()
self:UpdateSliderByCursorPos(x)
self.dragging = true
self:MouseCapture(true)
self.ripple_x = x
self.ripple_anim = 0
self.ripple_active = true
end
end
function PANEL:OnMouseReleased(mcode)
if mcode == MOUSE_LEFT then
self.dragging = false
self:MouseCapture(false)
end
end
function PANEL:OnCursorMoved(x)
if self.dragging then
self:UpdateSliderByCursorPos(x)
end
end
function PANEL:OnCursorEntered()
self.hover = true
end
function PANEL:OnCursorExited()
self.hover = false
end
vgui.Register('MantleSlideBox', PANEL, 'Panel')

View File

@@ -0,0 +1,513 @@
local PANEL = {}
local FrameTime = FrameTime
local CurTime = CurTime
local Lerp = Lerp
local math_max = math.max
local math_floor = math.floor
function PANEL:Init()
self.columns = {}
self.rows = {}
self.headerHeight = 36
self.rowHeight = 32
self.font = 'Fated.18'
self.rowFont = 'Fated.16'
self.selectedRow = nil
self.sortColumn = nil
self.sortDesc = true
self.sortState = 0
self._originalRows = nil
self.hoverAnim = 0
self.padding = 8
self.sidePadding = 12
self.vbarRightPadding = 6
self.vbarLeftExtra = 0
self.header = vgui.Create('Panel', self)
self.header:Dock(TOP)
self.header:SetTall(self.headerHeight)
self.scrollPanel = vgui.Create('MantleScrollPanel', self)
self.scrollPanel:Dock(FILL)
self.scrollPanel:DisableVBarPadding()
self.content = vgui.Create('Panel', self.scrollPanel)
self.content:Dock(TOP)
self.content.Paint = nil
self._rowPanels = {}
self._headerButtons = {}
self._colWidthsTarget = {}
self._colWidthsCurrent = {}
self._lastVBarVis = nil
self._headerPrevActive = {}
self.OnAction = function() end
self.OnRightClick = function() end
self.Think = function()
local dt = FrameTime()
self:UpdateColumnWidthTargets()
for i = 1, #self.columns do
local tgt = self._colWidthsTarget[i] or (self.columns[i] and self.columns[i].width or 100)
self._colWidthsCurrent[i] = Mantle.func.approachExp(self._colWidthsCurrent[i] or tgt, tgt, 20, dt)
end
local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
local x = leftPad
for i, btn in ipairs(self._headerButtons) do
local w = math_floor(self._colWidthsCurrent[i] or (self.columns[i] and self.columns[i].width or 100))
if IsValid(btn) then
btn:SetSize(w, self.headerHeight)
btn:SetPos(x, 0)
end
x = x + w
end
local total = 0
for i = 1, #self.columns do total = total + (self._colWidthsCurrent[i] or self.columns[i].width) end
local panelW = self:GetWide() or 0
if panelW <= 0 then
local par = self:GetParent()
if IsValid(par) and par.GetWide then panelW = par:GetWide() end
end
if panelW <= 0 then panelW = ScrW() end
local rightPad = self.sidePadding + ((self._lastVBarVis and self.vbarRightPadding) or 0)
local contentW = math_max(total + leftPad + rightPad, panelW)
for _, row in ipairs(self._rowPanels) do
if IsValid(row) and row._labels then
local x2 = leftPad
local hv = row._hoverAlpha or 0
local eased = Mantle.func.easeOutCubic(math.Clamp(hv, 0, 1))
local shift = math_floor(6 * eased)
for i, label in ipairs(row._labels) do
local w = math_floor(self._colWidthsCurrent[i] or (self.columns[i] and self.columns[i].width or 100))
if IsValid(label) then
local dx = 0
if i == 1 then
dx = shift
elseif i == #self.columns then
dx = -shift
end
label:SetSize(w, self.rowHeight)
label:SetPos(x2 + dx, 0)
end
x2 = x2 + w
end
row:SetWide(contentW)
end
end
self.content:SetWide(contentW)
end
end
function PANEL:AddColumn(name, width, align, sortable)
table.insert(self.columns, {
name = name,
width = width or 100,
align = align or TEXT_ALIGN_LEFT,
sortable = sortable or false
})
end
function PANEL:AddItem(...)
local args = {...}
if #args != #self.columns then
print(Mantle.lang.get('mantle', 'table_wrong_args'))
return
end
table.insert(self.rows, args)
self:RebuildRows()
return #self.rows
end
local function getValueType(value)
if value == nil then return 'nil' end
value = tostring(value)
return tonumber(value) and 'number' or 'string'
end
local function compareValues(a, b)
if a == nil and b == nil then return false end
if a == nil then return true end
if b == nil then return false end
local typeA = getValueType(a)
local typeB = getValueType(b)
if typeA != typeB then
return typeA < typeB
end
if typeA == 'number' then
local numA = tonumber(a) or 0
local numB = tonumber(b) or 0
return numA > numB
else
local strA = tostring(a)
local strB = tostring(b)
return strA < strB
end
end
local function cloneRows(tbl)
local out = {}
for i, v in ipairs(tbl) do out[i] = v end
return out
end
function PANEL:SortByColumn(columnIndex)
local column = self.columns[columnIndex]
if !column or !column.sortable then return end
if self.sortColumn != columnIndex then
self.sortColumn = columnIndex
local numCount, total = 0, 0
for _, row in ipairs(self.rows) do
local v = row[columnIndex]
if v != nil then
total = total + 1
if tonumber(tostring(v)) then numCount = numCount + 1 end
end
end
local isNumeric = (total > 0 and numCount >= math.ceil(total / 2))
self.sortDesc = isNumeric
else
self.sortDesc = !self.sortDesc
end
local desc = self.sortDesc
table.sort(self.rows, function(a, b)
local va = a[columnIndex]
local vb = b[columnIndex]
if va == nil and vb == nil then return false end
if va == nil then return !desc end
if vb == nil then return desc end
local sa = tostring(va)
local sb = tostring(vb)
local na = tonumber(sa)
local nb = tonumber(sb)
if na and nb then
if desc then
return na > nb
else
return na < nb
end
end
if na and !nb then
return desc
elseif nb and !na then
return !desc
end
local la = string.lower(sa)
local lb = string.lower(sb)
if desc then
return la > lb
else
return la < lb
end
end)
self:RebuildRows()
end
function PANEL:UpdateColumnWidthTargets()
local cols = self.columns
local n = #cols
if n == 0 then return end
local panelW = self:GetWide() or 0
if (!panelW) or panelW <= 0 then
local parent = self:GetParent()
if IsValid(parent) and parent.GetWide then panelW = parent:GetWide() end
end
if (!panelW) or panelW <= 0 then panelW = ScrW() end
local vbar = (IsValid(self.scrollPanel) and self.scrollPanel.GetVBar) and self.scrollPanel:GetVBar() or nil
local vbarVisible = (IsValid(vbar) and vbar:IsVisible())
local vbarW = (vbarVisible and vbar:GetWide() or 0)
local leftPad = self.sidePadding + (vbarVisible and self.vbarLeftExtra or 0)
local rightPad = self.sidePadding + (vbarVisible and self.vbarRightPadding or 0)
local usable = math_max(0, panelW - leftPad - rightPad - vbarW)
local used = 0
for i = 1, math.max(0, n - 1) do
self._colWidthsTarget[i] = cols[i].width or 100
used = used + self._colWidthsTarget[i]
end
local lastMin = cols[n].width or 100
local remaining = usable - used
if remaining < lastMin then remaining = lastMin end
self._colWidthsTarget[n] = remaining
for i = 1, n do
if self._colWidthsCurrent[i] == nil then
self._colWidthsCurrent[i] = self._colWidthsTarget[i]
end
end
self._lastVBarVis = vbarVisible
end
function PANEL:CreateHeader()
local prev = {}
for i, btn in ipairs(self._headerButtons) do
if IsValid(btn) then prev[i] = btn._activeAlpha or 0 end
end
self.header:Clear()
self._headerButtons = {}
self.header.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Radii(16, 16, 0, 0)
:Color(Mantle.color.focus_panel)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
self:UpdateColumnWidthTargets()
local xPos = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
for i, column in ipairs(self.columns) do
local w = math_floor(self._colWidthsCurrent[i] or self._colWidthsTarget[i] or column.width)
local label = vgui.Create('DButton', self.header)
label:SetText('')
label:SetSize(w, self.headerHeight)
label:SetPos(xPos, 0)
label._hover = 0
label._activeAlpha = prev[i] or ((self.sortColumn == i) and 1 or 0)
label.Paint = function(s, bw, bh)
local dt = FrameTime()
local target = s:IsHovered() and 1 or 0
s._hover = Mantle.func.approachExp(s._hover or 0, target, 14, dt)
local activeTarget = (self.sortColumn == i) and 1 or 0
s._activeAlpha = Mantle.func.approachExp(s._activeAlpha or 0, activeTarget, 12, dt)
local tr = Lerp(s._activeAlpha, Mantle.color.text.r, Mantle.color.theme.r)
local tg = Lerp(s._activeAlpha, Mantle.color.text.g, Mantle.color.theme.g)
local tb = Lerp(s._activeAlpha, Mantle.color.text.b, Mantle.color.theme.b)
local ta = Lerp(s._activeAlpha, Mantle.color.text.a, Mantle.color.theme.a)
local textColor = Color(math_floor(tr), math_floor(tg), math_floor(tb), math_floor(ta))
draw.SimpleText(column.name, self.font, bw/2, bh/2, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
if column.sortable then
label.DoClick = function()
self:SortByColumn(i)
Mantle.func.sound()
end
end
table.insert(self._headerButtons, label)
xPos = xPos + w
end
end
function PANEL:CreateRow(rowIndex, rowData)
local row = vgui.Create('DButton', self.content)
row:Dock(TOP)
row:DockMargin(0, 0, 0, 1)
row:SetTall(self.rowHeight)
row:SetText('')
row._index = rowIndex
row._hoverAlpha = 0
row._selectedAlpha = 0
row._labels = {}
row.Paint = function(s, w, h)
local dt = FrameTime()
local hoverTarget = s:IsHovered() and 1 or 0
s._hoverAlpha = Mantle.func.approachExp(s._hoverAlpha, hoverTarget, 18, dt)
local selTarget = (self.selectedRow == s._index) and 1 or 0
s._selectedAlpha = Mantle.func.approachExp(s._selectedAlpha, selTarget, 22, dt)
local base = Mantle.color.panel_alpha[1]
local hoverCol = Mantle.color.hover
local selCol = Mantle.color.theme
local mixHover = s._hoverAlpha * (1 - s._selectedAlpha)
local blendA = s._selectedAlpha * 0.9 + mixHover * 0.35
local r = Lerp(blendA, base.r, selCol.r)
local g = Lerp(blendA, base.g, selCol.g)
local b = Lerp(blendA, base.b, selCol.b)
local a = Lerp(blendA, base.a, selCol.a)
if s._hoverAlpha > 0.01 and s._selectedAlpha < 0.9 then
local hoverR = Lerp(s._hoverAlpha * 0.6, r, hoverCol.r)
local hoverG = Lerp(s._hoverAlpha * 0.6, g, hoverCol.g)
local hoverB = Lerp(s._hoverAlpha * 0.6, b, hoverCol.b)
r,g,b = hoverR, hoverG, hoverB
end
RNDX().Rect(0, 0, w, math.max(0, h - 1))
:Color(Color(math.floor(r), math.floor(g), math.floor(b), math.floor(a)))
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
row.DoClick = function()
self.selectedRow = rowIndex
self._keyboardIndex = rowIndex
self.OnAction(rowData)
Mantle.func.sound()
end
row.DoRightClick = function()
self.selectedRow = rowIndex
self.OnRightClick(rowData)
local menu = Mantle.ui.derma_menu()
for i, column in ipairs(self.columns) do
menu:AddOption(Mantle.lang.get('mantle', 'table_copy') .. ' ' .. column.name, function()
SetClipboardText(tostring(rowData[i]))
end)
end
menu:AddSpacer()
menu:AddOption(Mantle.lang.get('mantle', 'table_delete_row'), function()
self:RemoveRow(rowIndex)
end, 'icon16/delete.png')
end
local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
local xPos = leftPad
for i, column in ipairs(self.columns) do
local w = math_floor(self._colWidthsCurrent[i] or self._colWidthsTarget[i] or column.width)
local label = vgui.Create('DLabel', row)
label:SetText(tostring(rowData[i]))
label:SetFont(self.rowFont)
label:SetTextColor(Mantle.color.text)
label:SetSize(w, self.rowHeight)
label:SetPos(xPos, 0)
if column.align == TEXT_ALIGN_LEFT then
label:SetTextInset(self.padding, 0)
label:SetContentAlignment(4)
elseif column.align == TEXT_ALIGN_RIGHT then
label:SetTextInset(0, 0)
label:SetContentAlignment(6)
else
label:SetTextInset(0, 0)
label:SetContentAlignment(5)
end
table.insert(row._labels, label)
xPos = xPos + w
end
table.insert(self._rowPanels, row)
end
function PANEL:RebuildRows()
local savedRowHover = {}
for idx, oldRow in ipairs(self._rowPanels) do
if IsValid(oldRow) then
savedRowHover[idx] = oldRow._hoverAlpha or 0
end
end
local prevHeader = {}
for i, btn in ipairs(self._headerButtons) do
if IsValid(btn) then prevHeader[i] = btn._activeAlpha or 0 end
end
self.content:Clear()
self._rowPanels = {}
self._headerButtons = {}
self:UpdateColumnWidthTargets()
self:CreateHeader()
for rowIndex, rowData in ipairs(self.rows) do
self:CreateRow(rowIndex, rowData)
if savedRowHover[rowIndex] and IsValid(self._rowPanels[#self._rowPanels]) then
self._rowPanels[#self._rowPanels]._hoverAlpha = savedRowHover[rowIndex]
end
end
local total = 0
for i = 1, #self.columns do total = total + (self._colWidthsTarget[i] or self.columns[i].width) end
local panelW = self:GetWide() or 0
if panelW <= 0 then
local parent = self:GetParent()
if IsValid(parent) and parent.GetWide then panelW = parent:GetWide() end
end
if panelW <= 0 then panelW = ScrW() end
local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
local rightPad = self.sidePadding + ((self._lastVBarVis and self.vbarRightPadding) or 0)
local contentW = math_max(total + leftPad + rightPad, panelW)
self.content:SetSize(contentW, #self.rows * (self.rowHeight + 1))
self.scrollPanel:InvalidateLayout(true)
end
function PANEL:SetAction(func)
self.OnAction = func
end
function PANEL:SetRightClickAction(func)
self.OnRightClick = func
end
function PANEL:Clear()
self.rows = {}
self.selectedRow = nil
self.content:Clear()
end
function PANEL:GetSelectedRow()
return self.selectedRow and self.rows[self.selectedRow] or nil
end
function PANEL:GetRowCount()
return #self.rows
end
function PANEL:RemoveRow(index)
if index and index > 0 and index <= #self.rows then
table.remove(self.rows, index)
if self.selectedRow == index then
self.selectedRow = nil
elseif self.selectedRow and self.selectedRow > index then
self.selectedRow = self.selectedRow - 1
end
self:RebuildRows()
self.scrollPanel:InvalidateLayout(true)
end
end
function PANEL:Paint(w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.panel_alpha[2])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
vgui.Register('MantleTable', PANEL, 'Panel')

View File

@@ -0,0 +1,196 @@
local PANEL = {}
function PANEL:Init()
self.tabs = {}
self.active_id = 1
self.tab_height = 38
self.animation_speed = 12
self.tab_style = 'modern' -- modern или classic
self.indicator_height = 2
self.indicator_x = 0
self.indicator_w = 0
self.indicator_target_x = 0
self.indicator_target_w = 0
self.panel_tabs = vgui.Create('Panel', self)
self.panel_tabs.Paint = nil
self.content = vgui.Create('Panel', self)
self.content.Paint = nil
end
function PANEL:Think()
if self.tab_style == 'modern' then
self.indicator_x = Mantle.func.approachExp(self.indicator_x, self.indicator_target_x, self.animation_speed, FrameTime())
self.indicator_w = Mantle.func.approachExp(self.indicator_w, self.indicator_target_w, self.animation_speed, FrameTime())
if math.abs(self.indicator_x - self.indicator_target_x) < 0.5 then
self.indicator_x = self.indicator_target_x
end
if math.abs(self.indicator_w - self.indicator_target_w) < 0.5 then
self.indicator_w = self.indicator_target_w
end
end
end
function PANEL:SetTabStyle(style)
self.tab_style = style
self:Rebuild()
end
function PANEL:SetTabHeight(height)
self.tab_height = height
self:Rebuild()
end
function PANEL:SetIndicatorHeight(height)
self.indicator_height = height
self:Rebuild()
end
function PANEL:AddTab(name, pan, icon)
local newId = #self.tabs + 1
self.tabs[newId] = {
name = name,
pan = pan,
icon = icon
}
self.tabs[newId].pan:SetParent(self.content)
self.tabs[newId].pan:Dock(FILL)
self.tabs[newId].pan:SetVisible(newId == 1 and true or false)
self:Rebuild()
end
local color_btn_hovered = Color(255, 255, 255, 10)
function PANEL:Rebuild()
self.panel_tabs:Clear()
for id, tab in ipairs(self.tabs) do
local btnTab = vgui.Create('Button', self.panel_tabs)
tab._btn = btnTab
if self.tab_style == 'modern' then
surface.SetFont('Fated.18')
local textW = select(1, surface.GetTextSize(tab.name))
local iconW = tab.icon and 16 or 0
local iconTextGap = tab.icon and 8 or 0
local padding = 16
local btnWidth = padding + iconW + iconTextGap + textW + padding
btnTab:Dock(LEFT)
btnTab:DockMargin(0, 0, 6, 0)
btnTab:SetTall(34)
btnTab:SetWide(btnWidth)
else
btnTab:Dock(TOP)
btnTab:DockMargin(0, 0, 0, 6)
btnTab:SetTall(34)
end
btnTab:SetText('')
btnTab.DoClick = function()
self.tabs[self.active_id].pan:SetVisible(false)
tab.pan:SetVisible(true)
self.active_id = id
if self.tab_style == 'modern' and tab._btn then
self.indicator_target_x = tab._btn:GetX()
self.indicator_target_w = tab._btn:GetWide()
end
Mantle.func.sound()
end
btnTab.DoRightClick = function()
local dm = Mantle.ui.derma_menu()
for k, tab in pairs(self.tabs) do
dm:AddOption(tab.name, function()
self.tabs[self.active_id].pan:SetVisible(false)
tab.pan:SetVisible(true)
self.active_id = k
if self.tab_style == 'modern' and tab._btn then
self.indicator_target_x = tab._btn:GetX()
self.indicator_target_w = tab._btn:GetWide()
end
end, tab.icon)
end
end
btnTab.Paint = function(s, w, h)
local isActive = self.active_id == id
local colorText = isActive and Mantle.color.theme or Mantle.color.text
local colorIcon = isActive and Mantle.color.theme or color_white
if self.tab_style == 'modern' then
if s:IsHovered() then
RNDX.Draw(16, 0, 0, w, h, color_btn_hovered, RNDX.SHAPE_IOS + (isActive and RNDX.NO_BL + RNDX.NO_BR or 0))
end
local padding = 16
local iconW = tab.icon and 16 or 0
local iconTextGap = tab.icon and 8 or 0
local textX = padding + (iconW > 0 and (iconW + iconTextGap) or 0)
if tab.icon then
RNDX.DrawMaterial(0, padding, (h - 16) * 0.5, 16, 16, colorIcon, tab.icon)
end
draw.SimpleText(tab.name, 'Fated.18', textX, h * 0.5, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
else
if s:IsHovered() then
RNDX.Draw(24, 0, 0, w, h, color_btn_hovered, RNDX.SHAPE_IOS)
end
draw.SimpleText(tab.name, 'Fated.18', 34, h * 0.5 - 1, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
if tab.icon then
RNDX.DrawMaterial(0, 9, 9, 16, 16, colorIcon, tab.icon)
else
RNDX.Draw(24, 9, 9, 16, 16, colorIcon, RNDX.SHAPE_IOS)
end
end
end
end
self.panel_tabs.Paint = function(s, w, h)
if self.tab_style == 'modern' and self.indicator_w > 0 then
RNDX.Draw(0, self.indicator_x, h - self.indicator_height, self.indicator_w, self.indicator_height, Mantle.color.theme)
end
end
end
function PANEL:PerformLayout(w, h)
if self.tab_style == 'modern' then
self.panel_tabs:Dock(TOP)
self.panel_tabs:DockMargin(0, 0, 0, 4)
self.panel_tabs:SetTall(self.tab_height)
else
self.panel_tabs:Dock(LEFT)
self.panel_tabs:DockMargin(0, 0, 4, 0)
self.panel_tabs:SetWide(190)
end
self.content:Dock(FILL)
if self.tab_style == 'modern' then
local activeBtn = nil
if self.tabs[self.active_id] then
activeBtn = self.tabs[self.active_id]._btn
end
if IsValid(activeBtn) then
local bx, by = activeBtn:GetPos()
local bw, bh = activeBtn:GetSize()
self.indicator_target_x = bx
self.indicator_target_w = bw
if self.indicator_w == 0 and self.indicator_x == 0 then
self.indicator_x = self.indicator_target_x
self.indicator_w = self.indicator_target_w
end
else
self.indicator_target_x = 0
self.indicator_target_w = 0
end
end
end
vgui.Register('MantleTabs', PANEL, 'Panel')

View File

@@ -0,0 +1,257 @@
local PANEL = {}
local function utf8_iter(s)
return s:gmatch('([%z\1-\127\194-\244][\128-\191]*)')
end
function PANEL:Init()
self.text = ''
self.font = 'Fated.18'
self.color = Mantle.color.text
self.align = TEXT_ALIGN_LEFT
self.valign = 'top'
self.padding = 6
self._lines = {''}
self._line_h = 16
self._last_w, self._last_h = 0, 0
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
end
function PANEL:SetText(text)
self.text = text
self:InvalidateLayout()
end
function PANEL:GetText()
return self.text
end
function PANEL:SetFont(font)
self.font = font
self:InvalidateLayout()
end
function PANEL:SetColor(col)
self.color = col
self:InvalidateLayout()
end
function PANEL:SetAlign(a)
self.align = a
self:InvalidateLayout()
end
function PANEL:SetVAlign(v)
if v == 'top' or v == 'center' or v == 'bottom' then
self.valign = v
self:InvalidateLayout()
end
end
function PANEL:SetPadding(p)
self.padding = p
self:InvalidateLayout()
end
local function GetTextSize(font, txt)
surface.SetFont(font)
local ok, w, h = pcall(surface.GetTextSize, txt)
if not ok then return 0, 16 end
if not h or type(h) != 'number' or h <= 0 then
local ok2, _, h2 = pcall(surface.GetTextSize, 'Ay')
if ok2 and type(h2) == 'number' and h2 > 0 then
h = h2
else
h = 16
end
end
return tonumber(w) or 0, h
end
local function WrapAndEllipsize(text, font, maxw, max_lines)
if maxw <= 0 or max_lines <= 0 then
return {''}, true
end
local ell = '...'
local ell_w = GetTextSize(font, ell)
local paragraphs = string.Explode('\n', text)
local lines = {}
local truncated = false
for pi = 1, #paragraphs do
local para = paragraphs[pi]
if para == '' then
table.insert(lines, '')
if #lines >= max_lines then
truncated = (pi < #paragraphs)
break
end
else
local words = string.Explode(' ', para)
local i = 1
local cur = ''
while i <= #words do
local w = words[i]
local test = (cur == '') and w or (cur .. ' ' .. w)
local tw = GetTextSize(font, test)
if tw <= maxw then
cur = test
i = i + 1
else
if cur != '' then
table.insert(lines, cur)
cur = ''
if #lines >= max_lines then break end
else
local part = ''
for ch in utf8_iter(w) do
local tpart = part .. ch
local tw2 = GetTextSize(font, tpart)
if tw2 <= maxw then
part = tpart
else
if part == '' then
local ch_w = GetTextSize(font, ch)
if ch_w <= maxw then
table.insert(lines, ch)
else
table.insert(lines, ch)
end
else
table.insert(lines, part)
local ch_w = GetTextSize(font, ch)
if ch_w <= maxw then
part = ch
else
table.insert(lines, ch)
part = ''
end
end
part = part or ''
if #lines >= max_lines then break end
end
end
if #lines >= max_lines then break end
cur = part
i = i + 1
end
end
end
if #lines < max_lines and cur != '' then
table.insert(lines, cur)
end
if #lines >= max_lines then
if i <= #words or pi < #paragraphs then
truncated = true
end
if truncated then
local rest = ''
if i <= #words then
for j = i, #words do
rest = rest .. words[j] .. (j < #words and ' ' or '')
end
end
if pi < #paragraphs then
for pj = pi + 1, #paragraphs do
if paragraphs[pj] != '' then
rest = rest .. (rest != '' and ' ' or '') .. paragraphs[pj]
end
end
end
if #lines == 0 then
lines[1] = ''
end
local lastIdx = #lines
local prev = lines[lastIdx] or ''
if rest == '' then
rest = prev
else
rest = prev .. (rest != '' and (' ' .. rest) or '')
end
local res = ''
for ch in utf8_iter(rest) do
local t = res .. ch
local tw = GetTextSize(font, t)
if tw + ell_w <= maxw then
res = t
else
break
end
end
lines[lastIdx] = (res == '' and ell) or (res .. ell)
end
break
end
end
end
if #lines == 0 then lines[1] = '' end
return lines, truncated
end
function PANEL:_rebuild_if_needed()
local w, h = self:GetSize()
if w == self._last_w and h == self._last_h then return end
self._last_w, self._last_h = w, h
local avail_w_df = math.max(1, w - self.padding * 2)
local _, line_h = GetTextSize(self.font, 'Ay')
self._line_h = line_h or 16
local max_lines = math.max(1, math.floor((h - self.padding * 2) / self._line_h))
local lines, trunc = WrapAndEllipsize(self.text, self.font, avail_w_df, max_lines)
self._lines = lines
self._truncated = trunc
end
function PANEL:PerformLayout(w, h)
self:_rebuild_if_needed()
end
function PANEL:Paint(w, h)
self:_rebuild_if_needed()
local lines = self._lines or {''}
local line_h = self._line_h or 16
local total_h = #lines * line_h
local start_y = self.padding
if self.valign == 'center' then
start_y = math.floor((h - total_h) / 2)
elseif self.valign == 'bottom' then
start_y = h - self.padding - total_h
end
surface.SetFont(self.font)
for i = 1, #lines do
local line = lines[i]
local y = start_y + (i - 1) * line_h
local x = self.padding
if self.align == TEXT_ALIGN_CENTER then
x = w * 0.5
elseif self.align == TEXT_ALIGN_RIGHT then
x = w - self.padding
end
draw.SimpleText(line, self.font, x, y, self.color, self.align, TEXT_ALIGN_TOP)
end
return true
end
vgui.Register('MantleText', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,35 @@
local color_accept = Color(35, 103, 51)
function Mantle.ui.text_box(title, desc, func)
Mantle.ui.menu_text_box = vgui.Create('MantleFrame')
Mantle.ui.menu_text_box:SetSize(300, 134)
Mantle.ui.menu_text_box:Center()
Mantle.ui.menu_text_box:MakePopup()
Mantle.ui.menu_text_box:SetTitle(title)
Mantle.func.animate_appearance(Mantle.ui.menu_text_box, Mantle.ui.menu_text_box:GetWide(), Mantle.ui.menu_text_box:GetTall(), 0.3, 0.2, nil, 0.9)
Mantle.ui.menu_text_box:DockPadding(12, 30, 12, 12)
local entry = vgui.Create('MantleEntry', Mantle.ui.menu_text_box)
entry:Dock(TOP)
entry:SetTitle(desc)
local function apply_func()
func(entry:GetValue())
Mantle.ui.menu_text_box:Remove()
end
entry.OnEnter = function()
apply_func()
end
local btn_accept = vgui.Create('MantleBtn', Mantle.ui.menu_text_box)
btn_accept:Dock(BOTTOM)
btn_accept:SetTall(30)
btn_accept:SetTxt(Mantle.lang.get('mantle', 'apply'))
btn_accept:SetColorHover(color_accept)
btn_accept.DoClick = function()
Mantle.func.sound()
apply_func()
end
end

View File

@@ -0,0 +1,82 @@
--[[
* Mantle *
GitHub: https://github.com/darkfated/mantle
Author's telegram: @darkfated
]]--
local function RunScripts()
Mantle.run_cl('config/colors.lua')
Mantle.run_cl('core/func.lua')
Mantle.run_cl('core/vgui.lua')
Mantle.run_cl('core/legacy_vgui.lua')
Mantle.run_cl('core/menu.lua')
Mantle.run_cl('core/vgui_elements/button.lua')
Mantle.run_cl('core/vgui_elements/checkbox.lua')
Mantle.run_cl('core/vgui_elements/color_picker.lua')
Mantle.run_cl('core/vgui_elements/derma_menu.lua')
Mantle.run_cl('core/vgui_elements/entry.lua')
Mantle.run_cl('core/vgui_elements/frame.lua')
Mantle.run_cl('core/vgui_elements/player_selector.lua')
Mantle.run_cl('core/vgui_elements/radialpanel.lua')
Mantle.run_cl('core/vgui_elements/scrollpanel.lua')
Mantle.run_cl('core/vgui_elements/slidebox.lua')
Mantle.run_cl('core/vgui_elements/tabs.lua')
Mantle.run_cl('core/vgui_elements/textbox.lua')
Mantle.run_cl('core/vgui_elements/category.lua')
Mantle.run_cl('core/vgui_elements/combobox.lua')
Mantle.run_cl('core/vgui_elements/table.lua')
Mantle.run_cl('core/vgui_elements/text.lua')
Mantle.run_cl('modules/shadows.lua')
Mantle.run_cl('modules/material_url.lua')
Mantle.run_sh('modules/notify.lua')
Mantle.run_sh('modules/utf8.lua')
end
local function RunAddons()
local _, addonsName = file.Find('mantle_addons/*', 'LUA')
for _, addon in ipairs(addonsName) do
if file.Exists('mantle_addons/' .. addon .. '/init.lua', 'LUA') then
Mantle.run_sh('mantle_addons/' .. addon .. '/init.lua')
end
if file.Exists('mantle_addons/' .. addon .. '/lang.lua', 'LUA') then
local lang = Mantle.run_sh('mantle_addons/' .. addon .. '/lang.lua')
Mantle.lang.list[addon] = lang
end
end
Mantle.run_sh('core/lang.lua')
end
local function InitLib()
if SERVER then
resource.AddWorkshop('2924839375') -- DarkFated font
resource.AddWorkshop('3126986993') -- Mantle
end
local color_div = Color(168, 109, 236)
MsgC(color_white, '------------------\n')
MsgC(Color(0, 255, 0), '| Mantle LIBRARY |\n')
MsgC(color_white, '------------------\n')
Mantle = Mantle or {
lang = { list = {}, default = 'en' },
}
Mantle.run_cl = SERVER and AddCSLuaFile or include
Mantle.run_sv = SERVER and include or function() end
Mantle.run_sh = function(f)
local client = Mantle.run_cl(f)
local server = Mantle.run_sv(f)
return SERVER and server or client
end
RunScripts()
RunAddons()
end
InitLib()

View File

@@ -0,0 +1,40 @@
local file, Mat, Fetch, find = file, Material, http.Fetch, string.find
local errorMat = Mat('error')
local WebImageCache = {}
--[[
Функция для скачивания материала по ссылке и его кэшированного использования
]]--
function http.DownloadMaterial(url, path, callback, retry_count)
if WebImageCache[url] then
return callback(WebImageCache[url])
end
local dataPath = 'data/' .. path
if file.Exists(path, 'DATA') then
WebImageCache[url] = Mat(dataPath, 'noclamp mips')
callback(WebImageCache[url])
else
Fetch(url, function(img)
if !img or find(img, '<!DOCTYPE HTML>', 1, true) then
return callback(errorMat)
end
file.Write(path, img)
WebImageCache[url] = Mat(dataPath, 'noclamp mips')
callback(WebImageCache[url])
end, function()
if retry_count and retry_count > 0 then
retry_count = retry_count - 1
http.DownloadMaterial(url, path, callback, retry_count)
else
callback(errorMat)
end
end)
end
end

View File

@@ -0,0 +1,25 @@
if SERVER then
util.AddNetworkString('Mantle-Notify')
--[[
Функция для выведения в чат текста.
Можно выводить определённой цели информацию, либо всем, указав вместо pl - true
]]--
function Mantle.notify(pl, header_color, header, text)
net.Start('Mantle-Notify')
net.WriteString(header)
net.WriteColor(header_color)
net.WriteString(text)
if pl == true then net.Broadcast() else net.Send(pl) end
end
else
net.Receive('Mantle-Notify', function()
local headerText = net.ReadString()
local headerColor = net.ReadColor()
local headerColorDop = Color(headerColor.r + 10, headerColor.g + 10, headerColor.b + 10)
local text = net.ReadString()
chat.AddText(headerColorDop, '[', headerColor, headerText, headerColorDop, '] ', color_white, text)
chat.PlaySound()
end)
end

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
local math_sin = math.sin
local math_cos = math.cos
local math_rad = math.rad
local math_ceil = math.ceil
local function CreateBShadows()
BShadows = {}
local resStr = Mantle.func.sw .. Mantle.func.sh
BShadows.RenderTarget = GetRenderTarget('BShadows_original_' .. resStr, Mantle.func.sw, Mantle.func.sh)
BShadows.RenderTarget2 = GetRenderTarget('BShadows_shadow_' .. resStr, Mantle.func.sw, Mantle.func.sh)
BShadows.ShadowMaterial = CreateMaterial('BShadows', 'UnlitGeneric', {
['$translucent'] = 1,
['$vertexalpha'] = 1,
['alpha'] = 1
})
BShadows.ShadowMaterialGrayscale = CreateMaterial('BShadows_grayscale', 'UnlitGeneric', {
['$translucent'] = 1,
['$vertexalpha'] = 1,
['$alpha'] = 1,
['$color'] = '0 0 0',
['$color2'] = '0 0 0'
})
BShadows.BeginShadow = function()
render.PushRenderTarget(BShadows.RenderTarget)
render.OverrideAlphaWriteEnable(true, true)
render.Clear(0, 0, 0, 0)
render.OverrideAlphaWriteEnable(false, false)
cam.Start2D()
end
BShadows.EndShadow = function(intensity, spread, blur, opacity, direction, distance, bool_shadow_only)
opacity = opacity or 255
direction = direction or 0
distance = distance or 0
bool_shadow_only = bool_shadow_only or false
render.CopyRenderTargetToTexture(BShadows.RenderTarget2)
if blur > 0 then
render.OverrideAlphaWriteEnable(true, true)
render.BlurRenderTarget(BShadows.RenderTarget2, spread, spread, blur)
render.OverrideAlphaWriteEnable(false, false)
end
render.PopRenderTarget()
BShadows.ShadowMaterial:SetTexture('$basetexture', BShadows.RenderTarget)
BShadows.ShadowMaterialGrayscale:SetTexture('$basetexture', BShadows.RenderTarget2)
local xOffset = math_sin(math_rad(direction)) * distance
local yOffset = math_cos(math_rad(direction)) * distance
BShadows.ShadowMaterialGrayscale:SetFloat('$alpha', opacity / 255)
render.SetMaterial(BShadows.ShadowMaterialGrayscale)
for i = 1, math_ceil(intensity) do
render.DrawScreenQuadEx(xOffset, yOffset, Mantle.func.sw, Mantle.func.sh)
end
if !bool_shadow_only then
BShadows.ShadowMaterial:SetTexture('$basetexture', BShadows.RenderTarget)
render.SetMaterial(BShadows.ShadowMaterial)
render.DrawScreenQuad()
end
cam.End2D()
end
BShadows.DrawShadowTexture = function(texture, intensity, spread, blur, opacity, direction, distance, bool_shadow_only)
opacity = opacity or 255
direction = direction or 0
distance = distance or 0
bool_shadow_only = bool_shadow_only or false
render.CopyTexture(texture, BShadows.RenderTarget2)
if blur > 0 then
render.PushRenderTarget(BShadows.RenderTarget2)
render.OverrideAlphaWriteEnable(true, true)
render.BlurRenderTarget(BShadows.RenderTarget2, spread, spread, blur)
render.OverrideAlphaWriteEnable(false, false)
render.PopRenderTarget()
end
BShadows.ShadowMaterialGrayscale:SetTexture('$basetexture', BShadows.RenderTarget2)
local xOffset = math_sin(math_rad(direction)) * distance
local yOffset = math_cos(math_rad(direction)) * distance
BShadows.ShadowMaterialGrayscale:SetFloat('$alpha', opacity / 255)
render.SetMaterial(BShadows.ShadowMaterialGrayscale)
for i = 1, math_ceil(intensity) do
render.DrawScreenQuadEx(xOffset, yOffset, Mantle.func.sw, Mantle.func.sh)
end
if !bool_shadow_only then
BShadows.ShadowMaterial:SetTexture('$basetexture', texture)
render.SetMaterial(BShadows.ShadowMaterial)
render.DrawScreenQuad()
end
end
end
CreateBShadows()
hook.Add('OnScreenSizeChanged', 'Mantle.Shadows', function()
CreateBShadows()
end)

View File

@@ -0,0 +1,23 @@
if utf8 == nil then
utf8 = {
charpattern = '[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*'
}
end
local uc_lc = {['А']='а',['Б']='б',['В']='в',['Г']='г',['Д']='д',['Е']='е',['Ё']='ё',['Ж']='ж',['З']='з',['И']='и',['Й']='й',['К']='к',['Л']='л',['М']='м',['Н']='н',['О']='о',['П']='п',['Р']='р',['С']='с',['Т']='т',['У']='у',['Ф']='ф',['Х']='х',['Ц']='ц',['Ч']='ч',['Ш']='ш',['Щ']='щ',['Ъ']='ъ',['Ы']='ы',['Ь']='ь',['Э']='э',['Ю']='ю',['Я']='я'}
local lc_uc = {}
for uc, lc in pairs(uc_lc) do
lc_uc[lc] = uc
end
setmetatable(uc_lc, {__index = function(_, char) return char:lower() end})
setmetatable(lc_uc, {__index = function(_, char) return char:upper() end})
function utf8.lower(text)
return text:gsub(utf8.charpattern, uc_lc)
end
function utf8.upper(text)
return text:gsub(utf8.charpattern, lc_uc)
end

View File

@@ -0,0 +1,55 @@
local tabl = {}
tabl['en'] = {
apply = 'Apply',
table_wrong_args = 'MantleTable Error: Invalid number of arguments',
table_copy = 'Copy',
table_delete_row = 'Delete row',
player_title = 'Player Selector',
player_offline = 'Disconnected',
player_close = 'Close',
player_ping = 'ms',
frame_title = 'Title',
frame_alpha = 'Transparency',
frame_move_from_menu = 'Move from menu',
frame_close_window = 'Close window',
color_title = 'Color Picker',
color_cancel = 'Cancel',
color_select = 'Select',
btn_default = 'Button',
entry_default_placeholder = 'Enter text'
}
tabl['ru'] = {
apply = 'Применить',
table_wrong_args = 'MantleTable Error: Неверное количество аргументов',
table_copy = 'Копировать',
table_delete_row = 'Удалить строку',
player_title = 'Выбор игрока',
player_offline = 'Вышел',
player_close = 'Закрыть',
player_ping = 'мс',
frame_title = 'Заголовок',
frame_alpha = 'Прозрачность',
frame_move_from_menu = 'Передвижение из меню',
frame_close_window = 'Закрыть окно',
color_title = 'Выбор цвета',
color_cancel = 'Отмена',
color_select = 'Выбрать',
btn_default = 'Кнопка',
entry_default_placeholder = 'Введите текст'
}
return tabl

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B