├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── documentation.md
├── examples
├── README.md
├── data
│ ├── Montserrat-Regular.ttf
│ ├── SFPro.ttf
│ ├── SourceCodePro-Regular.ttf
│ ├── buttonicon.png
│ ├── checkicon.png
│ ├── codeicon.png
│ ├── codetextshowcase.gif
│ ├── colorpckicon.png
│ ├── colorpicker.png
│ ├── dlicon.png
│ ├── dragdropicon.png
│ ├── dropfileshowcase.gif
│ ├── embedwindowshowcase.gif
│ ├── homeicon.png
│ ├── homeiconw.png
│ ├── image.jpg
│ ├── imagebox.png
│ ├── imageicon.png
│ ├── iosshowcase.gif
│ ├── luffy.gif
│ ├── rqshowcase.gif
│ ├── showcase.gif
│ ├── spinnericon.png
│ ├── spinnershowcase.gif
│ ├── styledbutton.gif
│ ├── switchicon.png
│ ├── titlebarshowcase.gif
│ ├── toastshowcase.gif
│ ├── toggleswitch.gif
│ └── windowicon.png
├── example_ios.py
├── example_requesthandler.py
├── example_showcase.py
└── example_titlebar.py
├── pyqt5Custom
├── __init__.py
├── animation.py
├── codetextedit.py
├── colorpicker.py
├── dragdropfile.py
├── embedwindow.py
├── imagebox.py
├── requesthandler.py
├── segbtngroup.py
├── spinner.py
├── styledbutton.py
├── syntax
│ ├── cpp.json
│ └── python.json
├── syntaxhighlighter.py
├── themes
│ ├── default.json
│ ├── monokai.json
│ ├── oceanic.json
│ ├── one-dark.json
│ ├── one-light.json
│ └── zenburn.json
├── titlebar.py
├── toast.py
└── toggleswitch.py
└── stylingref.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | dev/
3 | dist/
4 | olddist/
5 | pyqt5Custom.egg-info/
6 | setup.py
7 | MANIFEST.in
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets
2 |
3 |
4 |
5 |
6 |
7 | More useful and stylish widgets for PyQt5 such as toggle switches, animated buttons, etc..
8 |
9 |
10 |
11 | ## Table of Contents
12 | - [Installing](#Installing)
13 | - [Usage](#Usage)
14 | - [Widgets](#Widgets)
15 | - [Examples](#References)
16 | - [Documentation](#References)
17 | - [Styling Reference](#References)
18 | - [Dependencies](#Dependencies)
19 | - [TODO](#Todo)
20 | - [License](#License)
21 |
22 | ## Installing
23 | Install using PIP (it might be `pip3` or `python3` depending on your platform)
24 | ```
25 | pip install pyqt5Custom
26 | ```
27 | or
28 | ```
29 | python -m pip install pyqt5Custom
30 | ```
31 | Also you can also use PySide2 instead of PyQt5 with just litte changes.
32 |
33 | ## Usage
34 | Just import `pyqt5Custom` and you're ready to go. You can check out [Examples](https://github.com/kadir014/pyqt5-custom-widgets/blob/main/examples/), one little example for StyledButton widget:
35 | ```py
36 | from pyqt5Custom import StyledButton
37 |
38 | ...
39 |
40 | btn = StyledButton(text="Hello!")
41 | btn.setStyleDict({
42 | "border-radius" : 20,
43 | "font-family" : "Helvetica",
44 | "font-size" : 17
45 | })
46 |
47 | @btn.clicked.connect
48 | def slot():
49 | print("Quitting!")
50 | app.exit()
51 |
52 | layout.addWidget(btn)
53 |
54 | ...
55 | ```
56 |
57 | ## Widgets
58 | | 
ToggleSwitch
[Documentation](documentation.md) | 
StyledButton
[Documentation](documentation.md) |
59 | | :---: | :---: |
60 | | 
**ImageBox**
[Documentation](documentation.md) | 
**ColorPicker**
[Documentation](documentation.md) |
61 | | 
**DragDropFile**
[Documentation](documentation.md) | 
**EmbedWindow**
[Documentation](documentation.md) |
62 | | 
**CodeTextEdit**
[Documentation](documentation.md) | 
**TitleBar**
[Documentation](documentation.md) |
63 | | 
**Spinner**
[Documentation](documentation.md) | 
**Toast**
[Documentation](documentation.md) |
64 |
65 | ## References
66 | - See [Examples](https://github.com/kadir014/pyqt5-custom-widgets/blob/main/examples/) page for examples
67 | - See [Documentation](documentation.md) page for documentation and detailed widget references
68 | - See [styling reference](stylingref.md) page for styling instructions on custom widget
69 |
70 | ## Dependencies
71 | - [PyQt5](https://pypi.org/project/PyQt5/)
72 | - [requests](https://pypi.org/project/requests/)
73 |
74 | ## TODO
75 | - [ ] Better styling and QSS support
76 | - [ ] Rework animations using [Qt's animation framework](https://doc.qt.io/qtforpython/overviews/animation-overview.html)
77 | - [ ] Optimize and complete ColorPicker widget
78 |
79 | ## License
80 | [GPL v3](LICENSE) © Kadir Aksoy
81 |
--------------------------------------------------------------------------------
/documentation.md:
--------------------------------------------------------------------------------
1 | # Welcome to PyQt5 Custom Widgets Documentation
2 | You can check out `showcase.py` in the [Examples](https://github.com/kadir014/pyqt5-custom-widgets/blob/main/examples/) to see a clear demonstration of all widgets. \
3 | Here is the list of currently implemented widgets
4 | - [ToggleSwitch](#ToggleSwitch)
5 | - [StyledButton](#StyledButton)
6 | - [ImageBox](#ImageBox)
7 | - [ColorPicker](#ColorPicker)
8 | - [DragDropFile](#DragDropFile)
9 | - [EmbedWindow](#DEmbedWindow)
10 | - [CodeTextEdit](#CodeTextEdit)
11 | - [TitleBar](#TitleBar)
12 | - [Spinner](#Spinner)
13 | - [Toast](#Toast)
14 |
15 | ## Other stuff
16 | Other stuff that the the library provides but are not mainly widgets. Some are tools, data classes, etc...
17 | - [RequestHandler](#RequestHandler)
18 | - [FileDetails](#FileDetails)
19 | - [Animation](#Animation)
20 | - [AnimationHandler](#AnimationHandler)
21 | - [ColorPreview](#ColorPreview)
22 | - [SyntaxHighlighter](#SyntaxHighlighter)
23 |
24 |
25 | ## ToggleSwitch
26 | `ToggleSwitch` is simply a modern looking checkbox. It has 3 styles named win10, ios and android. \
27 | \
28 | 
29 |
30 | #### Parameters
31 | - `text` (str) : Text that is shown next to the switch
32 | - `style` (str) : Style of the widget. "win10", "ios" or "android"
33 | - `on` (bool) : Whether the switch is toggled or not
34 |
35 | #### Methods
36 | - `isToggled()` (bool) : Returns switch's state
37 |
38 | #### Signals
39 | - `toggled` : This signal is emitted when switch's state gets changed
40 |
41 |
42 |
43 | ## StyledButton
44 | `StyledButton` is an animated push button. It has two styles named flat and hyper. \
45 | \
46 | 
47 |
48 | #### Parameters
49 | - `text` (str) : Text that is shown inside the button
50 | - `style` (str) : Style of the widget. "flat" or "hyper"
51 | - `icon` (str) : Filepath for the icon. This parameter is optional
52 | - `fixedBottom` (bool) : Is hyper styled button's bottom fixed or not. This parameter is optional
53 |
54 | #### Methods
55 | - `setIcon(filepath)` : Changes button's icon
56 | - `setDropShadow(bool)` : Enables or disables button's drop shadow effect
57 |
58 |
59 |
60 | ## ImageBox
61 | `ImageBox` is a container for images or animated GIFs \
62 | \
63 | 
64 |
65 | #### Parameters
66 | - `source` (str) : Filepath or URL of the source image/gif
67 | - `keepAspectRatio` (bool) : Protect aspect ratio. This parameter is optional
68 | - `smoothScale` (bool) : Transform the image smoothly. This parameter is optional
69 |
70 | #### Methods
71 | - `setIcon(filepath)` : Changes button's icon
72 |
73 |
74 | ## ColorPicker
75 | `ColorPicker` is not completed yet
76 | \
77 | 
78 |
79 |
80 |
81 | ## DragDropFile
82 | `DragDropFile` is an area to let user simply drop files onto it instead of browsing it on file dialog \
83 | \
84 | 
85 |
86 | #### Signals
87 | - `fileDropped(file)` FileDetails : This signal is emitted when a file is dropped on widget
88 |
89 | ## EmbedWindow
90 | `EmbedWindow` is a dialog window which is not a popup, it is embedded onto the parent widget \
91 | \
92 | 
93 |
94 | #### Parameters
95 | - `parent` (QWidget) : Parent widget of the window
96 |
97 | #### Attributes
98 | - `content` (QLayout) : Window's layout where you can add your own widgets
99 |
100 | #### Methods
101 | - `setTitle(title)` : Change title of the window
102 | - `setControlsVisible(bool)` : Change visibility of control buttons
103 |
104 | ## CodeTextEdit
105 | `CodeTextEdit` is simply a code editor. It's a multiline text area with syntax highlighting, it only supports few languages for now. For details see [SyntaxHighlighter](#SyntaxHighlighter) \
106 | \
107 | 
108 |
109 | #### Methods
110 | - `setTheme(theme)` : Change syntax coloring theme
111 | - `setLang(lang)` : Change language syntax
112 | - `loadFile(filepath)` : Load file content into editor
113 |
114 | ## TitleBar
115 | `TitleBar` lets the developer use a custom window title bar, this widget also provides window resizing controls (WIP) \
116 | \
117 | 
118 |
119 | #### Parameters
120 | - `parent` (QWidget) : Parent widget of the window
121 | - `title` (str) : Title of the window (Optional)
122 |
123 | #### Attributes
124 | - `closeButton` (StyledButton) : Close button
125 | - `maxButton` (StyledButton) : Maximize button
126 | - `minButton` (StyledButton) : Minimize button
127 |
128 | #### Methods
129 | - `setTitle(title)` : Change title
130 | - `title()` (str) : Get title
131 |
132 | ## Spinner
133 | `Spinner` is just a visual widget that has a spinning circle on it. You can use this as `icon` parameter on widgets that has icons\
134 | \
135 | 
136 |
137 | #### Parameters
138 | - `width` (float) : Width of the circle
139 | - `color` (QColor) : Color of the circle
140 |
141 | ## Toast
142 | `Toast` is notification widget that appears at the bottom of the window.\
143 | \
144 | 
145 |
146 | #### Parameters
147 | - `parent` (QWidget) : Parent widget
148 | - `text` (str) : Text of the widget (Optional)
149 | - `icon` (str/Spinner) : Icon of the widget (Optional)
150 | - `closeButton` (bool) : Whether to show the close button or not
151 |
152 | #### Methods
153 | - `rise(duration: int)` : Shows the toast notification for duration seconds
154 | - `fall()` : Hides the toast notification
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | # Other stuff
164 | Other stuff that the the library provides but are not mainly widgets. Some are tools, data classes, etc...
165 |
166 | ## RequestHandler
167 | `RequestHandler` is a thread (QThread) that can be used to handle HTTP requests while avoiding blocking Qt's event loop. You can see [Examples](https://github.com/kadir014/pyqt5-custom-widgets/blob/main/examples/) to see usage of this class.
168 |
169 | #### Methods
170 | - `newRequest(method, url, headers, data)`
171 | - `method` (str) : Request method
172 | - `url` (str) : Address where request will be sent at
173 | - `headers` (dict) : Request headers
174 | - `data` (dict) : Request data
175 |
176 | #### Signals
177 | - `requestResponded` : Emitted when a requeest in the current pool gets responded. Response is a [requests.Response](https://docs.python-requests.org/en/latest/api/#requests.Response) object
178 |
179 | ## FileDetails
180 | `FileDetails` object is a data class which is meant to be used by `DragDropFile` for `fileDropped` signal
181 |
182 | #### Attributes
183 | - `path` (str) : File's path
184 | - `content` (str) : Content of the file
185 | - `name` (str) : File's name
186 | - `pureName` (str) : File's name without the extension
187 | - `extension` (str) : File's extension
188 |
189 | ## Animation
190 | `Animation` is just a static class holding easing animation functions. This class is most likely going to be deprecated when I rework animations.
191 |
192 | ## AnimationHandler
193 | `AnimationHandler` animates widget's properties using `Animation` class's functions. This class is most likely going to be deprecated when I rework animations.
194 |
195 | ## ColorPreview
196 | `ColorPreview` is a widget to display some color. It can bee seen used next to ColorPicker example. But this widget is most likely going to be deprecated
197 |
198 | ## SyntaxHighlighter
199 | `SyntaxHighlighter` inherits `QSyntaxHighlighter`, it's only purpose is to serve `CodeTextEdit` widget. `pyqt5Custom` module currently supports only Python and C++ syntax highlighting.
200 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets Examples
2 | You can view or run example scripts to have a better understanding of the `pyqt5Custom` module and using the widgets. \
3 | Assets in the `data` folder is _**only for examples**_ and is not required to use the module alone. \
4 | \
5 | Quick look at example scripts:
6 | ## `example_showcase.py` is an example where most widgets and styling variatons are shown
7 |
8 | ## `example_requesthandler.py` is an example about using RequestHandler class
9 |
10 | ## `example_ios.py` is a replica of iOS design
11 |
12 | ## `example_titlebar.py` is an example about customizing titlebar
13 |
14 |
--------------------------------------------------------------------------------
/examples/data/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/examples/data/SFPro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/SFPro.ttf
--------------------------------------------------------------------------------
/examples/data/SourceCodePro-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/SourceCodePro-Regular.ttf
--------------------------------------------------------------------------------
/examples/data/buttonicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/buttonicon.png
--------------------------------------------------------------------------------
/examples/data/checkicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/checkicon.png
--------------------------------------------------------------------------------
/examples/data/codeicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/codeicon.png
--------------------------------------------------------------------------------
/examples/data/codetextshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/codetextshowcase.gif
--------------------------------------------------------------------------------
/examples/data/colorpckicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/colorpckicon.png
--------------------------------------------------------------------------------
/examples/data/colorpicker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/colorpicker.png
--------------------------------------------------------------------------------
/examples/data/dlicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/dlicon.png
--------------------------------------------------------------------------------
/examples/data/dragdropicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/dragdropicon.png
--------------------------------------------------------------------------------
/examples/data/dropfileshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/dropfileshowcase.gif
--------------------------------------------------------------------------------
/examples/data/embedwindowshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/embedwindowshowcase.gif
--------------------------------------------------------------------------------
/examples/data/homeicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/homeicon.png
--------------------------------------------------------------------------------
/examples/data/homeiconw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/homeiconw.png
--------------------------------------------------------------------------------
/examples/data/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/image.jpg
--------------------------------------------------------------------------------
/examples/data/imagebox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/imagebox.png
--------------------------------------------------------------------------------
/examples/data/imageicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/imageicon.png
--------------------------------------------------------------------------------
/examples/data/iosshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/iosshowcase.gif
--------------------------------------------------------------------------------
/examples/data/luffy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/luffy.gif
--------------------------------------------------------------------------------
/examples/data/rqshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/rqshowcase.gif
--------------------------------------------------------------------------------
/examples/data/showcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/showcase.gif
--------------------------------------------------------------------------------
/examples/data/spinnericon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/spinnericon.png
--------------------------------------------------------------------------------
/examples/data/spinnershowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/spinnershowcase.gif
--------------------------------------------------------------------------------
/examples/data/styledbutton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/styledbutton.gif
--------------------------------------------------------------------------------
/examples/data/switchicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/switchicon.png
--------------------------------------------------------------------------------
/examples/data/titlebarshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/titlebarshowcase.gif
--------------------------------------------------------------------------------
/examples/data/toastshowcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/toastshowcase.gif
--------------------------------------------------------------------------------
/examples/data/toggleswitch.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/toggleswitch.gif
--------------------------------------------------------------------------------
/examples/data/windowicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kadir014/pyqt5-custom-widgets/638df4ff7a54ae76169bac3c9e43804960ccfc26/examples/data/windowicon.png
--------------------------------------------------------------------------------
/examples/example_ios.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 | # #
5 | # This script is one of the pyqt5Custom examples #
6 |
7 |
8 | import sys
9 |
10 | from PyQt5.QtCore import Qt
11 | from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QVBoxLayout, QLabel, QFrame, QGridLayout
12 | from PyQt5.QtGui import QColor, QFontDatabase
13 |
14 | from pyqt5Custom import ToggleSwitch, StyledButton, ImageBox, ColorPicker, ColorPreview, DragDropFile, EmbedWindow, TitleBar, CodeTextEdit, SegmentedButtonGroup, Spinner, Toast
15 |
16 |
17 |
18 | class MainWindow(QWidget):
19 | def __init__(self):
20 | super().__init__()
21 | QFontDatabase.addApplicationFont("data/SFPro.ttf")
22 |
23 | self.setMinimumSize(150, 37)
24 | self.setGeometry(100, 100, 890, 610)
25 |
26 | self.setAutoFillBackground(True)
27 | p = self.palette()
28 | p.setColor(self.backgroundRole(), QColor(255, 255, 255))
29 | self.setPalette(p)
30 |
31 | self.layout = QVBoxLayout()
32 | self.layout.setAlignment(Qt.AlignTop)
33 | self.setLayout(self.layout)
34 | self.layout.setContentsMargins(0, 0, 0, 0)
35 |
36 | self.titlebar = TitleBar(self, title="iOS Design")
37 | self.titlebar.setStyleDict({
38 | "background-color" : (255, 255, 255),
39 | "font-size" : 17,
40 | "border-radius": 6,
41 | "font-family" : "SF Pro Display"
42 | })
43 |
44 | self.layout.addWidget(self.titlebar, alignment=Qt.AlignTop)
45 |
46 |
47 | self.conlyt = QVBoxLayout()
48 | self.conlyt.setSpacing(0)
49 | self.conlyt.setContentsMargins(70, 15, 70, 0)
50 | self.layout.addLayout(self.conlyt)
51 | h = QLabel("Heading")
52 | ah = QLabel("Alt heading")
53 | h.setContentsMargins(100, 0, 0, 0)
54 | ah.setContentsMargins(103, 0, 0, 0)
55 | self.conlyt.addWidget(h)
56 | self.conlyt.addWidget(ah)
57 |
58 | self.conlyt.addSpacing(90)
59 |
60 | self.btnslyt = QHBoxLayout()
61 | self.conlyt.addLayout(self.btnslyt)
62 | self.btnlyt = QVBoxLayout()
63 | self.btnlyt.setSpacing(16)
64 | self.btnslyt.addLayout(self.btnlyt)
65 |
66 | self.btnlyt2 = QVBoxLayout()
67 | self.btnslyt.addLayout(self.btnlyt2)
68 |
69 | self.btn2 = StyledButton("Default")
70 | self.btn2.setFixedSize(170, 54)
71 | self.btn2.anim_press.speed = 7.3
72 | self.btn2.setStyleDict({
73 | "background-color" : (0, 122, 255),
74 | "border-color" : (0, 122, 255),
75 | "border-radius" : 7,
76 | "color" : (255, 255, 255),
77 | "font-family" : "SF Pro Display",
78 | "font-size" : 21,
79 | })
80 | self.btn2.setStyleDict({
81 | "background-color" : (36, 141, 255),
82 | "border-color" : (36, 141, 255)
83 | }, "hover")
84 | self.btn2.setStyleDict({
85 | "background-color" : (130, 190, 255),
86 | "border-color" : (130, 190, 255),
87 | "color" : (255, 255, 255),
88 | }, "press")
89 |
90 | self.btnlyt.addWidget(self.btn2, alignment=Qt.AlignTop|Qt.AlignHCenter)
91 |
92 | self.btn3 = StyledButton("Quiet")
93 | self.btn3.setFixedSize(170, 54)
94 | self.btn3.anim_press.speed = 5
95 | self.btn3.setStyleDict({
96 | "background-color" : (255, 255, 255),
97 | "border-color" : (255, 255, 255),
98 | "border-radius" : 7,
99 | "color" : (0, 122, 255),
100 | "font-family" : "SF Pro Display",
101 | "font-size" : 21
102 | })
103 | self.btn3.setStyleDict({
104 | "color" : (107, 178, 255),
105 | }, "hover")
106 | self.btn3.setStyleDict({
107 | "color" : (227, 227, 255),
108 | }, "press")
109 |
110 | self.btnlyt.addWidget(self.btn3, alignment=Qt.AlignTop|Qt.AlignHCenter)
111 |
112 | self.btn1 = StyledButton("Outline")
113 | self.btn1.setFixedSize(170, 54)
114 | self.btn1.anim_press.speed = 5
115 | self.btn1.setStyleDict({
116 | "background-color" : (255, 255, 255),
117 | "border-color" : (0, 122, 255),
118 | "border-radius" : 7,
119 | "color" : (0, 122, 255),
120 | "font-family" : "SF Pro Display",
121 | "font-size" : 21
122 | })
123 | self.btn1.setStyleDict({
124 | "color" : (107, 178, 255),
125 | }, "hover")
126 | self.btn1.setStyleDict({
127 | "background-color" : (0, 122, 255),
128 | "color" : (255, 255, 255),
129 | }, "press")
130 |
131 | self.btnlyt.addWidget(self.btn1, alignment=Qt.AlignTop|Qt.AlignHCenter)
132 |
133 | self.btn4 = StyledButton("Flat")
134 | self.btn4.setFixedSize(170, 54)
135 | self.btn1.anim_press.speed = 5
136 | self.btn4.setStyleDict({
137 | "background-color" : (247, 247, 247),
138 | "border-color" : (0, 0, 0, 0),
139 | "border-radius" : 7,
140 | "color" : (0, 122, 255),
141 | "font-family" : "SF Pro Display",
142 | "font-size" : 21
143 | })
144 | self.btn4.setStyleDict({
145 | "background-color" : (242, 242, 242),
146 | }, "hover")
147 | self.btn4.setStyleDict({
148 | "background-color" : (230, 230, 230),
149 | }, "press")
150 |
151 | self.btnlyt.addWidget(self.btn4, alignment=Qt.AlignTop|Qt.AlignHCenter)
152 |
153 |
154 | self.btnlyt2.setAlignment(Qt.AlignTop)
155 | self.btnlyt2.addWidget(QLabel("Segmented Button Group (Horizontal)"))
156 | self.btnlyt2.addSpacing(10)
157 |
158 | self.segbg = SegmentedButtonGroup(radio=True)
159 | self.segbg.setFixedSize(349, 36)
160 | self.segbg .setStyleDict({
161 | "background-color" : (255, 255, 255),
162 | "border-color" : (0, 122, 255),
163 | "border-radius" : 7,
164 | "color" : (0, 122, 255),
165 | "font-family" : "SF Pro Display",
166 | "font-size" : 15,
167 | "font-subpixel-aa" : True
168 | })
169 | self.segbg .setStyleDict({
170 | "color" : (107, 178, 255),
171 | }, "hover")
172 | self.segbg .setStyleDict({
173 | "background-color" : (0, 122, 255),
174 | "color" : (255, 255, 255),
175 | }, "press")
176 | self.segbg .setStyleDict({
177 | "background-color" : (61, 154, 255),
178 | "color" : (255, 255, 255),
179 | }, "check-hover")
180 |
181 | self.segbg.addButton("First")
182 | self.segbg.addButton("Second")
183 | self.segbg.addButton("Third")
184 |
185 | self.btnlyt2.addWidget(self.segbg)
186 |
187 | self.btnlyt2.addSpacing(35)
188 | self.btnlyt2.addWidget(QLabel("Toggle Switches"))
189 | self.btnlyt2.addSpacing(10)
190 |
191 | self.tglyt = QHBoxLayout()
192 | self.tglyt.setAlignment(Qt.AlignLeft)
193 | self.btnlyt2.addLayout(self.tglyt)
194 |
195 | self.tgsw1 = ToggleSwitch(style="ios")
196 | self.tgsw2 = ToggleSwitch(style="ios")
197 | self.tgsw2.setEnabled(False)
198 |
199 | self.tgsw1.setFixedWidth(120)
200 | self.tgsw2.setFixedWidth(120)
201 |
202 | @self.tgsw1.toggled.connect
203 | def slot():
204 | if self.tgsw1.isToggled():
205 | self.tgsw2.setEnabled(True)
206 | else:
207 | self.tgsw2.setEnabled(False)
208 |
209 | self.tglyt.addWidget(self.tgsw1)
210 | self.tglyt.addWidget(self.tgsw2)
211 |
212 | self.btnlyt2.addSpacing(35)
213 | self.btnlyt2.addWidget(QLabel("Buttons with icons & spinners"))
214 | self.btnlyt2.addSpacing(10)
215 |
216 | self.ibtnlyt = QHBoxLayout()
217 | self.btnlyt2.addLayout(self.ibtnlyt)
218 |
219 | self.ibtn = StyledButton("Image Icon", icon="data/homeiconw.png")
220 | self.ibtn.setFixedSize(140, 45)
221 | self.ibtn.anim_press.speed = 7.3
222 | self.ibtn.setStyleDict({
223 | "background-color" : (0, 122, 255),
224 | "border-color" : (0, 122, 255),
225 | "border-radius" : 7,
226 | "color" : (255, 255, 255),
227 | "font-family" : "SF Pro Display",
228 | "font-size" : 18,
229 | })
230 | self.ibtn.setStyleDict({
231 | "background-color" : (36, 141, 255),
232 | "border-color" : (36, 141, 255)
233 | }, "hover")
234 | self.ibtn.setStyleDict({
235 | "background-color" : (130, 190, 255),
236 | "border-color" : (130, 190, 255),
237 | "color" : (255, 255, 255),
238 | }, "press")
239 |
240 | s = Spinner(1.5, QColor(255, 255, 255))
241 | s.animType = 0
242 | s.speed = 2
243 | self.ibtn2 = StyledButton("Spinner Icon", icon=s)
244 | self.ibtn2.setFixedSize(140, 45)
245 | self.ibtn2.anim_press.speed = 7.3
246 | self.ibtn2.setStyleDict({
247 | "background-color" : (0, 122, 255),
248 | "border-color" : (0, 122, 255),
249 | "border-radius" : 7,
250 | "color" : (255, 255, 255),
251 | "font-family" : "SF Pro Display",
252 | "font-size" : 18,
253 | })
254 | self.ibtn2.setStyleDict({
255 | "background-color" : (36, 141, 255),
256 | "border-color" : (36, 141, 255)
257 | }, "hover")
258 | self.ibtn2.setStyleDict({
259 | "background-color" : (130, 190, 255),
260 | "border-color" : (130, 190, 255),
261 | "color" : (255, 255, 255),
262 | }, "press")
263 |
264 | self.ibtnlyt.addWidget(self.ibtn)
265 | self.ibtnlyt.addWidget(self.ibtn2)
266 |
267 | self.ibtnl = StyledButton("Loading", icon=Spinner(1.5, QColor(255, 255, 255)))
268 | self.ibtnl.setMinimumSize(118, 38)
269 | self.ibtnl.anim_press.speed = 7.3
270 | self.ibtnl.setStyleDict({
271 | "background-color" : (52, 199, 89),
272 | "border-color" : (2, 199, 89),
273 | "border-radius" : 39,
274 | "color" : (255, 255, 255),
275 | "font-family" : "SF Pro Display",
276 | "font-size" : 18,
277 | })
278 | self.ibtnl.setStyleDict({
279 | "background-color" : (47, 212, 119),
280 | "border-color" : (47, 212, 119)
281 | }, "hover")
282 | self.ibtnl.setStyleDict({
283 | "background-color" : (89, 227, 149),
284 | "border-color" : (89, 227, 149),
285 | "color" : (255, 255, 255),
286 | }, "press")
287 |
288 | self.btnlyt2.addSpacing(15)
289 | self.btnlyt2.addWidget(self.ibtnl, alignment=Qt.AlignHCenter)
290 |
291 | self.toast = Toast(self, text="Simple Toast with Spinner", icon=Spinner(1.3, QColor(255, 255, 255)))
292 | self.toast.setFixedWidth(287)
293 | self.toast.setStyleDict({
294 | "font-family" : "SF Pro Display",
295 | "font-size" : 17
296 | })
297 |
298 | self.ibtnl.clicked.connect(lambda: self.toast.rise(3))
299 |
300 |
301 |
302 | if __name__ == "__main__":
303 | app = QApplication(sys.argv)
304 |
305 | mw = MainWindow()
306 | mw.show()
307 |
308 | sys.exit(app.exec_())
309 |
--------------------------------------------------------------------------------
/examples/example_requesthandler.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 | # #
5 | # This script is one of the pyqt5Custom examples #
6 |
7 |
8 | import sys
9 | import time
10 |
11 | from PyQt5.QtCore import Qt
12 | from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit
13 | from PyQt5.QtGui import QColor, QFontDatabase
14 |
15 | from pyqt5Custom import StyledButton, TitleBar, Spinner, RequestHandler
16 |
17 |
18 |
19 | class MainWindow(QWidget):
20 | def __init__(self):
21 | super().__init__()
22 |
23 | self.setFixedSize(400, 400)
24 | self.setGeometry(100, 100, 400, 400)
25 | self.setWindowTitle("RequestHandler Example")
26 |
27 | QFontDatabase.addApplicationFont("data/SFPro.ttf")
28 |
29 | self.setAutoFillBackground(True)
30 | p = self.palette()
31 | p.setColor(self.backgroundRole(), QColor(255, 255, 255))
32 | self.setPalette(p)
33 |
34 | self.layout = QVBoxLayout()
35 | self.layout.setAlignment(Qt.AlignTop)
36 | self.setLayout(self.layout)
37 | self.layout.setContentsMargins(0, 0, 0, 0)
38 |
39 | # Creating and starting the RequestHandler object
40 | self.rh = RequestHandler()
41 | self.rh.start()
42 |
43 | self.btn = StyledButton("GET Request", icon="data/dlicon.png")
44 | self.btn.setIconSize(33, 33)
45 | self.btn.setMinimumSize(178, 44)
46 | self.btn.setStyleDict({
47 | "background-color" : (52, 199, 89),
48 | "border-color" : (2, 199, 89),
49 | "border-radius" : 39,
50 | "color" : (255, 255, 255),
51 | "font-family" : "SF Pro Display",
52 | "font-size" : 21,
53 | })
54 | self.btn.setStyleDict({
55 | "background-color" : (47, 212, 119),
56 | "border-color" : (47, 212, 119)
57 | }, "hover")
58 | self.btn.setStyleDict({
59 | "background-color" : (89, 227, 149),
60 | "border-color" : (89, 227, 149),
61 | "color" : (255, 255, 255),
62 | }, "press")
63 |
64 | self.layout.addSpacing(80)
65 | self.layout.addWidget(self.btn, alignment=Qt.AlignHCenter)
66 |
67 | self.search = QLineEdit()
68 | self.search.setFixedSize(220, 41)
69 | self.search.setStyleSheet("padding: 10px; padding-bottom: 3px; font-size:16px; font-family: SF Pro Display; border: none; border-bottom: 2px solid rgb(0,122,255);")
70 | self.search.setText("https://github.com/")
71 | self.layout.addWidget(self.search, alignment=Qt.AlignHCenter)
72 |
73 | self.layout.addSpacing(25)
74 |
75 | self.panel = QWidget()
76 | self.panel.setStyleSheet("font-size: 17px; font-family: SF Pro Default;")
77 | self.panel.setFixedWidth(350)
78 | self.panellyt = QVBoxLayout()
79 | self.panellyt.setContentsMargins(0, 10, 0, 10)
80 | self.panel.setLayout(self.panellyt)
81 | self.layout.addWidget(self.panel, alignment=Qt.AlignHCenter)
82 |
83 | self.panel.setAutoFillBackground(True)
84 | p = self.panel.palette()
85 | p.setColor(self.backgroundRole(), QColor(250, 250, 250))
86 | self.panel.setPalette(p)
87 |
88 | self.row1 = QHBoxLayout()
89 | self.panellyt.addLayout(self.row1)
90 | self.row1.setContentsMargins(10, 0, 10, 0)
91 |
92 | self.row2wdt = QWidget()
93 | self.row2 = QHBoxLayout()
94 | self.row2wdt.setLayout(self.row2)
95 | self.panellyt.addWidget(self.row2wdt)
96 | self.row2.setContentsMargins(10, 8, 10, 8)
97 |
98 | self.row2wdt.setAutoFillBackground(True)
99 | p = self.row2wdt.palette()
100 | p.setColor(self.backgroundRole(), QColor(222, 222, 222))
101 | self.row2wdt.setPalette(p)
102 |
103 | self.row3 = QHBoxLayout()
104 | self.panellyt.addLayout(self.row3)
105 | self.row3.setContentsMargins(10, 0, 10, 0)
106 |
107 | self.info_status_d = QLabel("HTTP Code:")
108 | self.info_status = QLabel("0")
109 | self.row1.addWidget(self.info_status_d, alignment=Qt.AlignLeft)
110 | self.row1.addWidget(self.info_status, alignment=Qt.AlignRight)
111 |
112 | self.info_conlength_d = QLabel("Response content size:")
113 | self.info_conlength = QLabel("0 MB")
114 | self.row2.addWidget(self.info_conlength_d, alignment=Qt.AlignLeft)
115 | self.row2.addWidget(self.info_conlength, alignment=Qt.AlignRight)
116 |
117 | self.info_elaps_d = QLabel("Elapsed time:")
118 | self.info_elaps = QLabel("0 ms")
119 | self.row3.addWidget(self.info_elaps_d, alignment=Qt.AlignLeft)
120 | self.row3.addWidget(self.info_elaps, alignment=Qt.AlignRight)
121 |
122 |
123 | @self.btn.clicked.connect
124 | def slot():
125 | self.btn.setIcon(Spinner(2.4, QColor(255, 255, 255)))
126 | # Add new GET request to the pool
127 | self.rh.newRequest("GET", self.search.text())
128 |
129 | @self.rh.requestResponsed.connect
130 | def slot(response):
131 | self.btn.setIcon("data/dlicon.png")
132 | self.btn.setIconSize(28, 28)
133 |
134 | self.info_status.setText(str(response.status_code))
135 | l = len(response.content) / 1048576
136 | if l > 1: l = int(l)
137 |
138 | self.info_conlength.setText(f"{l:.2} MB")
139 | self.info_elaps.setText(str(response.elapsed.microseconds//1000)+" ms")
140 |
141 |
142 |
143 | if __name__ == "__main__":
144 | app = QApplication(sys.argv)
145 |
146 | mw = MainWindow()
147 | mw.show()
148 |
149 | sys.exit(app.exec_())
150 |
--------------------------------------------------------------------------------
/examples/example_showcase.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets - Showcase Demo #
2 | # Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 | # #
5 | # This script is one of the pyqt5Custom examples #
6 |
7 |
8 | import sys
9 |
10 | from PyQt5.QtCore import Qt
11 | from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QVBoxLayout, QLabel, QFrame
12 | from PyQt5.QtGui import QColor, QFontDatabase
13 |
14 | from pyqt5Custom import ToggleSwitch, StyledButton, ImageBox, ColorPicker, ColorPreview, DragDropFile, EmbedWindow, TitleBar, CodeTextEdit, Spinner, SegmentedButtonGroup
15 |
16 |
17 |
18 | class MainWindow(QWidget):
19 | def __init__(self):
20 | super().__init__()
21 |
22 | w, h = 950, 630
23 | self.setMinimumSize(w//6, h//6)
24 | self.setGeometry(100, 100, w, h)
25 |
26 | QFontDatabase.addApplicationFont("data/Montserrat-Regular.ttf")
27 | QFontDatabase.addApplicationFont("data/SourceCodePro-Regular.ttf")
28 | QFontDatabase.addApplicationFont("data/SFPro.ttf")
29 |
30 | self.setStyleSheet("QLabel {font-family: Montserrat-Regular;}")
31 |
32 | self.setAutoFillBackground(True)
33 | p = self.palette()
34 | p.setColor(self.backgroundRole(), QColor(254, 254, 254))
35 | self.setPalette(p)
36 |
37 |
38 | self.mainlayout = QVBoxLayout()
39 | self.mainlayout.setSpacing(0)
40 | self.mainlayout.setContentsMargins(0, 0, 0, 0)
41 | self.setLayout(self.mainlayout)
42 |
43 | self.titlebar = TitleBar(self, title="PyQt5 Custom Widgets Showcase")
44 | self.titlebar.setStyleDict({
45 | "color" : (0, 0, 0, 0),
46 | "font-family" : "Montserrat-Regular",
47 | "font-size" : 14
48 | })
49 | self.mainlayout.addWidget(self.titlebar)
50 |
51 | self.layout = QHBoxLayout()
52 | self.layout.setContentsMargins(0, 0, 0, 0)
53 | self.mainlayout.addLayout(self.layout)
54 |
55 | # Side menu
56 | self.menu_wdt = QWidget()
57 | self.menu_wdt.setFixedWidth(90)
58 | self.menu = QVBoxLayout()
59 | self.menu.setAlignment(Qt.AlignTop)
60 | self.menu.setSpacing(5)
61 | self.menu_wdt.setLayout(self.menu)
62 | self.layout.addWidget(self.menu_wdt)
63 |
64 | self.menu_wdt.setAutoFillBackground(True)
65 | p = self.menu_wdt.palette()
66 | p.setColor(self.menu_wdt.backgroundRole(), QColor(245, 66, 126))
67 | self.menu_wdt.setPalette(p)
68 |
69 | self.menubtn1 = StyledButton(icon="data/switchicon.png")
70 | self.menubtn2 = StyledButton(icon="data/buttonicon.png")
71 | self.menubtn3 = StyledButton(icon="data/imageicon.png")
72 | self.menubtn4 = StyledButton(icon="data/colorpckicon.png")
73 | self.menubtn5 = StyledButton(icon="data/dragdropicon.png")
74 | self.menubtn6 = StyledButton(icon="data/windowicon.png")
75 | self.menubtn7 = StyledButton(icon="data/codeicon.png")
76 | self.menubtn8 = StyledButton(icon="data/spinnericon.png")
77 |
78 | w, i = 60, 30
79 |
80 | self.menubtn1.setFixedSize(w, w)
81 | self.menubtn1.setIconSize(40, 40)
82 | self.menubtn1.setStyleDict({
83 | "background-color" : (245, 66, 126),
84 | "border-color" : (0, 0, 0, 0),
85 | "border-radius" : 14
86 | })
87 | self.menubtn1.setStyleDict({
88 | "background-color" : (245, 127, 167),
89 | }, "hover")
90 | self.menubtn1.setStyleDict({
91 | "background-color" : (255, 156, 189),
92 | }, "press")
93 |
94 | self.menubtn2.setFixedSize(w, w)
95 | self.menubtn2.setIconSize(i, i)
96 | self.menubtn2.copyStyleDict(self.menubtn1)
97 |
98 | self.menubtn3.setFixedSize(w, w)
99 | self.menubtn3.setIconSize(i, i)
100 | self.menubtn3.copyStyleDict(self.menubtn1)
101 |
102 | self.menubtn4.setFixedSize(w, w)
103 | self.menubtn4.setIconSize(i, i)
104 | self.menubtn4.copyStyleDict(self.menubtn1)
105 |
106 | self.menubtn5.setFixedSize(w, w)
107 | self.menubtn5.setIconSize(i, i)
108 | self.menubtn5.copyStyleDict(self.menubtn1)
109 |
110 | self.menubtn6.setFixedSize(w, w)
111 | self.menubtn6.setIconSize(i, i)
112 | self.menubtn6.copyStyleDict(self.menubtn1)
113 |
114 | self.menubtn7.setFixedSize(w, w)
115 | self.menubtn7.setIconSize(i, i)
116 | self.menubtn7.copyStyleDict(self.menubtn1)
117 |
118 | self.menubtn8.setFixedSize(w, w)
119 | self.menubtn8.setIconSize(i, i)
120 | self.menubtn8.copyStyleDict(self.menubtn1)
121 |
122 | self.menu.addSpacing(10)
123 | self.menu.addWidget(self.menubtn1, alignment=Qt.AlignTop|Qt.AlignCenter)
124 | self.menu.addWidget(self.menubtn2, alignment=Qt.AlignTop|Qt.AlignCenter)
125 | self.menu.addWidget(self.menubtn3, alignment=Qt.AlignTop|Qt.AlignCenter)
126 | self.menu.addWidget(self.menubtn4, alignment=Qt.AlignTop|Qt.AlignCenter)
127 | self.menu.addWidget(self.menubtn5, alignment=Qt.AlignTop|Qt.AlignCenter)
128 | self.menu.addWidget(self.menubtn6, alignment=Qt.AlignTop|Qt.AlignCenter)
129 | self.menu.addWidget(self.menubtn7, alignment=Qt.AlignTop|Qt.AlignCenter)
130 | self.menu.addWidget(self.menubtn8, alignment=Qt.AlignTop|Qt.AlignCenter)
131 |
132 | @self.menubtn1.clicked.connect
133 | def slot():
134 | self.togglesw_showcase_wdt.show()
135 | self.stbtn_showcase_wdt.hide()
136 | self.imgbox_showcase_wdt.hide()
137 | self.colorpk_showcase_wdt.hide()
138 | self.dropfile_showcase_wdt.hide()
139 | self.emwin_showcase_wdt.hide()
140 | self.codeedit_showcase_wdt.hide()
141 | self.spinner_showcase_wdt.hide()
142 |
143 | @self.menubtn2.clicked.connect
144 | def slot():
145 | self.togglesw_showcase_wdt.hide()
146 | self.stbtn_showcase_wdt.show()
147 | self.imgbox_showcase_wdt.hide()
148 | self.colorpk_showcase_wdt.hide()
149 | self.dropfile_showcase_wdt.hide()
150 | self.emwin_showcase_wdt.hide()
151 | self.codeedit_showcase_wdt.hide()
152 | self.spinner_showcase_wdt.hide()
153 |
154 | @self.menubtn3.clicked.connect
155 | def slot():
156 | self.togglesw_showcase_wdt.hide()
157 | self.stbtn_showcase_wdt.hide()
158 | self.imgbox_showcase_wdt.show()
159 | self.colorpk_showcase_wdt.hide()
160 | self.dropfile_showcase_wdt.hide()
161 | self.emwin_showcase_wdt.hide()
162 | self.codeedit_showcase_wdt.hide()
163 | self.spinner_showcase_wdt.hide()
164 |
165 | @self.menubtn4.clicked.connect
166 | def slot():
167 | self.togglesw_showcase_wdt.hide()
168 | self.stbtn_showcase_wdt.hide()
169 | self.imgbox_showcase_wdt.hide()
170 | self.colorpk_showcase_wdt.show()
171 | self.dropfile_showcase_wdt.hide()
172 | self.emwin_showcase_wdt.hide()
173 | self.codeedit_showcase_wdt.hide()
174 | self.spinner_showcase_wdt.hide()
175 |
176 | @self.menubtn5.clicked.connect
177 | def slot():
178 | self.togglesw_showcase_wdt.hide()
179 | self.stbtn_showcase_wdt.hide()
180 | self.imgbox_showcase_wdt.hide()
181 | self.colorpk_showcase_wdt.hide()
182 | self.dropfile_showcase_wdt.show()
183 | self.emwin_showcase_wdt.hide()
184 | self.codeedit_showcase_wdt.hide()
185 | self.spinner_showcase_wdt.hide()
186 |
187 | @self.menubtn6.clicked.connect
188 | def slot():
189 | self.togglesw_showcase_wdt.hide()
190 | self.stbtn_showcase_wdt.hide()
191 | self.imgbox_showcase_wdt.hide()
192 | self.colorpk_showcase_wdt.hide()
193 | self.dropfile_showcase_wdt.hide()
194 | self.emwin_showcase_wdt.show()
195 | self.codeedit_showcase_wdt.hide()
196 | self.spinner_showcase_wdt.hide()
197 |
198 | @self.menubtn7.clicked.connect
199 | def slot():
200 | self.togglesw_showcase_wdt.hide()
201 | self.stbtn_showcase_wdt.hide()
202 | self.imgbox_showcase_wdt.hide()
203 | self.colorpk_showcase_wdt.hide()
204 | self.dropfile_showcase_wdt.hide()
205 | self.emwin_showcase_wdt.hide()
206 | self.codeedit_showcase_wdt.show()
207 | self.spinner_showcase_wdt.hide()
208 |
209 | @self.menubtn8.clicked.connect
210 | def slot():
211 | self.togglesw_showcase_wdt.hide()
212 | self.stbtn_showcase_wdt.hide()
213 | self.imgbox_showcase_wdt.hide()
214 | self.colorpk_showcase_wdt.hide()
215 | self.dropfile_showcase_wdt.hide()
216 | self.emwin_showcase_wdt.hide()
217 | self.codeedit_showcase_wdt.hide()
218 | self.spinner_showcase_wdt.show()
219 |
220 |
221 | ##################################################
222 | # #
223 | # ToggleSwitch Widget #
224 | # #
225 | ##################################################
226 |
227 | self.togglesw_showcase_wdt = QWidget()
228 | self.togglesw_showcase_wdt.setStyleSheet("font-family: Montserrat-Regular; font-size: 15px;")
229 | self.togglesw_showcase_lyt = QVBoxLayout()
230 | self.togglesw_showcase_lyt.setSpacing(5)
231 | self.togglesw_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
232 | self.togglesw_showcase_wdt.setLayout(self.togglesw_showcase_lyt)
233 | self.togglesw_showcase_lyt.addWidget(QLabel("Toggle Switch"),
234 | alignment=Qt.AlignHCenter)
235 | self.togglesw_showcase_lyt.addWidget(QLabel("These are styled & animated toggle switches."),
236 | alignment=Qt.AlignHCenter)
237 |
238 | self.togglesw_showcase_lyt.addSpacing(140)
239 |
240 | self.togglesw_content_lyt = QHBoxLayout()
241 | self.togglesw_showcase_lyt.addLayout(self.togglesw_content_lyt)
242 |
243 | self.togglesw_style1_lyt = QVBoxLayout()
244 | self.togglesw_style1_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
245 | self.togglesw_style1_lyt.setSpacing(15)
246 | self.togglesw_content_lyt.addLayout(self.togglesw_style1_lyt)
247 | self.togglesw_style2_lyt = QVBoxLayout()
248 | self.togglesw_style2_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
249 | self.togglesw_style2_lyt.setSpacing(15)
250 | self.togglesw_content_lyt.addLayout(self.togglesw_style2_lyt)
251 | self.togglesw_style3_lyt = QVBoxLayout()
252 | self.togglesw_style3_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
253 | self.togglesw_style3_lyt.setSpacing(15)
254 | self.togglesw_content_lyt.addLayout(self.togglesw_style3_lyt)
255 |
256 |
257 | # Windows 10 Styled Toggle Switch
258 | self.togglesw_style1_lyt.addWidget(QLabel("Windows 10"),
259 | alignment=Qt.AlignHCenter)
260 | self.togglesw_style1_lyt.addSpacing(35)
261 |
262 | self.togglesw_style1_off = ToggleSwitch(text="Off", style="win10")
263 | self.togglesw_style1_on = ToggleSwitch(text="On", style="win10", on=True)
264 | self.togglesw_style1_en = ToggleSwitch(text="Disabled off", style="win10")
265 | self.togglesw_style1_di = ToggleSwitch(text="Disabled on", style="win10", on=True)
266 | self.togglesw_style1_en.setEnabled(False)
267 | self.togglesw_style1_di.setEnabled(False)
268 |
269 | self.togglesw_style1_lyt.addWidget(self.togglesw_style1_off)
270 | self.togglesw_style1_lyt.addWidget(self.togglesw_style1_on)
271 | self.togglesw_style1_lyt.addWidget(self.togglesw_style1_en)
272 | self.togglesw_style1_lyt.addWidget(self.togglesw_style1_di)
273 |
274 |
275 | # iOS Styled Toggle Switch
276 | self.togglesw_style2_lyt.addWidget(QLabel("iOS"),
277 | alignment=Qt.AlignHCenter)
278 | self.togglesw_style2_lyt.addSpacing(35)
279 |
280 | self.togglesw_style2_off = ToggleSwitch(text="Off", style="ios")
281 | self.togglesw_style2_on = ToggleSwitch(text="On", style="ios", on=True)
282 | self.togglesw_style2_en = ToggleSwitch(text="Disabled off", style="ios")
283 | self.togglesw_style2_di = ToggleSwitch(text="Disabled on", style="ios", on=True)
284 | self.togglesw_style2_en.setEnabled(False)
285 | self.togglesw_style2_di.setEnabled(False)
286 |
287 | self.togglesw_style2_lyt.addWidget(self.togglesw_style2_off)
288 | self.togglesw_style2_lyt.addWidget(self.togglesw_style2_on)
289 | self.togglesw_style2_lyt.addWidget(self.togglesw_style2_en)
290 | self.togglesw_style2_lyt.addWidget(self.togglesw_style2_di)
291 |
292 |
293 | # Android Styled Toggle Switch
294 | self.togglesw_style3_lyt.addWidget(QLabel("Android"),
295 | alignment=Qt.AlignHCenter)
296 | self.togglesw_style3_lyt.addSpacing(35)
297 |
298 | self.togglesw_style3_off = ToggleSwitch(text="Off", style="android")
299 | self.togglesw_style3_on = ToggleSwitch(text="On", style="android", on=True)
300 | self.togglesw_style3_en = ToggleSwitch(text="Disabled off", style="android")
301 | self.togglesw_style3_di = ToggleSwitch(text="Disabled on", style="android", on=True)
302 | self.togglesw_style3_en.setEnabled(False)
303 | self.togglesw_style3_di.setEnabled(False)
304 |
305 | self.togglesw_style3_lyt.addWidget(self.togglesw_style3_off)
306 | self.togglesw_style3_lyt.addWidget(self.togglesw_style3_on)
307 | self.togglesw_style3_lyt.addWidget(self.togglesw_style3_en)
308 | self.togglesw_style3_lyt.addWidget(self.togglesw_style3_di)
309 |
310 |
311 | ##################################################
312 | # #
313 | # StyledButton Widget #
314 | # #
315 | ##################################################
316 |
317 | self.stbtn_showcase_wdt = QWidget()
318 | self.stbtn_showcase_wdt.setStyleSheet("font-family: Montserrat-Regular; font-size:15px;")
319 | self.stbtn_showcase_lyt = QVBoxLayout()
320 | self.stbtn_showcase_lyt.setSpacing(5)
321 | self.stbtn_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
322 | self.stbtn_showcase_wdt.setLayout(self.stbtn_showcase_lyt)
323 |
324 | self.stbtn_showcase_lyt.addWidget(QLabel("Styled Buttons"),
325 | alignment=Qt.AlignHCenter)
326 | self.stbtn_showcase_lyt.addWidget(QLabel("These are styled & animated buttons."),
327 | alignment=Qt.AlignHCenter)
328 |
329 | self.stbtn_showcase_lyt.addSpacing(80)
330 |
331 | self.stbtn_content_lyt = QHBoxLayout()
332 | self.stbtn_content_lyt.setSpacing(60)
333 | self.stbtn_showcase_lyt.addLayout(self.stbtn_content_lyt)
334 |
335 | self.stbtn_style1_lyt = QVBoxLayout()
336 | self.stbtn_style1_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
337 | self.stbtn_style1_lyt.setSpacing(15)
338 | self.stbtn_content_lyt.addLayout(self.stbtn_style1_lyt)
339 |
340 | self.stbtn_style2_lyt = QVBoxLayout()
341 | self.stbtn_style2_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
342 | self.stbtn_style2_lyt.setSpacing(15)
343 | self.stbtn_content_lyt.addLayout(self.stbtn_style2_lyt)
344 |
345 | self.stbtn_style3_lyt = QVBoxLayout()
346 | self.stbtn_style3_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
347 | self.stbtn_style3_lyt.setSpacing(15)
348 | self.stbtn_content_lyt.addLayout(self.stbtn_style3_lyt)
349 |
350 |
351 | self.stbtn_style1_lyt.addWidget(QLabel("Default style"),
352 | alignment=Qt.AlignHCenter)
353 | self.stbtn_style1_lyt.addSpacing(35)
354 |
355 | self.stbtn_style1 = StyledButton(text="Button")
356 | self.stbtn_style1_icon = StyledButton(text="Icon", icon="data/homeicon.png")
357 | self.stbtn_style1_spn = StyledButton(text="Spinner", icon=Spinner(2, QColor(0, 0, 0)))
358 | self.stbtn_style1_shad = StyledButton(text="Drop shadow")
359 | self.stbtn_style1_shad.setStyleDict({
360 | "drop-shadow-radius" : 7,
361 | "drop-shadow-offset" : (0, 2)
362 | })
363 |
364 | self.stbtn_style1_lyt.addWidget(self.stbtn_style1)
365 | self.stbtn_style1_lyt.addWidget(self.stbtn_style1_icon)
366 | self.stbtn_style1_lyt.addWidget(self.stbtn_style1_spn)
367 | self.stbtn_style1_lyt.addWidget(self.stbtn_style1_shad)
368 |
369 |
370 | self.stbtn_style2_lyt.addWidget(QLabel("Shadow interactions"),
371 | alignment=Qt.AlignHCenter)
372 | self.stbtn_style2_lyt.addSpacing(35)
373 |
374 | self.stbtn_style2_1 = StyledButton(text="Shadow on hover")
375 | self.stbtn_style2_2 = StyledButton(text="Shadow on press")
376 | self.stbtn_style2_3 = StyledButton(text="Shadow on idle")
377 | self.stbtn_style2_4 = StyledButton(text="Changing offset")
378 |
379 | self.stbtn_style2_1.setStyleDict({
380 | "drop-shadow-radius" : 10,
381 | "drop-shadow-offset" : (0, 3)
382 | }, "hover")
383 |
384 | self.stbtn_style2_2.setStyleDict({
385 | "drop-shadow-radius" : 10,
386 | "drop-shadow-offset" : (0, 3)
387 | }, "press")
388 |
389 | self.stbtn_style2_3.setStyleDict({
390 | "drop-shadow-radius" : 10,
391 | "drop-shadow-offset" : (0, 3)
392 | }, "default")
393 |
394 | self.stbtn_style2_4.setStyleDict({
395 | "drop-shadow-radius" : 10,
396 | "drop-shadow-offset" : (0, 3)
397 | })
398 | self.stbtn_style2_4.setStyleDict({
399 | "drop-shadow-offset" : (3, 0)
400 | }, "hover")
401 | self.stbtn_style2_4.setStyleDict({
402 | "drop-shadow-offset" : (-1.5, -1.5)
403 | }, "press")
404 |
405 | self.stbtn_style2_lyt.addWidget(self.stbtn_style2_1)
406 | self.stbtn_style2_lyt.addWidget(self.stbtn_style2_2)
407 | self.stbtn_style2_lyt.addWidget(self.stbtn_style2_3)
408 | self.stbtn_style2_lyt.addWidget(self.stbtn_style2_4)
409 |
410 |
411 | self.stbtn_style3_lyt.addWidget(QLabel("Variations"),
412 | alignment=Qt.AlignHCenter)
413 | self.stbtn_style3_lyt.addSpacing(35)
414 |
415 | self.stbtn_style3_1 = StyledButton(text="iOS styled")
416 | self.stbtn_style3_2 = StyledButton(text="Pill shaped")
417 | self.stbtn_style3_3 = StyledButton(text="✕")
418 | self.stbtn_style3_4 = StyledButton(text="Only text")
419 |
420 | self.stbtn_style3_1.setStyleDict({
421 | "border-radius" : 4,
422 | "border-color" : (0, 122, 255),
423 | "color" : (0, 122, 255)
424 | })
425 | self.stbtn_style3_1.setStyleDict({
426 | "background-color" : (255, 255, 255),
427 | "border-color" : (0, 172, 255),
428 | "color" : (0, 172, 255)
429 | }, "hover")
430 | self.stbtn_style3_1.setStyleDict({
431 | "background-color" : (0, 122, 255),
432 | "border-color" : (0, 122, 255),
433 | "color" : (255, 255, 255)
434 | }, "press")
435 |
436 | self.stbtn_style3_2.setStyleDict({
437 | "border-radius" : 100
438 | })
439 |
440 | self.stbtn_style3_3.setFixedSize(52, 52)
441 | self.stbtn_style3_3.setStyleDict({
442 | "border-radius" : 100,
443 | "border-color" : (0, 0, 0, 0),
444 | "background-color" : (241, 241, 241),
445 | "font-size" : 24
446 | })
447 | self.stbtn_style3_3.setStyleDict({
448 | "background-color": (200, 0, 0),
449 | "color" : (255, 255, 255)
450 | }, "hover")
451 | self.stbtn_style3_3.setStyleDict({
452 | "background-color": (255, 0, 0),
453 | "color" : (255, 255, 255)
454 | }, "press")
455 |
456 | self.stbtn_style3_4.setStyleDict({
457 | "border-color" : (0, 0, 0, 0),
458 | "background-color" : (0, 0, 0, 0)
459 | })
460 |
461 | self.stbtn_style3_lyt.addWidget(self.stbtn_style3_1)
462 | self.stbtn_style3_lyt.addWidget(self.stbtn_style3_2)
463 | self.stbtn_style3_lyt.addWidget(self.stbtn_style3_3)
464 | self.stbtn_style3_lyt.addWidget(self.stbtn_style3_4)
465 |
466 |
467 | ##################################################
468 | # #
469 | # ImageBox Widget #
470 | # #
471 | ##################################################
472 |
473 | self.imgbox_showcase_wdt = QWidget()
474 | self.imgbox_showcase_lyt = QVBoxLayout()
475 | self.imgbox_showcase_lyt.setSpacing(5)
476 | self.imgbox_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
477 | self.imgbox_showcase_wdt.setLayout(self.imgbox_showcase_lyt)
478 |
479 | self.imgbox_showcase_lyt.addWidget(QLabel("Image Box"),
480 | alignment=Qt.AlignHCenter)
481 | self.imgbox_showcase_lyt.addWidget(QLabel("Image boxes are simply widgets to hold visual content, such as PNG, JPG and GIF files."),
482 | alignment=Qt.AlignHCenter)
483 |
484 | self.imgbox_showcase_lyt.addSpacing(45)
485 |
486 | self.imgbox_hstatic_lyt = QHBoxLayout()
487 | self.imgbox_hstatic_lyt.setAlignment(Qt.AlignHCenter)
488 | self.imgbox_showcase_lyt.addLayout(self.imgbox_hstatic_lyt)
489 |
490 | self.imgbox_static1_lyt = QVBoxLayout()
491 | self.imgbox_static1_lyt.setAlignment(Qt.AlignHCenter)
492 | self.imgbox_hstatic_lyt.addLayout(self.imgbox_static1_lyt)
493 |
494 | self.imgbox_hstatic_lyt.addSpacing(100)
495 |
496 | self.imgbox_static1 = ImageBox("data/image.jpg", keepAspectRatio=True)
497 | self.imgbox_static1.setFixedSize(110, 110)
498 | self.imgbox_static1_lbl = QLabel("Aspect ratio protected")
499 | self.imgbox_static1_lyt.addWidget(self.imgbox_static1)
500 | self.imgbox_static1_lyt.addWidget(self.imgbox_static1_lbl, alignment=Qt.AlignHCenter)
501 |
502 | self.imgbox_static2_lyt = QVBoxLayout()
503 | self.imgbox_static2_lyt.setAlignment(Qt.AlignHCenter)
504 | self.imgbox_hstatic_lyt.addLayout(self.imgbox_static2_lyt)
505 |
506 | self.imgbox_static2 = ImageBox("data/image.jpg", keepAspectRatio=False)
507 | self.imgbox_static2.setFixedSize(110, 110)
508 | self.imgbox_static2_lbl = QLabel("Aspect ratio ignored")
509 | self.imgbox_static2_lyt.addWidget(self.imgbox_static2)
510 | self.imgbox_static2_lyt.addWidget(self.imgbox_static2_lbl, alignment=Qt.AlignHCenter)
511 |
512 | self.imgbox_showcase_lyt.addSpacing(80)
513 |
514 | self.imgbox_showcase_lyt.addWidget(QLabel("You can also use animated GIFs."),
515 | alignment=Qt.AlignHCenter)
516 |
517 | self.imgbox_animated = ImageBox("data/luffy.gif")
518 | self.imgbox_animated.setFixedSize(250, 210)
519 | self.imgbox_showcase_lyt.addWidget(self.imgbox_animated, alignment=Qt.AlignHCenter)
520 |
521 |
522 | ##################################################
523 | # #
524 | # ColorPicker Widget #
525 | # #
526 | ##################################################
527 |
528 | self.colorpk_showcase_wdt = QWidget()
529 | self.colorpk_showcase_lyt = QVBoxLayout()
530 | self.colorpk_showcase_lyt.setSpacing(5)
531 | self.colorpk_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
532 | self.colorpk_showcase_wdt.setLayout(self.colorpk_showcase_lyt)
533 |
534 | self.colorpk_showcase_lyt.addWidget(QLabel("Color Picker"),
535 | alignment=Qt.AlignHCenter)
536 | self.colorpk_showcase_lyt.addWidget(QLabel("Color Picker widget simply lets you to choose a color from the wheel."),
537 | alignment=Qt.AlignHCenter)
538 |
539 | self.colorpk_showcase_lyt.addSpacing(85)
540 |
541 | self.colorpk_hori_lyt = QHBoxLayout()
542 | self.colorpk_showcase_lyt.addLayout(self.colorpk_hori_lyt)
543 |
544 | self.colorpk_picker = ColorPicker()
545 | self.colorpk_hori_lyt.addWidget(self.colorpk_picker)
546 |
547 | self.colorpk_cpre = ColorPreview()
548 | self.colorpk_hori_lyt.addWidget(self.colorpk_cpre)
549 |
550 | self.colorpk_picker.colorChanged.connect(self.colorpk_cpre.setColor)
551 |
552 |
553 | ##################################################
554 | # #
555 | # DragDropFile Widget #
556 | # #
557 | ##################################################
558 |
559 | self.dropfile_showcase_wdt = QWidget()
560 | self.dropfile_showcase_lyt = QVBoxLayout()
561 | self.dropfile_showcase_lyt.setSpacing(5)
562 | self.dropfile_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
563 | self.dropfile_showcase_wdt.setLayout(self.dropfile_showcase_lyt)
564 |
565 | self.dropfile_showcase_lyt.addWidget(QLabel("File Drag & Drop"),
566 | alignment=Qt.AlignHCenter)
567 | self.dropfile_showcase_lyt.addWidget(QLabel("Drag files and drop on this widget."),
568 | alignment=Qt.AlignHCenter)
569 |
570 | self.dropfile_showcase_lyt.addSpacing(140)
571 |
572 | self.dropfile = DragDropFile()
573 | self.dropfile.setFixedSize(300, 210)
574 | self.dropfile_showcase_lyt.addWidget(self.dropfile)
575 |
576 |
577 | ##################################################
578 | # #
579 | # EmbedWindow Widget #
580 | # #
581 | ##################################################
582 |
583 | self.emwin_showcase_wdt = QWidget()
584 | self.emwin_showcase_lyt = QVBoxLayout()
585 | self.emwin_showcase_lyt.setSpacing(5)
586 | self.emwin_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
587 | self.emwin_showcase_wdt.setLayout(self.emwin_showcase_lyt)
588 |
589 | self.emwin_showcase_lyt.addWidget(QLabel("Embed Window"),
590 | alignment=Qt.AlignHCenter)
591 | self.emwin_showcase_lyt.addWidget(QLabel("A pop-up dialog, but actually embed."),
592 | alignment=Qt.AlignHCenter)
593 |
594 | self.emwin_showcase_lyt.addSpacing(70)
595 |
596 | self.emwin_spawner = StyledButton("Click here to spawn embed windows")
597 | self.emwin_spawner.setFixedHeight(33)
598 | self.emwin_showcase_lyt.addWidget(self.emwin_spawner)
599 |
600 | self.emwin_windows = list()
601 |
602 | # Parent is main window
603 | @self.emwin_spawner.clicked.connect
604 | def slot():
605 | ewl = QLabel("My parent is the top-level widget so I can move anywhere 😎")
606 | ewl.setWordWrap(True)
607 | ew = EmbedWindow(self)
608 | ew.content.addWidget(ewl)
609 | ew.closed.connect(lambda: self.emwin_windows.remove(ew))
610 | self.emwin_windows.append(ew)
611 | ew.show()
612 | ew.raise_()
613 |
614 | self.emwin_showcase_lyt.addSpacing(30)
615 |
616 | self.emwin_showcase_lyt.addWidget(QLabel("There is a QFrame with invisible borders"))
617 |
618 | self.emwin_frame = QFrame()
619 | self.emwin_frame.setFixedSize(400, 300)
620 | self.emwin_showcase_lyt.addWidget(self.emwin_frame)
621 |
622 | #Parent is QFrame
623 | self.emwin_frame_window = EmbedWindow(self.emwin_frame)
624 | lb = QLabel("My parent is this QFrame so I'm embed here and can't get out!")
625 | lb.setStyleSheet("color:#333333; font-size: 15px;")
626 | lb.setWordWrap(True)
627 | self.emwin_frame_window.content.addWidget(lb)
628 |
629 |
630 | ##################################################
631 | # #
632 | # CodeTextEdit Widget #
633 | # #
634 | ##################################################
635 |
636 | self.codeedit_showcase_wdt = QWidget()
637 | self.codeedit_showcase_lyt = QVBoxLayout()
638 | self.codeedit_showcase_lyt.setSpacing(5)
639 | self.codeedit_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
640 | self.codeedit_showcase_wdt.setLayout(self.codeedit_showcase_lyt)
641 |
642 | self.codeedit_showcase_lyt.addWidget(QLabel("Code Editor"),
643 | alignment=Qt.AlignHCenter)
644 | self.codeedit_showcase_lyt.addWidget(QLabel("CodeTextEdit widget is simply a syntax-highlighted editor"),
645 | alignment=Qt.AlignHCenter)
646 | self.codeedit_showcase_lyt.addWidget(QLabel("Currently it only supports few languages"),
647 | alignment=Qt.AlignHCenter)
648 |
649 | self.codeedit_showcase_lyt.addSpacing(34)
650 |
651 | self.codeedit_filedrop = DragDropFile()
652 | self.codeedit_filedrop.setTitle("Drop source file")
653 | self.codeedit_filedrop.setFixedSize(410, 64)
654 | self.codeedit_filedrop.borderRadius = 6
655 | self.codeedit_filedrop.borderWidth = 3
656 | self.codeedit_showcase_lyt.addWidget(self.codeedit_filedrop, alignment=Qt.AlignHCenter)
657 |
658 | self.codeedit_showcase_lyt.addSpacing(9)
659 |
660 | @self.codeedit_filedrop.fileDropped.connect
661 | def slot(file):
662 | self.codeedit.loadFile(file.path)
663 |
664 | self.codeedit = CodeTextEdit()
665 | self.codeedit.setFixedSize(510, 300)
666 | self.codeedit.setStyleSheet("QPlainTextEdit {font-size:17px; font-family: Source Code Pro;}")
667 | self.codeedit.loadFile("example.cpp")
668 | self.codeedit.setTheme("one-dark")
669 | self.codeedit_showcase_lyt.addWidget(self.codeedit, alignment=Qt.AlignHCenter|Qt.AlignTop)
670 |
671 | self.codeedit_showcase_lyt.addSpacing(9)
672 |
673 | self.codeedit_theme_btngrp = SegmentedButtonGroup(radio=True)
674 | self.codeedit_theme_btngrp.setFixedHeight(32)
675 |
676 | self.codeedit_theme_btngrp.addButton("Default", tag=0)
677 | self.codeedit_theme_btngrp.addButton("One Light", tag=1)
678 | self.codeedit_theme_btngrp.addButton("One Dark", tag=2)
679 | self.codeedit_theme_btngrp.addButton("Monokai", tag=3)
680 | self.codeedit_theme_btngrp.addButton("Oceanic", tag=4)
681 | self.codeedit_theme_btngrp.addButton("Zenburn", tag=5)
682 |
683 | self.codeedit_theme_btngrp.getByTag(2).setChecked(True)
684 |
685 | @self.codeedit_theme_btngrp.clicked.connect
686 | def slot(tag):
687 | btn = self.codeedit_theme_btngrp.getByTag(tag)
688 | self.codeedit.setTheme(btn.text().lower().replace(" ", "-"))
689 |
690 | self.codeedit_showcase_lyt.addWidget(self.codeedit_theme_btngrp)
691 |
692 |
693 | ##################################################
694 | # #
695 | # Spinner Widget #
696 | # #
697 | ##################################################
698 |
699 | self.spinner_showcase_wdt = QWidget()
700 | self.spinner_showcase_lyt = QVBoxLayout()
701 | self.spinner_showcase_lyt.setSpacing(5)
702 | self.spinner_showcase_lyt.setAlignment(Qt.AlignTop|Qt.AlignHCenter)
703 | self.spinner_showcase_wdt.setLayout(self.spinner_showcase_lyt)
704 |
705 | self.spinner_showcase_lyt.addWidget(QLabel("Spinner"),
706 | alignment=Qt.AlignHCenter)
707 | self.spinner_showcase_lyt.addWidget(QLabel("It's spinning! You can use this widget as icon argument."),
708 | alignment=Qt.AlignHCenter)
709 |
710 | self.spinner_showcase_lyt.addSpacing(70)
711 |
712 | self.spinner_row1 = QHBoxLayout()
713 | self.spinner_row2 = QHBoxLayout()
714 | self.spinner_row3 = QHBoxLayout()
715 | self.spinner_row4 = QHBoxLayout()
716 | self.spinner_showcase_lyt.addLayout(self.spinner_row1)
717 | self.spinner_showcase_lyt.addSpacing(15)
718 | self.spinner_showcase_lyt.addLayout(self.spinner_row2)
719 | self.spinner_showcase_lyt.addSpacing(15)
720 | self.spinner_showcase_lyt.addLayout(self.spinner_row3)
721 | self.spinner_showcase_lyt.addSpacing(15)
722 | self.spinner_showcase_lyt.addLayout(self.spinner_row4)
723 |
724 |
725 | self.spinner_style1_1 = Spinner(1.5, QColor(0, 0, 0))
726 | self.spinner_style1_1.setFixedSize(18, 18)
727 |
728 | self.spinner_style1_2 = Spinner(1.5, QColor(0, 0, 0))
729 | self.spinner_style1_2.setFixedSize(36, 36)
730 |
731 | self.spinner_style1_3 = Spinner(1.5, QColor(0, 0, 0))
732 | self.spinner_style1_3.setFixedSize(78, 78)
733 |
734 | self.spinner_row1.addWidget(QLabel("Fixed Width"))
735 | self.spinner_row1.addWidget(self.spinner_style1_1)
736 | self.spinner_row1.addSpacing(20)
737 | self.spinner_row1.addWidget(self.spinner_style1_2)
738 | self.spinner_row1.addSpacing(20)
739 | self.spinner_row1.addWidget(self.spinner_style1_3)
740 |
741 |
742 | self.spinner_style2_1 = Spinner(1.5, QColor(0, 0, 0))
743 | self.spinner_style2_1.setFixedSize(18, 18)
744 |
745 | self.spinner_style2_2 = Spinner(6.0, QColor(0, 0, 0))
746 | self.spinner_style2_2.setFixedSize(36, 36)
747 |
748 | self.spinner_style2_3 = Spinner(18, QColor(0, 0, 0))
749 | self.spinner_style2_3.setFixedSize(78, 78)
750 |
751 | self.spinner_row2.addWidget(QLabel("Increasing width"))
752 | self.spinner_row2.addWidget(self.spinner_style2_1)
753 | self.spinner_row2.addSpacing(20)
754 | self.spinner_row2.addWidget(self.spinner_style2_2)
755 | self.spinner_row2.addSpacing(20)
756 | self.spinner_row2.addWidget(self.spinner_style2_3)
757 |
758 |
759 | self.spinner_style3_1 = Spinner(2, QColor(0, 0, 0))
760 | self.spinner_style3_1.animType = 0
761 | self.spinner_style3_1.setFixedSize(18, 18)
762 |
763 | self.spinner_style3_2 = Spinner(2, QColor(0, 0, 0))
764 | self.spinner_style3_2.animType = 0
765 | self.spinner_style3_2.setFixedSize(36, 36)
766 |
767 | self.spinner_style3_3 = Spinner(2, QColor(0, 0, 0))
768 | self.spinner_style3_3.animType = 0
769 | self.spinner_style3_3.setFixedSize(78, 78)
770 |
771 | self.spinner_row3.addWidget(QLabel("Boring animation type"))
772 | self.spinner_row3.addWidget(self.spinner_style3_1)
773 | self.spinner_row3.addSpacing(20)
774 | self.spinner_row3.addWidget(self.spinner_style3_2)
775 | self.spinner_row3.addSpacing(20)
776 | self.spinner_row3.addWidget(self.spinner_style3_3)
777 |
778 |
779 | self.spinner_style4_1 = Spinner(4, QColor(255, 0, 0))
780 | self.spinner_style4_1.animType = 0
781 | self.spinner_style4_1.speed = 1.2
782 | self.spinner_style4_1.setFixedSize(24, 24)
783 |
784 | self.spinner_style4_2 = Spinner(2, QColor(0, 255, 20))
785 | self.spinner_style4_2.setFixedSize(36, 36)
786 |
787 | self.spinner_style4_3 = Spinner(5, QColor(0, 55, 255))
788 | self.spinner_style4_3.speed = 12
789 | self.spinner_style4_3.setFixedSize(55, 55)
790 |
791 | self.spinner_row4.addWidget(QLabel("Variations"))
792 | self.spinner_row4.addWidget(self.spinner_style4_1)
793 | self.spinner_row4.addSpacing(25)
794 | self.spinner_row4.addWidget(self.spinner_style4_2)
795 | self.spinner_row4.addSpacing(45)
796 | self.spinner_row4.addWidget(self.spinner_style4_3)
797 |
798 |
799 | # finalize layout
800 | self.layout.addWidget(self.togglesw_showcase_wdt)
801 | self.layout.addWidget(self.stbtn_showcase_wdt)
802 | self.layout.addWidget(self.imgbox_showcase_wdt)
803 | self.layout.addWidget(self.colorpk_showcase_wdt)
804 | self.layout.addWidget(self.dropfile_showcase_wdt)
805 | self.layout.addWidget(self.emwin_showcase_wdt)
806 | self.layout.addWidget(self.codeedit_showcase_wdt)
807 | self.layout.addWidget(self.spinner_showcase_wdt)
808 |
809 | self.togglesw_showcase_wdt.show()
810 | self.stbtn_showcase_wdt.hide()
811 | self.imgbox_showcase_wdt.hide()
812 | self.colorpk_showcase_wdt.hide()
813 | self.dropfile_showcase_wdt.hide()
814 | self.emwin_showcase_wdt.hide()
815 | self.codeedit_showcase_wdt.hide()
816 | self.spinner_showcase_wdt.hide()
817 |
818 |
819 |
820 | if __name__ == "__main__":
821 | app = QApplication(sys.argv)
822 |
823 | mw = MainWindow()
824 | mw.show()
825 |
826 | sys.exit(app.exec_())
827 |
--------------------------------------------------------------------------------
/examples/example_titlebar.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 | # #
5 | # This script is one of the pyqt5Custom examples #
6 |
7 |
8 | import sys
9 |
10 | from PyQt5.QtCore import Qt
11 | from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout
12 | from PyQt5.QtGui import QColor, QFontDatabase, QBrush, QPalette, QLinearGradient
13 |
14 | from pyqt5Custom import TitleBar
15 |
16 |
17 |
18 | class MainWindow(QWidget):
19 | def __init__(self):
20 | super().__init__()
21 |
22 | QFontDatabase.addApplicationFont("data/SFPro.ttf")
23 |
24 | self.setGeometry(100, 100, 410, 240)
25 |
26 | self.layout = QVBoxLayout()
27 | self.layout.setAlignment(Qt.AlignTop)
28 | self.setLayout(self.layout)
29 | self.layout.setContentsMargins(0, 0, 0, 0)
30 |
31 | self.titlebar = TitleBar(self, title="Custom TitleBar!")
32 | self.layout.addWidget(self.titlebar, alignment=Qt.AlignTop)
33 | self.titlebar.setStyleDict({
34 | "background-color" : (255, 255, 255),
35 | "font-size" : 18,
36 | "font-subpixel-aa" : True,
37 | "font-family" : "SF Pro Display",
38 | })
39 |
40 | self.titlebar.closeButton.setStyleDict({
41 | "border-radius" : 100,
42 | "background-color" : (255, 255, 255, 120),
43 | "font-size" : 18,
44 | "font-family" : "SF Pro Display",
45 | "render-fast" : True
46 | })
47 | self.titlebar.maxButton.copyStyleDict(self.titlebar.closeButton)
48 | self.titlebar.minButton.copyStyleDict(self.titlebar.closeButton)
49 |
50 |
51 | self.anim = self.titlebar.newAnimation()
52 | self.anim.speed = 0.7
53 |
54 | @self.anim.tick
55 | def callback():
56 | r = QColor(255, 100, 100)
57 | g = QColor(100, 255, 100)
58 | b = QColor(100, 100, 255)
59 |
60 | if self.anim.current() < 0.25:
61 | c = self.anim.lerp(r, g)
62 | elif self.anim.current() < 0.75:
63 | c = self.anim.lerp(g, b)
64 | else:
65 | c = self.anim.lerp(r, b)
66 |
67 | self.titlebar.setStyleDict({
68 | "background-color" : (c.red(), c.green(), c.blue()),
69 | })
70 |
71 | self.anim.start(loop = True)
72 |
73 |
74 |
75 | if __name__ == "__main__":
76 | app = QApplication(sys.argv)
77 |
78 | mw = MainWindow()
79 | mw.show()
80 |
81 | sys.exit(app.exec_())
82 |
--------------------------------------------------------------------------------
/pyqt5Custom/__init__.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | __version__ = "1.0.1"
7 |
8 |
9 | from .toggleswitch import ToggleSwitch
10 | from .styledbutton import StyledButton
11 | from .segbtngroup import SegmentedButtonGroup
12 | from .imagebox import ImageBox
13 | from .colorpicker import ColorPicker
14 | from .dragdropfile import DragDropFile
15 | from .embedwindow import EmbedWindow
16 | from .codetextedit import CodeTextEdit
17 | from .titlebar import TitleBar
18 | from .spinner import Spinner
19 | from .toast import Toast
20 |
21 | from .dragdropfile import FileDetails
22 | from .colorpicker import ColorPreview
23 | from .animation import Animation, AnimationHandler
24 | from .requesthandler import RequestHandler
25 | from .syntaxhighlighter import SyntaxHighlighter
26 |
--------------------------------------------------------------------------------
/pyqt5Custom/animation.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import time
7 | from math import ceil, sin, pi, sqrt, pow
8 |
9 | from PyQt5.QtGui import QColor
10 |
11 |
12 |
13 | class Animation:
14 | easeOutSine = lambda x: sin((x * pi) / 2)
15 | easeOutCubic = lambda x: 1 - ((1 - x)**3)
16 | easeOutQuart = lambda x: 1 - pow(1 - x, 4)
17 | easeOutCirc = lambda x: 1 - ((1 - x)**3)#sqrt(1 - pow(x - 1, 2))
18 |
19 |
20 |
21 | class AnimationHandler:
22 | def __init__(self, widget, startv, endv, type):
23 | self.widget = widget
24 | self.type = type
25 |
26 | self.startv = startv
27 | self.endv = endv
28 |
29 | self.value = 0
30 |
31 | self.speed = 3.45
32 |
33 | self.sensitivity = 0.001
34 |
35 | self.reverse = False
36 | self.loop = False
37 | self.started = None
38 |
39 | self._tickfunc = None
40 |
41 | def __repr__(self):
42 | return f"{self.endv})>"
43 |
44 | def tick(self, func):
45 | self._tickfunc = func
46 | return func
47 |
48 | def start(self, reverse=False, loop=False):
49 | self.reverse = reverse
50 | self.loop = loop
51 | self.started = True
52 | self.orgstart_time = time.time()
53 | self.value = 0
54 | self.widget.update()
55 |
56 | def reset(self):
57 | self.value = 0
58 | self.started = None
59 |
60 | def done(self):
61 | return self.started is None
62 |
63 | def update(self):
64 | if not self.done():
65 | ep = time.time() - self.orgstart_time
66 |
67 | self.value = self.type(ep * self.speed)
68 |
69 | if self.reverse:
70 | if self.current() <= self.startv + self.sensitivity: self.started = None
71 | else:
72 | if self.current() >= self.endv - self.sensitivity: self.started = None
73 |
74 | if self.done():
75 | if self.loop:
76 | self.start(reverse=not self.reverse, loop=True)
77 | return
78 |
79 | #print(self.value)
80 | if self._tickfunc: self._tickfunc()
81 |
82 | def current(self):
83 | if self.reverse:
84 | return self.endv - (self.value * (self.endv-self.startv))
85 | else:
86 | return self.value * (self.endv-self.startv)
87 |
88 | def lerp(self, a, b):
89 | f = self.current() / self.endv
90 |
91 | if isinstance(a, QColor):
92 | r1, r2 = a.red(), b.red()
93 | g1, g2 = a.green(), b.green()
94 | b1, b2 = a.blue(), b.blue()
95 | a1, a2 = a.alpha(), b.alpha()
96 |
97 | r = (r2 - r1) * f + r1
98 | g = (g2 - g1) * f + g1
99 | _b = (b2 - b1) * f + b1
100 | _a = (a2 - a1) * f + a1
101 |
102 | return QColor(r, g, _b, _a)
103 |
104 | else:
105 | return (b - a) * f + a
106 |
--------------------------------------------------------------------------------
/pyqt5Custom/codetextedit.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | from PyQt5.QtCore import Qt
7 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QPlainTextEdit
8 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush
9 |
10 | from .syntaxhighlighter import SyntaxHighlighter
11 |
12 |
13 |
14 | class CodeTextEdit(QWidget):
15 |
16 | LANG_DISPLAY = {
17 | "plain" : "Plain text",
18 | "python" : "Python",
19 | "py" : "Python",
20 | "cpp" : "C++",
21 | "c++" : "C++"
22 | }
23 |
24 | def __init__(self):
25 | super().__init__()
26 |
27 | self.layout = QHBoxLayout()
28 | self.layout.setContentsMargins(0, 0, 0, 0)
29 | self.setLayout(self.layout)
30 |
31 | self.layout.addSpacing(37)
32 |
33 | self.editorlyt = QVBoxLayout()
34 | self.editorlyt.setSpacing(0)
35 | self.editorlyt.setContentsMargins(0, 0, 0, 0)
36 | self.layout.addLayout(self.editorlyt)
37 |
38 | self.editor = QPlainTextEdit()
39 | self.editor.setLineWrapMode(QPlainTextEdit.NoWrap)
40 | self.editorlyt.addWidget(self.editor)
41 |
42 | self.setStyleSheet("QPlainTextEdit {font-family:Consolas; font-size:14px; color:#222222;}")
43 |
44 | self.highlighter = SyntaxHighlighter(self.editor.document())
45 |
46 | self.sliderVal = 0
47 | self.lastdigit = 2
48 | vs = self.editor.verticalScrollBar()
49 |
50 | @vs.rangeChanged.connect
51 | def slot(v):
52 | self.sliderVal = self.editor.verticalScrollBar().value()
53 |
54 | if len(str(vs.maximum()+15)) > self.lastdigit:
55 | self.lastdigit = len(str(vs.maximum()+15))
56 | self.layout.insertSpacing(0, 10)
57 |
58 | self.update()
59 |
60 | @vs.valueChanged.connect
61 | def slot(v):
62 | self.sliderVal = v
63 |
64 | self.update()
65 |
66 | self.statusbar = QWidget()
67 | self.statusbar.setFixedHeight(26)
68 | self.statusbarlyt = QHBoxLayout()
69 | self.statusbarlyt.setContentsMargins(10, 0, 30, 0)
70 | self.statusbar.setLayout(self.statusbarlyt)
71 | self.editorlyt.addWidget(self.statusbar)
72 |
73 | self.cursor_lbl = QLabel("0:0")
74 | self.cursor_lbl.setStyleSheet("font-size:16px;")
75 | self.statusbarlyt.addWidget(self.cursor_lbl, alignment=Qt.AlignLeft|Qt.AlignVCenter)
76 |
77 | self.lang_lbl = QLabel("Plain text")
78 | self.lang_lbl.setStyleSheet("font-size:16px;")
79 | self.statusbarlyt.addWidget(self.lang_lbl, alignment=Qt.AlignRight|Qt.AlignVCenter)
80 |
81 | @self.editor.cursorPositionChanged.connect
82 | def slot():
83 | self.cursor_lbl.setText(f"{self.editor.textCursor().blockNumber()}:{self.editor.textCursor().positionInBlock()}")
84 |
85 | def __repr__(self):
86 | return f""
87 |
88 | def setTheme(self, theme):
89 | self.highlighter.setTheme(theme)
90 | self.highlighter.setRules()
91 |
92 | c = self.highlighter.theme["background"]
93 | rgb = f"rgb({c.red()}, {c.green()}, {c.blue()})"
94 |
95 | cc = self.highlighter.theme["identifier"]
96 | crgb = f"rgb({cc.red()}, {cc.green()}, {cc.blue()})"
97 |
98 | self.editor.setStyleSheet(f"QPlainTextEdit {{background-color: {rgb}; color: {crgb};}}")
99 |
100 | self.cursor_lbl.setStyleSheet(f"color: {crgb}; font-size:16px;")
101 | self.lang_lbl.setStyleSheet(f"color: {crgb}; font-size:16px;")
102 |
103 | self.highlighter.rehighlight()
104 | self.update()
105 |
106 | def setLang(self, lang):
107 | self.lang_lbl.setText(CodeTextEdit.LANG_DISPLAY[lang])
108 |
109 | self.highlighter.setLang(lang)
110 | self.highlighter.setRules()
111 | self.highlighter.rehighlight()
112 | self.update()
113 |
114 | def loadFile(self, filepath, encoding="utf-8"):
115 |
116 | if filepath.endswith(".py"): lang = "python"
117 | elif filepath.endswith(".cpp"): lang = "cpp"
118 | else: lang = "plain"
119 |
120 | with open(filepath, "r", encoding=encoding) as f:
121 | self.editor.setPlainText(f.read())
122 |
123 | self.setLang(lang)
124 |
125 | def paintEvent(self, event):
126 | pt = QPainter()
127 | pt.begin(self)
128 | pt.setRenderHint(QPainter.Antialiasing)
129 |
130 | pt.setBrush(QBrush(self.highlighter.theme["lines-background"]))
131 |
132 | pt.drawRect(0, 0, self.width(), self.height())
133 |
134 | font = self.editor.font()
135 | pt.setFont(font)
136 |
137 | gap = font.pixelSize() + 3
138 |
139 | for i in range((self.height()//gap)):
140 | font = pt.font()
141 | pt.setFont(font)
142 | pt.setPen(QPen(self.highlighter.theme["lines"]))
143 |
144 | pt.drawText(13, i*gap, str(i+self.sliderVal))
145 |
146 | pt.setBrush(QBrush(self.highlighter.theme["background"]))
147 | pt.setPen(QPen(QColor(0, 0, 0, 0)))
148 | pt.drawRect(3, self.height()-self.statusbar.height(), 40, self.statusbar.height()-6)
149 |
150 | pt.end()
151 |
--------------------------------------------------------------------------------
/pyqt5Custom/colorpicker.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import time
7 | from math import sqrt, atan2, pow, pi
8 |
9 | from PyQt5.QtCore import Qt, pyqtSignal
10 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
11 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush
12 |
13 |
14 |
15 | class ColorPreview(QWidget):
16 | def __init__(self):
17 | super().__init__()
18 |
19 | self.color = QColor(0, 0, 0)
20 |
21 | self.layout = QVBoxLayout()
22 | self.setLayout(self.layout)
23 | self.label = QLabel("#000000")
24 | self.layout.addWidget(self.label, alignment=Qt.AlignBottom|Qt.AlignHCenter)
25 |
26 | self.setFixedSize(90, 65)
27 |
28 | def __repr__(self):
29 | return f""
30 |
31 | def setColor(self, color):
32 | self.color = color
33 | self.label.setText(self.color.name())
34 |
35 | def paintEvent(self, event):
36 | pt = QPainter()
37 | pt.begin(self)
38 | pt.setRenderHint(QPainter.Antialiasing, on=True)
39 |
40 | pt.setPen(QPen(QColor(0, 0, 0, 0)))
41 | pt.setBrush(QBrush(QColor(225, 225, 225)))
42 |
43 | pt.drawRoundedRect(0, 0, self.width(), self.height(), 9, 9)
44 |
45 | pt.setBrush(QBrush(self.color))
46 |
47 | pt.drawRoundedRect(15, 15, self.width()-30, self.height()-45, 4, 4)
48 |
49 | pt.end()
50 |
51 |
52 | # TODO: Complete optimized color wheel & picker are and cursor
53 | class ColorPicker(QWidget):
54 |
55 | colorChanged = pyqtSignal(QColor)
56 |
57 | def __init__(self):
58 | super().__init__()
59 |
60 | self.color = None
61 |
62 | self.radius = 110
63 | self.setFixedSize(self.radius*2, self.radius*2)
64 |
65 | self.mouse_x, self.mouse_y = 0, 0
66 |
67 | def __repr__(self):
68 | return f""
69 |
70 | def mouseMoveEvent(self, event):
71 | self.mouse_x, self.mouse_y = event.x(), event.y()
72 |
73 | dist = sqrt(pow(self.mouse_x-self.radius, 2)+pow(self.mouse_y-self.radius, 2))
74 |
75 | def paintEvent(self, event):
76 | pt = QPainter()
77 | pt.begin(self)
78 | pt.setRenderHint(QPainter.Antialiasing, on=True)
79 |
80 | for i in range(self.width()):
81 | for j in range(self.height()):
82 | color = QColor(255, 255, 255, 255)
83 | h = (atan2(i-self.radius, j-self.radius)+pi)/(2.*pi)
84 | s = sqrt(pow(i-self.radius, 2)+pow(j-self.radius, 2))/self.radius
85 | v = 1.0
86 |
87 | rr = 0.65
88 |
89 | ww = self.width()/(rr*5.72)
90 | hh = self.height()/(rr*5.72)
91 |
92 | if rr < s < 1.0:
93 | color.setHsvF(h, s, v, 1.0)
94 | pt.setPen(color)
95 | pt.drawPoint(i, j)
96 |
97 | elif ww < i < self.width()-ww and hh < j < self.height()-hh:
98 | h = 0.8
99 | s = (i - ww) / (self.width()-ww*2)
100 | v = 1-((j - hh) / (self.height()-hh*2))
101 |
102 | hh = int(h*360)
103 | ss = int(s*255)
104 | vv = int(v*255)
105 | color.setHsv(hh, ss, vv)
106 | pt.setPen(color)
107 | pt.drawPoint(i, j)
108 |
109 | pt.end()
110 |
--------------------------------------------------------------------------------
/pyqt5Custom/dragdropfile.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import pathlib
7 |
8 | from PyQt5.QtCore import Qt, pyqtSignal
9 | from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
10 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush
11 |
12 |
13 |
14 | class FileDetails:
15 | def __init__(self, path, content):
16 | self.path = path
17 | self.content = content
18 | self.size = len(self.content)
19 |
20 | self._path = pathlib.Path(self.path)
21 |
22 | self.name = self._path.name
23 | self.pureName = self._path.stem
24 | self.extension = self._path.suffix
25 |
26 | def __repr__(self):
27 | return f""
28 |
29 |
30 |
31 | class DragDropFile(QWidget):
32 |
33 | fileDropped = pyqtSignal(FileDetails)
34 |
35 | def __init__(self):
36 | super().__init__()
37 |
38 | self.setAcceptDrops(True)
39 |
40 | self.setMinimumSize(120, 65)
41 |
42 | self.borderColor = QColor(190, 190, 190)
43 | self.hoverBackground = QColor(245, 245, 250)
44 | self.borderRadius = 26
45 | self.borderWidth = 6
46 |
47 | self.layout = QVBoxLayout()
48 | self.layout.setAlignment(Qt.AlignCenter)
49 | self.setLayout(self.layout)
50 |
51 | self.title_lbl = QLabel("Drop your file here!")
52 | self.filename_lbl = QLabel("")
53 |
54 | self.layout.addWidget(self.title_lbl, alignment=Qt.AlignHCenter)
55 | self.layout.addSpacing(7)
56 | self.layout.addWidget(self.filename_lbl, alignment=Qt.AlignHCenter)
57 |
58 | self.title_lbl.setStyleSheet("font-size:19px;")
59 | self.filename_lbl.setStyleSheet("font-size:14px; color: #666666;")
60 |
61 | self.dragEnter = False
62 |
63 | self.file = None
64 |
65 | def setTitle(self, title):
66 | self.title_lbl.setText(title)
67 |
68 | def dragEnterEvent(self, event):
69 | if event.mimeData().hasUrls():
70 | self.dragEnter = True
71 | event.accept()
72 | self.repaint()
73 | else:
74 | event.ignore()
75 |
76 | def dragLeaveEvent(self, event):
77 | self.dragEnter = False
78 | self.repaint()
79 |
80 | def dropEvent(self, event):
81 | mime = event.mimeData()
82 | file = FileDetails(mime.urls()[0].toLocalFile(), mime.text())
83 |
84 | self.filename_lbl.setText(file.name)
85 |
86 | self.fileDropped.emit(file)
87 |
88 | self.dragEnter = False
89 | self.repaint()
90 |
91 | def paintEvent(self, event):
92 | pt = QPainter()
93 | pt.begin(self)
94 | pt.setRenderHint(QPainter.Antialiasing, on=True)
95 |
96 | pen = QPen(self.borderColor, self.borderWidth, Qt.DotLine, Qt.RoundCap)
97 | pt.setPen(pen)
98 |
99 | if self.dragEnter:
100 | brush = QBrush(self.hoverBackground)
101 | pt.setBrush(brush)
102 |
103 | pt.drawRoundedRect(self.borderWidth, self.borderWidth, self.width()-self.borderWidth*2, self.height()-self.borderWidth*2, self.borderRadius, self.borderRadius)
104 |
105 | pt.end()
106 |
--------------------------------------------------------------------------------
/pyqt5Custom/embedwindow.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import random
7 |
8 | from PyQt5.QtCore import Qt, QRect, pyqtSignal
9 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGraphicsDropShadowEffect
10 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush
11 |
12 | from .animation import Animation, AnimationHandler
13 | from .styledbutton import StyledButton
14 |
15 |
16 | class EmbedWindow(QWidget):
17 |
18 | closed = pyqtSignal()
19 |
20 | def __init__(self, parent, pos=None, title="New window"):
21 | super().__init__(parent)
22 |
23 | if pos is None:
24 | pos = (random.randint(0, self.parent().width()-285), random.randint(0, self.parent().height()-190))
25 | self.setFixedSize(285, 190)
26 | self.setGeometry(pos[0], pos[1], 285, 190)
27 |
28 | self.shadow = QGraphicsDropShadowEffect(self)
29 | self.shadow.setBlurRadius(8)
30 | self.shadow.setColor(QColor(0, 0, 0, 110))
31 | self.shadow.setOffset(0, 2)
32 | self.setGraphicsEffect(self.shadow)
33 |
34 | self.borderRadius = 12
35 | self.headerColor = QColor(255, 255, 255)
36 | self.headerHeight = 36
37 |
38 | self.pressed = None
39 | self.pressed_pos = None
40 |
41 | self.layout = QVBoxLayout()
42 | self.layout.setContentsMargins(0, 0, 0, 0)
43 | self.layout.setAlignment(Qt.AlignTop)
44 | self.setLayout(self.layout)
45 |
46 | self.header = QWidget()
47 | self.header.setFixedHeight(self.headerHeight)
48 | self.header_lyt = QHBoxLayout()
49 | self.header_lyt.setContentsMargins(0, 0, 0, 0)
50 | self.header_lyt.setSpacing(0)
51 | self.header.setLayout(self.header_lyt)
52 |
53 | self.contentwdt = QWidget()
54 | self.content = QVBoxLayout()
55 | self.contentwdt.setLayout(self.content)
56 | self.content_visible = True
57 | self.last_height = self.height()
58 |
59 | self.layout.addWidget(self.header)
60 | self.layout.addWidget(self.contentwdt)
61 |
62 | self.title = QLabel(title)
63 | self.title.setStyleSheet("color:black; font-size:14px;")
64 |
65 | self.close_btn = StyledButton("✕")
66 | self.close_btn.setFixedSize(self.headerHeight, self.headerHeight)
67 | self.close_btn.setStyleDict({
68 | "border-color" : (0, 0, 0, 0),
69 | "border-radius" : 50
70 | })
71 |
72 | @self.close_btn.clicked.connect
73 | def slot():
74 | self.close()
75 | self.closed.emit()
76 |
77 | self.deta_btn = StyledButton("▲")
78 | self.deta_btn.setFixedSize(self.headerHeight, self.headerHeight)
79 | self.deta_btn.setStyleDict({
80 | "border-color" : (0, 0, 0, 0),
81 | "border-radius" : 50
82 | })
83 |
84 | @self.deta_btn.clicked.connect
85 | def slot():
86 | if self.content_visible:
87 | self.content_visible = False
88 | self.contentwdt.hide()
89 | self.last_height = self.height()
90 | self.deta_btn.textLbl.setText("▼")
91 | self.anim.start(reverse=True)
92 |
93 | else:
94 | self.content_visible = True
95 | self.contentwdt.show()
96 | self.deta_btn.textLbl.setText("▲")
97 | self.anim.start()
98 |
99 | self.header_lyt.addSpacing(10)
100 | self.header_lyt.addWidget(self.title, alignment=Qt.AlignVCenter)
101 | self.header_lyt.addWidget(self.deta_btn, alignment=Qt.AlignVCenter|Qt.AlignRight)
102 | self.header_lyt.addWidget(self.close_btn, alignment=Qt.AlignVCenter)
103 |
104 | self.anim = AnimationHandler(self, 0, 1, Animation.easeOutSine)
105 | self.anim.interval = 10/1000
106 | self.anim.value = 1
107 |
108 | def __repr__(self):
109 | return f""
110 |
111 | def setTitle(text):
112 | self.title.setText(text)
113 |
114 | def title(self):
115 | return self.title.text()
116 |
117 | def setControlsVisible(b):
118 | if not b:
119 | self.close_btn.hide()
120 | self.deta_btn.hide()
121 |
122 | else:
123 | self.close_btn.show()
124 | self.deta_btn.show()
125 |
126 | def update(self, *args, **kwargs):
127 | self.anim.update()
128 | super().update(*args, **kwargs)
129 |
130 | def mousePressEvent(self, event):
131 | self.startpos = self.pos()
132 | self.__mousePressPos = None
133 | self.__mouseMovePos = None
134 | if event.button() == Qt.LeftButton:
135 | self.__mousePressPos = event.globalPos()
136 | self.__mouseMovePos = event.globalPos()
137 |
138 | def mouseMoveEvent(self, event):
139 | if event.buttons() == Qt.LeftButton and event.y() <= self.headerHeight:
140 | currPos = self.mapToGlobal(self.pos())
141 | globalPos = event.globalPos()
142 | diff = globalPos - self.__mouseMovePos
143 | newPos = self.mapFromGlobal(currPos + diff)
144 | self.move(newPos)
145 |
146 | self.__mouseMovePos = globalPos
147 |
148 | self.raise_()
149 |
150 | def mouseReleaseEvent(self, event):
151 | if self.__mousePressPos is not None:
152 | moved = event.globalPos() - self.__mousePressPos
153 | if moved.manhattanLength() > 3:
154 | event.ignore()
155 | return
156 |
157 | def paintEvent(self, event):
158 | pt = QPainter()
159 | pt.begin(self)
160 | pt.setRenderHint(QPainter.Antialiasing, on=True)
161 |
162 | pen = QPen(QColor(0, 0, 0, 0), 1)
163 | brush = QBrush(QColor(255, 255, 255))
164 | pt.setBrush(brush)
165 | pt.setPen(pen)
166 |
167 | self.setFixedHeight(((self.last_height-36)*self.anim.current())+36)
168 | pt.drawRoundedRect(0, 0, self.width(), self.height()*self.anim.current(), self.borderRadius, self.borderRadius)
169 |
170 | brush = QBrush(self.headerColor)
171 | pt.setBrush(brush)
172 |
173 | pt.drawRoundedRect(0, 0, self.width(), self.headerHeight, self.borderRadius, self.borderRadius)
174 | if self.content_visible:
175 | pt.drawRect(0, self.headerHeight/2, self.width()*2, self.headerHeight/2)
176 |
177 | pt.end()
178 | self.show()
179 |
180 | if not self.anim.done(): self.update()
181 |
--------------------------------------------------------------------------------
/pyqt5Custom/imagebox.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import pathlib
7 | import requests
8 |
9 | from PyQt5.QtCore import Qt, QSize
10 | from PyQt5.QtWidgets import QLabel
11 | from PyQt5.QtGui import QPixmap, QMovie, QImage
12 |
13 |
14 |
15 | class ImageBox(QLabel):
16 | def __init__(self, source=None, parent=None, keepAspectRatio=True, smoothScale=True):
17 | super().__init__()
18 |
19 | self.source = source
20 | self.animated = False
21 |
22 | self.keepAspectRatio = keepAspectRatio
23 | self.smoothScale = smoothScale
24 |
25 | if self.source is not None: self.setSource(self.source)
26 |
27 | def __repr__(self):
28 | return f""
29 |
30 | def setSource(self, source):
31 | self.source = source
32 |
33 | if isinstance(self.source, pathlib.Path):
34 | self.source = str(self.source)
35 |
36 | if isinstance(self.source, str):
37 |
38 | # TODO: Better URL validation
39 | if self.source.startswith("http"):
40 |
41 | if self.source.endswith(".gif"):
42 | r = requests.get(self.source)
43 |
44 | with open("temp.gif", "wb") as f:
45 | f.write(r.content)
46 |
47 | self.animated = True
48 | self.orgmovie = QMovie("temp.gif")
49 | self.movie = self.orgmovie
50 | self.setMovie(self.movie)
51 | self.movie.start()
52 |
53 | else:
54 | r = requests.get(self.source)
55 |
56 | self.animated = False
57 | self.orgpixmap = QPixmap.fromImage(QImage.fromData(r.content))
58 | self.pixmap = QPixmap(self.orgpixmap)
59 | self.setPixmap(self.pixmap)
60 |
61 | else:
62 | if source.endswith(".gif"):
63 | self.animated = True
64 | self.movie = QMovie(source)
65 | self.setMovie(self.movie)
66 | self.movie.start()
67 |
68 | else:
69 | self.animated = False
70 | self.orgpixmap = QPixmap(source)
71 | self.pixmap = QPixmap(source)
72 | self.setPixmap(self.pixmap)
73 |
74 | elif isinstance(self.source, QPixmap):
75 | self.animated = False
76 | self.orgpixmap = QPixmap(self.source)
77 | self.pixmap = QPixmap(self.source)
78 | self.setPixmap(self.pixmap)
79 |
80 | elif isinstance(self.source, QImage):
81 | self.animated = False
82 | self.orgpixmap = QPixmap.fromImage(self.source)
83 | self.pixmap = QPixmap.fromImage(self.source)
84 | self.setPixmap(self.pixmap)
85 |
86 | elif isinstance(self.source, QMovie):
87 | self.animated = True
88 | self.movie = QMovie(self.source)
89 | self.setMovie(self.movie)
90 | self.movie.start()
91 |
92 | else:
93 | raise TypeError(f"QImage(source: Union[str, pathlib.Path, QPixmap, QImage, QMovie]) -> Argument 1 has unexpected type '{type(self.source)}'")
94 |
95 | self.resizeEvent(None)
96 |
97 | def resizeEvent(self, event):
98 | w, h = self.width(), self.height()
99 |
100 | t = (Qt.FastTransformation, Qt.SmoothTransformation)[self.smoothScale]
101 | k = (Qt.IgnoreAspectRatio, Qt.KeepAspectRatio)[self.keepAspectRatio]
102 |
103 | if self.animated:
104 | self.movie.setScaledSize(QSize(w, h))
105 |
106 | else:
107 | self.pixmap = self.orgpixmap.scaled(w, h, transformMode=t, aspectRatioMode=k)
108 | self.setPixmap(self.pixmap)
109 |
--------------------------------------------------------------------------------
/pyqt5Custom/requesthandler.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import time
7 | import requests
8 |
9 | from PyQt5.QtCore import QThread, pyqtSignal
10 |
11 |
12 |
13 | class RequestHandler(QThread):
14 |
15 | requestResponsed = pyqtSignal(requests.Response)
16 |
17 | def __init__(self):
18 | super().__init__()
19 |
20 | self._request_pool = list()
21 | self._resume = True
22 |
23 | def __repr__():
24 | return f""
25 |
26 | def pause(self):
27 | self._resume = False
28 |
29 | def resume(self):
30 | self._resume = True
31 |
32 | def newRequest(self, method, url, headers=None, data=None):
33 | self._request_pool.append((method, url, headers, data))
34 |
35 | def run(self):
36 | # time.sleeps prevents main window from glitching out, but IDK why
37 | while True:
38 | while not self._resume: time.sleep(0.15)
39 |
40 | if len(self._request_pool) > 0:
41 | for req in self._request_pool:
42 | resp = requests.request(req[0], req[1], headers=req[2], data=req[3])
43 | self.requestResponsed.emit(resp)
44 |
45 | self._request_pool.clear()
46 |
47 | time.sleep(0.15)
48 |
--------------------------------------------------------------------------------
/pyqt5Custom/segbtngroup.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | from PyQt5.QtCore import Qt, pyqtSignal
7 | from PyQt5.QtWidgets import QWidget, QHBoxLayout
8 |
9 | from .styledbutton import StyledButton
10 |
11 |
12 |
13 | class SegmentedButtonGroup(QWidget):
14 |
15 | clicked = pyqtSignal(int)
16 |
17 | def __init__(self, radio=False):
18 | super().__init__()
19 |
20 | self.styleDict = {
21 | "default" : {
22 | "background-image" : None,
23 | "background-color" : (255, 255, 255),
24 |
25 | "border-color" : (0, 0, 0),
26 | "border-width" : 1,
27 | "border-radius" : 11,
28 | "radius-corners" : (True, True, True, True),
29 |
30 | "font-family" : None,
31 | "font-size" : 12,
32 | "font-weight" : "regular",
33 | "color" : (0, 0, 0),
34 |
35 | "drop-shadow-radius" : 0,
36 | "drop-shadow-offset" : (0, 0),
37 | "drop-shadow-alpha" : 120,
38 |
39 | "click-effect-radius" : 500,
40 | "click-effect-color" : (0, 0, 0, 90),
41 |
42 | "render-fast" : False,
43 | "render-aa" : True,
44 | "font-subpixel-aa" : False
45 | },
46 |
47 | "hover" : {
48 | "background-image" : None,
49 | "background-color" : (245, 245, 245),
50 |
51 | "border-color" : (0, 0, 0),
52 | "border-width" : 1,
53 | "border-radius" : 11,
54 |
55 | "font-size" : 12,
56 | "font-weight" : "regular",
57 | "color" : (0, 0, 0),
58 |
59 | "drop-shadow-radius" : 0,
60 | "drop-shadow-offset" : (0, 0),
61 | "drop-shadow-alpha" : 120
62 | },
63 |
64 | "press" : {
65 | "background-image" : None,
66 | "background-color" : (228, 228, 228),
67 |
68 | "border-color" : (0, 0, 0),
69 | "border-width" : 1,
70 | "border-radius" : 11,
71 |
72 | "font-size" : 12,
73 | "font-weight" : "regular",
74 | "color" : (0, 0, 0),
75 |
76 | "drop-shadow-radius" : 0,
77 | "drop-shadow-offset" : (0, 0),
78 | "drop-shadow-alpha" : 120
79 | },
80 |
81 | "check-hover" : {
82 | "background-image" : None,
83 | "background-color" : (245, 245, 245),
84 |
85 | "border-color" : (0, 0, 0),
86 | "border-width" : 1,
87 | "border-radius" : 11,
88 |
89 | "font-size" : 12,
90 | "font-weight" : "regular",
91 | "color" : (0, 0, 0),
92 |
93 | "drop-shadow-radius" : 0,
94 | "drop-shadow-offset" : (0, 0),
95 | "drop-shadow-alpha" : 120
96 | },
97 | }
98 |
99 | self.radio = radio
100 |
101 | self._buttons = list()
102 |
103 | self.layout = QHBoxLayout()
104 | self.setLayout(self.layout)
105 | self.layout.setContentsMargins(2, 0, 2, 0)
106 | self.layout.setSpacing(0)
107 |
108 | def __repr__(self):
109 | return f""
110 |
111 | def setStyleDict(self, styledict, state=None):
112 | if state is None:
113 | for k in styledict:
114 | self.styleDict["default"][k] = styledict[k]
115 | self.styleDict["hover"][k] = styledict[k]
116 | self.styleDict["press"][k] = styledict[k]
117 | self.styleDict["check-hover"][k] = styledict[k]
118 | else:
119 | for k in styledict:
120 | self.styleDict[state][k] = styledict[k]
121 |
122 | def addButton(self, text="", icon=None, tag=None):
123 | btn = StyledButton(text=text, icon=None)
124 | btn.setStyleDict(self.styleDict["default"])
125 | btn.setStyleDict(self.styleDict["hover"], "hover")
126 | btn.setStyleDict(self.styleDict["press"], "press")
127 | btn.setStyleDict(self.styleDict["check-hover"], "check-hover")
128 | if self.radio:
129 | btn.setCheckable(True)
130 |
131 | if tag is None: tag = id(btn)
132 | self._buttons.append((tag, btn))
133 | self.layout.addWidget(btn)
134 |
135 | if len(self._buttons) == 1:
136 | btn.setStyleDict({"radius-corners":(True, False, True, False)})
137 |
138 | else:
139 | if len(self._buttons) >= 3:
140 | btnPrev = self._buttons[-2][1]
141 | btnPrev.setStyleDict({"radius-corners":(False, False, False, False)})
142 | btn.setStyleDict({"radius-corners":(False, True, False, True)})
143 |
144 | for btnn in self._buttons:
145 | btnn[1].setFixedSize(self.width()/len(self._buttons), self.height())
146 |
147 | @btn.clicked.connect
148 | def slot():
149 | self._clicked(tag)
150 | self.clicked.emit(tag)
151 |
152 | l = len(self._buttons)
153 | self.layout.setContentsMargins(l*2, 0, l*2, 0)
154 | return btn
155 |
156 | def getByTag(self, tag):
157 | for btn in self._buttons:
158 | if btn[0] == tag: return btn[1]
159 |
160 | def _clicked(self, tag):
161 | for btn in self._buttons:
162 | if btn[0] != tag:
163 | if btn[1].isChecked():
164 | btn[1].anim_press.start(reverse=True)
165 | btn[1]._was_checked = False
166 | btn[1].setChecked(False)
167 |
--------------------------------------------------------------------------------
/pyqt5Custom/spinner.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | import time
7 | from math import sin, cos, radians
8 |
9 | from PyQt5.QtCore import Qt, pyqtSignal
10 | from PyQt5.QtWidgets import QWidget, QHBoxLayout
11 | from PyQt5.QtGui import QPainter, QPen
12 |
13 | from .animation import Animation, AnimationHandler
14 |
15 |
16 |
17 | class Spinner(QWidget):
18 | def __init__(self, width, color):
19 | super().__init__()
20 |
21 | self.w = width
22 | self.color = color
23 |
24 | self.angle = 0
25 | self.speed = 4.8
26 |
27 | self.animType = 1
28 |
29 | self.play = True
30 |
31 | self.last_call = time.time()
32 |
33 | def __repr__(self):
34 | return f""
35 |
36 | def paintEvent(self, event):
37 | pt = QPainter()
38 | pt.begin(self)
39 | pt.setRenderHint(QPainter.Antialiasing, on=True)
40 |
41 | w = self.w
42 | pen = QPen(self.color, w)
43 | pt.setPen(pen)
44 |
45 | if self.animType == 0:
46 | pt.drawArc(w, w, self.width()-w*2, self.height()-w*2, self.angle, 90*16)
47 |
48 | elif self.animType == 1:
49 | sa = ((sin(radians(self.angle/16))+1)/2)*(180*16) + ((sin(radians((self.angle/16)+130))+1)/2)*(180*16)
50 | pt.drawArc(w, w, self.width()-w*2, self.height()-w*2, self.angle, sa)
51 |
52 | pt.end()
53 |
54 | ep = (time.time()-self.last_call)*1000
55 | self.last_call = time.time()
56 |
57 | self.angle += self.speed*ep
58 | if self.angle > 360*16:
59 | self.angle = 0
60 |
61 | elif self.angle < 0:
62 | self.angle = 360*16
63 |
64 | if self.play: self.update()
65 |
--------------------------------------------------------------------------------
/pyqt5Custom/styledbutton.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | from PyQt5.QtCore import Qt, QPointF
7 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QAbstractButton, QGraphicsDropShadowEffect, QGraphicsOpacityEffect
8 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QFont
9 |
10 | from .animation import Animation, AnimationHandler
11 | from .imagebox import ImageBox
12 |
13 |
14 |
15 | class StyledButton(QAbstractButton):
16 | def __init__(self, text="", icon=None):
17 | super().__init__()
18 |
19 | self.setMinimumSize(100, 45)
20 |
21 | self.layout = QHBoxLayout()
22 | self.layout.setContentsMargins(0, 0, 0, 0)
23 | self.setLayout(self.layout)
24 |
25 | self.conwdt = QWidget()
26 | self.conlyt = QHBoxLayout()
27 | self.conlyt.setContentsMargins(0, 0, 0, 0)
28 | self.conwdt.setLayout(self.conlyt)
29 | self.layout.addWidget(self.conwdt, alignment=Qt.AlignCenter)
30 |
31 | self._text = text
32 | self.textLbl = QLabel(text)
33 | self.conlyt.addWidget(self.textLbl, alignment=Qt.AlignCenter)
34 |
35 | self._icon = None
36 | if icon is not None:
37 | self.setIcon(icon)
38 |
39 | # REMOVE
40 | self.opacity = QGraphicsOpacityEffect()
41 | self.opacity.setOpacity(1.0)
42 | #self.setGraphicsEffect(self.opacity)
43 |
44 | self.shadow = QGraphicsDropShadowEffect()
45 | self.shadow.setBlurRadius(0.8)
46 | self.shadow.setColor(QColor(0, 0, 0, 100))
47 | self.shadow.setOffset(0, 2)
48 | self.setGraphicsEffect(self.shadow)
49 |
50 | self.styleDict = {
51 | "default" : {
52 | "background-image" : None,
53 | "background-color" : (255, 255, 255),
54 |
55 | "border-color" : (0, 0, 0),
56 | "border-width" : 1,
57 | "border-radius" : 11,
58 | "radius-corners" : (True, True, True, True),
59 |
60 | "font-family" : None,
61 | "font-size" : 12,
62 | "font-weight" : "regular",
63 | "color" : (0, 0, 0),
64 |
65 | "drop-shadow-radius" : 0,
66 | "drop-shadow-offset" : (0, 0),
67 | "drop-shadow-alpha" : 120,
68 |
69 | "click-effect-radius" : 500,
70 | "click-effect-color" : (0, 0, 0, 90),
71 |
72 | "render-fast" : False,
73 | "render-aa" : True,
74 | "font-subpixel-aa" : False
75 | },
76 |
77 | "hover" : {
78 | "background-image" : None,
79 | "background-color" : (245, 245, 245),
80 |
81 | "border-color" : (0, 0, 0),
82 | "border-width" : 1,
83 | "border-radius" : 11,
84 |
85 | "font-size" : 12,
86 | "font-weight" : "regular",
87 | "color" : (0, 0, 0),
88 |
89 | "drop-shadow-radius" : 0,
90 | "drop-shadow-offset" : (0, 0),
91 | "drop-shadow-alpha" : 120
92 | },
93 |
94 | "press" : {
95 | "background-image" : None,
96 | "background-color" : (228, 228, 228),
97 |
98 | "border-color" : (0, 0, 0),
99 | "border-width" : 1,
100 | "border-radius" : 11,
101 |
102 | "font-size" : 12,
103 | "font-weight" : "regular",
104 | "color" : (0, 0, 0),
105 |
106 | "drop-shadow-radius" : 0,
107 | "drop-shadow-offset" : (0, 0),
108 | "drop-shadow-alpha" : 120
109 | },
110 |
111 | "check-hover" : {
112 | "background-image" : None,
113 | "background-color" : (245, 245, 245),
114 |
115 | "border-color" : (0, 0, 0),
116 | "border-width" : 1,
117 | "border-radius" : 11,
118 |
119 | "font-size" : 12,
120 | "font-weight" : "regular",
121 | "color" : (0, 0, 0),
122 |
123 | "drop-shadow-radius" : 0,
124 | "drop-shadow-offset" : (0, 0),
125 | "drop-shadow-alpha" : 120
126 | },
127 | }
128 |
129 | self.anim_hover = AnimationHandler(self, 0, 1, Animation.easeOutCirc)
130 | self.anim_press = AnimationHandler(self, 0, 1, Animation.easeOutCubic)
131 |
132 | self._press_reset = False
133 | self.mouse_x, self.mouse_y = 0, 0
134 | self._mouse_pressed = False
135 | self._hover = False
136 | self._was_checked = False
137 |
138 | def __repr__(self):
139 | return f""
140 |
141 | def setText(self, text):
142 | self._text = text
143 | self.textLbl.setText(self._text)
144 |
145 | def text(self):
146 | return self._text
147 |
148 | def setStyleDict(self, styledict, state=None):
149 | if state is None:
150 | for k in styledict:
151 | self.styleDict["default"][k] = styledict[k]
152 | self.styleDict["hover"][k] = styledict[k]
153 | self.styleDict["press"][k] = styledict[k]
154 | self.styleDict["check-hover"][k] = styledict[k]
155 | else:
156 | for k in styledict:
157 | self.styleDict[state][k] = styledict[k]
158 |
159 | def copyStyleDict(self, widget):
160 | for state in widget.styleDict:
161 | self.setStyleDict(widget.styleDict[state], state)
162 |
163 | def setIcon(self, icon):
164 | if self._icon is not None:
165 | self._icon.deleteLater()
166 |
167 | if isinstance(icon, str):
168 | self._icon = ImageBox(icon)
169 | self._icon.setFixedSize(18, 18)
170 | if self._text:
171 | self.textLbl.show()
172 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignVCenter|Qt.AlignRight)
173 | self.conlyt.removeItem(self.conlyt.itemAt(1))
174 | self.conlyt.addWidget(self.textLbl, alignment=Qt.AlignVCenter|Qt.AlignLeft)
175 | else:
176 | self.textLbl.hide()
177 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignCenter)
178 |
179 | else:
180 | self._icon = icon
181 | self._icon.setFixedSize(18, 18)
182 | if self._text:
183 | self.textLbl.show()
184 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignVCenter|Qt.AlignRight)
185 | self.conlyt.removeItem(self.conlyt.itemAt(1))
186 | self.conlyt.addWidget(self.textLbl, alignment=Qt.AlignVCenter|Qt.AlignLeft)
187 |
188 | else:
189 | self.textLbl.hide()
190 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignCenter)
191 |
192 | def setIconSize(self, width, height):
193 | self._icon.setFixedSize(width, height)
194 |
195 | def update(self):
196 | self.anim_hover.update()
197 | self.anim_press.update()
198 |
199 | super().update()
200 |
201 | def enterEvent(self, event):
202 | self.anim_hover.start()
203 | self._hover = True
204 |
205 | if not self.isChecked():
206 | self._was_checked = False
207 |
208 | def leaveEvent(self, event):
209 | self.anim_hover.start(reverse=True)
210 | self._hover = False
211 |
212 | if not self.isChecked():
213 | self._was_checked = False
214 |
215 | def mousePressEvent(self, event):
216 | self.mouse_x, self.mouse_y = event.x(), event.y()
217 | self._mouse_pressed = True
218 | self.anim_press.start()
219 |
220 | if self.isChecked(): self._was_checked = True
221 |
222 | super().mousePressEvent(event)
223 |
224 | def mouseReleaseEvent(self, event):
225 | self._mouse_pressed = False
226 | self.anim_press.start(reverse=True)
227 |
228 | super().mouseReleaseEvent(event)
229 |
230 | def paintEvent(self, event):
231 | pt = QPainter()
232 | pt.begin(self)
233 | pt.setRenderHint(QPainter.Antialiasing, on=self.styleDict["default"]["render-aa"])
234 |
235 | dsr1 = self.styleDict["default"]["drop-shadow-radius"]
236 | dsr2 = self.styleDict["hover"]["drop-shadow-radius"]
237 | dsr3 = self.styleDict["press"]["drop-shadow-radius"]
238 | dso1 = self.styleDict["default"]["drop-shadow-offset"]
239 | dso2 = self.styleDict["hover"]["drop-shadow-offset"]
240 | dso3 = self.styleDict["press"]["drop-shadow-offset"]
241 | dsc1 = self.styleDict["default"]["drop-shadow-alpha"]
242 | dsc2 = self.styleDict["hover"]["drop-shadow-alpha"]
243 | dsc3 = self.styleDict["press"]["drop-shadow-alpha"]
244 |
245 | if self.anim_press.current() > 0.001:
246 | dsr = dsr3
247 | dso = dso3
248 | dsc = dsc3
249 | elif self._hover:
250 | dsr = dsr2
251 | dso = dso2
252 | dsc = dsc2
253 | else:
254 | dsr = dsr1
255 | dso = dso1
256 | dsc = dsc1
257 |
258 | if dsr == 0:
259 | self.shadow.setEnabled(False)
260 | else:
261 | self.shadow.setEnabled(True)
262 | self.shadow.setBlurRadius(dsr)
263 | self.shadow.setOffset(*dso)
264 | self.shadow.setColor(QColor(0, 0, 0, dsc))
265 |
266 | if self.isChecked():
267 | fc1 = QColor(*self.styleDict["press"]["color"])
268 | fc2 = QColor(*self.styleDict["check-hover"]["color"])
269 | fc = self.anim_hover.lerp(fc1, fc2)
270 | else:
271 | fc1 = QColor(*self.styleDict["default"]["color"])
272 | fc2 = QColor(*self.styleDict["hover"]["color"])
273 | fc3 = QColor(*self.styleDict["press"]["color"])
274 | if self.anim_press.current() > 0.001 and self._hover: fc = self.anim_press.lerp(fc2, fc3)
275 | elif self.anim_press.current() > 0.001: fc = self.anim_press.lerp(fc1, fc3)
276 | else: fc = self.anim_hover.lerp(fc1, fc2)
277 |
278 | plt = self.textLbl.palette()
279 | plt.setColor(self.textLbl.foregroundRole(), fc)
280 | self.textLbl.setPalette(plt)
281 |
282 | fs1 = self.styleDict["default"]["font-size"]
283 | fs2 = self.styleDict["hover"]["font-size"]
284 | fs3 = self.styleDict["press"]["font-size"]
285 | if self.anim_press.current() > 0.001 and self._hover: fs = self.anim_press.lerp(fs2, fs3)
286 | elif self.anim_press.current() > 0.001: fs = self.anim_press.lerp(fs1, fs3)
287 | else: fs = self.anim_hover.lerp(fs1, fs2)
288 |
289 | fnt = self.textLbl.font()
290 | fnt.setPixelSize(fs)
291 | if not self.styleDict["default"]["font-subpixel-aa"]: fnt.setStyleStrategy(QFont.NoSubpixelAntialias)
292 | if self.styleDict["default"]["font-family"]: fnt.setFamily(self.styleDict["default"]["font-family"])
293 | self.textLbl.setFont(fnt)
294 |
295 | pc1 = QColor(*self.styleDict["default"]["border-color"])
296 | pc2 = QColor(*self.styleDict["hover"]["border-color"])
297 | pc3 = QColor(*self.styleDict["press"]["border-color"])
298 | if self.anim_press.current() > 0.001 and self._hover: pc = self.anim_press.lerp(pc2, pc3)
299 | elif self.anim_press.current() > 0.001: pc = self.anim_press.lerp(pc1, pc3)
300 | else: pc = self.anim_hover.lerp(pc1, pc2)
301 |
302 | pw1 = self.styleDict["default"]["border-width"]
303 | pw2 = self.styleDict["hover"]["border-width"]
304 | pw3 = self.styleDict["press"]["border-width"]
305 | if self.anim_press.current() > 0.001 and self._hover: pw = self.anim_press.lerp(pw2, pw3)
306 | elif self.anim_press.current() > 0.001: pw = self.anim_press.lerp(pw1, pw3)
307 | else: pw = self.anim_hover.lerp(pw1, pw2)
308 |
309 | pen = QPen(pc, pw)
310 |
311 | if self.isChecked():
312 | b1 = QColor(*self.styleDict["press"]["background-color"])
313 | b2 = QColor(*self.styleDict["check-hover"]["background-color"])
314 | b = self.anim_hover.lerp(b1, b2)
315 | else:
316 | b1 = QColor(*self.styleDict["default"]["background-color"])
317 | b2 = QColor(*self.styleDict["hover"]["background-color"])
318 | b3 = QColor(*self.styleDict["press"]["background-color"])
319 | b4 = QColor(*self.styleDict["check-hover"]["background-color"])
320 | if self._was_checked: b = self.anim_press.lerp(b2, b4)
321 | elif self.anim_press.current() > 0.001 and self._hover: b = self.anim_press.lerp(b2, b3)
322 | elif self.anim_press.current() > 0.001: b = self.anim_press.lerp(b1, b3)
323 | else: b = self.anim_hover.lerp(b1, b2)
324 |
325 | brush = QBrush(b)
326 |
327 | pt.setPen(pen)
328 | pt.setBrush(brush)
329 |
330 | r1 = self.styleDict["default"]["border-radius"]
331 | r2 = self.styleDict["hover"]["border-radius"]
332 | r3 = self.styleDict["press"]["border-radius"]
333 | if self.anim_press.current() > 0.001 and self._hover: r = self.anim_press.lerp(r2, r3)
334 | elif self.anim_press.current() > 0.001: r = self.anim_press.lerp(r1, r3)
335 | else: r = self.anim_hover.lerp(r1, r2)
336 |
337 | if r > self.height()/2: r = self.height()/2
338 |
339 |
340 | crn = self.styleDict["default"]["radius-corners"]
341 |
342 | if all(crn) and self.styleDict["default"]["render-fast"]:
343 | pt.drawRoundedRect(1, 1, self.width()-2, self.height()-2, r, r)
344 |
345 | else:
346 | pt.setPen(QPen(QColor(0, 0, 0, 0)))
347 | pt.setBrush(QBrush(pc))
348 |
349 | pt.drawRect(0, r, self.width(), self.height()-r*2)
350 | pt.drawRect(r, 0, self.width()-r*2, self.height())
351 |
352 | if crn[0]: pt.drawEllipse(QPointF(r, r), r, r)
353 | else: pt.drawRect(0, 0, r, r)
354 | if crn[3]: pt.drawEllipse(QPointF(self.width()-r, self.height()-r), r, r)
355 | else: pt.drawRect(self.width()-r, self.height()-r, r, r)
356 | if crn[2]: pt.drawEllipse(QPointF(r, self.height()-r), r, r)
357 | else: pt.drawRect(0, self.height()-r, r, r)
358 | if crn[1]: pt.drawEllipse(QPointF(self.width()-r, r), r, r)
359 | else: pt.drawRect(self.width()-r, 0, r, r)
360 |
361 | pt.setBrush(QBrush(b))
362 |
363 | pt.drawRect(pw, r+pw, self.width()-pw*2, self.height()-r*2-pw*2)
364 | pt.drawRect(r+pw, pw, self.width()-r*2-pw*2, self.height()-pw*2)
365 | if crn[0]: pt.drawEllipse(QPointF(r, r), r-pw, r-pw)
366 | else: pt.drawRect(pw, pw, r+1, r+1)
367 | if crn[3]: pt.drawEllipse(QPointF(self.width()-r, self.height()-r), r-pw, r-pw)
368 | else: pt.drawRect(self.width()-r-pw, self.height()-r-pw, r, r)
369 | if crn[2]: pt.drawEllipse(QPointF(r, self.height()-r), r-pw, r-pw)
370 | else: pt.drawRect(pw, self.height()-r-pw, r, r)
371 | if crn[1]: pt.drawEllipse(QPointF(self.width()-r, r), r-pw, r-pw)
372 | else: pt.drawRect(self.width()-r-pw, pw, r, r)
373 |
374 | pt.end()
375 |
376 | if not self.anim_hover.done(): self.update()
377 | if not self.anim_press.done(): self.update()
378 |
--------------------------------------------------------------------------------
/pyqt5Custom/syntax/cpp.json:
--------------------------------------------------------------------------------
1 | {
2 | "keywords" : [
3 | "auto", "break", "case", "char", "const", "continue", "default",
4 | "double", "else", "enum", "extern", "float", "for", "goto", "if",
5 | "int", "long", "register", "return", "short", "signed", "sizeof",
6 | "static", "do", "struct", "switch", "typedef", "union", "unsigned",
7 | "volatile", "while", "void", "asm", "dynamic_cast", "namespace",
8 | "reinterpret_cast", "bool", "explicit", "new", "static_cast",
9 | "false", "catch", "operator", "template", "friend", "private",
10 | "class", "inline", "public", "throw", "const_cast",
11 | "delete", "mutable", "protected", "true", "try",
12 | "typeid", "typename", "using", "virtual", "wchar_t"
13 | ],
14 |
15 | "this" : "this",
16 |
17 | "comment" : "//"
18 | }
19 |
--------------------------------------------------------------------------------
/pyqt5Custom/syntax/python.json:
--------------------------------------------------------------------------------
1 | {
2 | "keywords" : [
3 | "and", "assert", "break", "class", "continue", "def",
4 | "del", "elif", "else", "except", "exec", "finally",
5 | "for", "from", "global", "if", "import", "in",
6 | "is", "lambda", "not", "or", "pass",
7 | "raise", "return", "try", "while", "yield",
8 | "None", "True", "False"
9 | ],
10 |
11 | "this" : "self",
12 |
13 | "comment" : "#"
14 | }
15 |
--------------------------------------------------------------------------------
/pyqt5Custom/syntaxhighlighter.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 | # #
5 | # DISCLAIMER: This class uses the JSON files in the #
6 | # syntax and themes folders which has #
7 | # pre-defined syntax rules and themes. #
8 | # Don't forget to include them in your #
9 | # package's folder if you're installing #
10 | # this project manually. #
11 |
12 |
13 | import json
14 | import pathlib
15 |
16 | from PyQt5.QtCore import QRegExp
17 | from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter
18 |
19 |
20 |
21 | class SyntaxHighlighter(QSyntaxHighlighter):
22 |
23 | AVAILABLE_LANGS = {
24 | "plain" : "plain",
25 | "py" : "python",
26 | "python" : "python",
27 | "cpp" : "cpp",
28 | "c++" : "cpp"
29 | }
30 |
31 | def __init__(self, document, lang="python", theme="default"):
32 | super().__init__(document)
33 |
34 | self.setLang(lang)
35 | self.setTheme(theme)
36 | self.setRules()
37 |
38 | def formatThemeKey(self, color, style="", returnColor=False):
39 | _color = QColor()
40 | if type(color) is not str:
41 | _color.setRgb(color[0], color[1], color[2])
42 |
43 | elif color.startswith("#"):
44 | hexc = color.lstrip('#')
45 | hexc = tuple(int(hexc[i:i+2], 16) for i in (0, 2, 4))
46 | _color.setRgb(*hexc)
47 |
48 | else:
49 | _color.setNamedColor(color)
50 |
51 | if returnColor: return _color
52 |
53 | _format = QTextCharFormat()
54 | _format.setForeground(_color)
55 | if "bold" in style: _format.setFontWeight(QFont.Bold)
56 | if "italic" in style: _format.setFontItalic(True)
57 |
58 | return _format
59 |
60 | def setTheme(self, theme):
61 | if isinstance(theme, dict):
62 | pass
63 |
64 | else:
65 | path = pathlib.Path(__file__).parents[0] / "themes" / f"{theme}.json"
66 | with open(path, "r", encoding="utf-8") as f:
67 | s = json.loads(f.read())
68 |
69 | self.theme = dict()
70 |
71 | for k in s:
72 | p = s[k].split("-")
73 | if len(p) == 2: self.theme[k] = self.formatThemeKey(s[k], p[1])
74 | else: self.theme[k] = self.formatThemeKey(s[k])
75 |
76 | self.theme["background"] = self.formatThemeKey(s["background"], returnColor=True)
77 | self.theme["lines-background"] = self.formatThemeKey(s["lines-background"], returnColor=True)
78 | self.theme["lines"] = self.formatThemeKey(s["lines"], returnColor=True)
79 | self.theme["identifier"] = self.formatThemeKey(s["identifier"], returnColor=True)
80 |
81 | def setLang(self, lang):
82 | lang = lang.lower()
83 |
84 | if lang in SyntaxHighlighter.AVAILABLE_LANGS:
85 | self.lang = SyntaxHighlighter.AVAILABLE_LANGS[lang]
86 |
87 | if self.lang == "plain": return
88 |
89 | path = pathlib.Path(__file__).parents[0] / "syntax" / f"{self.lang}.json"
90 | with open(path, "r", encoding="utf-8") as f:
91 | rr = json.loads(f.read())
92 |
93 | self.keywords = rr["keywords"]
94 | self.this = rr["this"]
95 | self.comment = rr["comment"]
96 |
97 | self.operators = operators = (
98 | "=",
99 | "==", "!=", "<", "<=", ">", ">=",
100 | "\+", "-", "\*", "/", "//", "\%", "\*\*",
101 | "\+=", "-=", "\*=", "/=", "\%=",
102 | "\^", "\|", "\&", "\~", ">>", "<<",
103 | "\+\+", "--", "\&\&", "\|\|"
104 | )
105 |
106 | self.braces = (
107 | "\{", "\}", "\(", "\)", "\[", "\]",
108 | )
109 |
110 | else:
111 | raise ValueError(f"Language '{lang}' is not supported.")
112 |
113 | def setRules(self):
114 | self.tri_single = (QRegExp("'''"), 1, self.theme["string"])
115 | self.tri_double = (QRegExp('"""'), 2, self.theme["string"])
116 |
117 | if self.lang == "plain":
118 | self.rules = list()
119 | return
120 |
121 | rules = list()
122 |
123 | rules += [(r'\b%s\b' % w, 0, self.theme["keyword"]) for w in self.keywords]
124 |
125 | rules += [(r'%s' % o, 0, self.theme["operator"]) for o in self.operators]
126 |
127 | rules += [(r'%s' % b, 0, self.theme["brace"]) for b in self.braces]
128 |
129 | if self.lang in ("cpp", "c"):
130 | rules.append( (r'\#[^\n]*', 0, self.theme["preprocessor"]) )
131 |
132 | rules += [
133 | (r"\b[A-Za-z0-9_]+(?=\()", 0, self.theme["function"]),
134 |
135 | (r'\b%s\b' % self.this, 0, self.theme["this"]),
136 |
137 | (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.theme["string"]),
138 |
139 | (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.theme["string"]),
140 |
141 | (r'%s[^\n]*' % self.comment, 0, self.theme["comment"]),
142 |
143 | (r'\b[+-]?[0-9]+[lL]?\b', 0, self.theme["numeric"]),
144 | (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, self.theme["numeric"]),
145 | (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, self.theme["numeric"])
146 | ]
147 |
148 | self.rules = [(QRegExp(pat), index, fmt) for (pat, index, fmt) in rules]
149 |
150 | def highlightBlock(self, text):
151 | for expression, nth, format in self.rules:
152 | index = expression.indexIn(text, 0)
153 |
154 | while index >= 0:
155 | index = expression.pos(nth)
156 | length = len(expression.cap(nth))
157 | self.setFormat(index, length, format)
158 | index = expression.indexIn(text, index + length)
159 |
160 | self.setCurrentBlockState(0)
161 |
162 | in_multiline = self.match_multiline(text, *self.tri_single)
163 | if not in_multiline:
164 | in_multiline = self.match_multiline(text, *self.tri_double)
165 |
166 | def match_multiline(self, text, delimiter, in_state, style):
167 | if self.previousBlockState() == in_state:
168 | start = 0
169 | add = 0
170 |
171 | else:
172 | start = delimiter.indexIn(text)
173 | add = delimiter.matchedLength()
174 |
175 | while start >= 0:
176 | end = delimiter.indexIn(text, start + add)
177 |
178 | if end >= add:
179 | length = end - start + add + delimiter.matchedLength()
180 | self.setCurrentBlockState(0)
181 |
182 | else:
183 | self.setCurrentBlockState(in_state)
184 | length = len(text) - start + add
185 |
186 | self.setFormat(start, length, style)
187 | start = delimiter.indexIn(text, start + length)
188 |
189 |
190 | if self.currentBlockState() == in_state: return True
191 | else: return False
192 |
--------------------------------------------------------------------------------
/pyqt5Custom/themes/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "background" : "#ffffff",
3 | "lines-background" : "#ffffff",
4 | "lines" : "#bbbbbb",
5 |
6 | "identifier" : "#222222",
7 | "operator" : "#969696",
8 | "brace" : "#6b6b6b",
9 | "string" : "#198052",
10 | "comment" : "#637278-italic",
11 | "keyword" : "#f07116",
12 | "numeric" : "#6496be",
13 | "this" : "#96558c-italic",
14 | "function" : "#4993b8",
15 | "preprocessor" : "#f07116"
16 | }
17 |
--------------------------------------------------------------------------------
/pyqt5Custom/themes/monokai.json:
--------------------------------------------------------------------------------
1 | {
2 | "background" : "263238",
3 | "lines-background" : "263238",
4 | "lines" : "#526771",
5 |
6 | "identifier" : "#F8F8F2",
7 | "operator" : "#cccccc",
8 | "brace" : "#abb2bf",
9 | "string" : "#E6DB74",
10 | "comment" : "#5F7A87-italic",
11 | "keyword" : "#FF2C96",
12 | "numeric" : "#B78AFF",
13 | "this" : "#2EDCFF-italic",
14 | "function" : "#9DFF00",
15 | "preprocessor" : "#FF2C96"
16 | }
17 |
--------------------------------------------------------------------------------
/pyqt5Custom/themes/oceanic.json:
--------------------------------------------------------------------------------
1 | {
2 | "background" : "#032d38",
3 | "lines-background" : "#032d38",
4 | "lines" : "#4b5f66",
5 |
6 | "identifier" : "#9bb1b4",
7 | "operator" : "#9bb1b4",
8 | "brace" : "#9bb1b4",
9 | "string" : "#31afa5",
10 | "comment" : "#4b5f66-italic",
11 | "keyword" : "#90a507",
12 | "numeric" : "#31afa5",
13 | "this" : "#b58910-italic",
14 | "function" : "#2f95dc",
15 | "preprocessor" : "#90a507"
16 | }
17 |
--------------------------------------------------------------------------------
/pyqt5Custom/themes/one-dark.json:
--------------------------------------------------------------------------------
1 | {
2 | "background" : "#282c34",
3 | "lines-background" : "#282c34",
4 | "lines" : "#525b6e",
5 |
6 | "identifier" : "#abb2bf",
7 | "operator" : "#c678dd",
8 | "brace" : "#abb2bf",
9 | "string" : "#98c379",
10 | "comment" : "#5e6a84-italic",
11 | "keyword" : "#c678dd",
12 | "numeric" : "#d19a66",
13 | "this" : "#de6a73-italic",
14 | "function" : "#61afef",
15 | "preprocessor" : "#c678dd"
16 | }
17 |
--------------------------------------------------------------------------------
/pyqt5Custom/themes/one-light.json:
--------------------------------------------------------------------------------
1 | {
2 | "background" : "#fafafa",
3 | "lines-background" : "#fafafa",
4 | "lines" : "#a7a7a7",
5 |
6 | "identifier" : "#383a42",
7 | "operator" : "#4078f2",
8 | "brace" : "#4e5260",
9 | "string" : "#50a14f",
10 | "comment" : "#a7a7a7-italic",
11 | "keyword" : "#a626a4",
12 | "numeric" : "#986801",
13 | "this" : "#e45649-italic",
14 | "function" : "#4078f2",
15 | "preprocessor" : "#a626a4"
16 | }
17 |
--------------------------------------------------------------------------------
/pyqt5Custom/themes/zenburn.json:
--------------------------------------------------------------------------------
1 | {
2 | "background" : "#3f3f3f",
3 | "lines-background" : "#3f3f3f",
4 | "lines" : "#627483",
5 |
6 | "identifier" : "#e3e2d5",
7 | "operator" : "#e3e2d5",
8 | "brace" : "#e3e2d5",
9 | "string" : "#a06666",
10 | "comment" : "#627483-italic",
11 | "keyword" : "#e6bda0",
12 | "numeric" : "#9bd8dc",
13 | "this" : "#e4a1ce-italic",
14 | "function" : "#ede7a5",
15 | "preprocessor" : "#e6bda0"
16 | }
17 |
--------------------------------------------------------------------------------
/pyqt5Custom/titlebar.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | from PyQt5.QtCore import Qt
7 | from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QLabel
8 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QFont
9 |
10 | from .animation import Animation, AnimationHandler
11 | from .styledbutton import StyledButton
12 |
13 |
14 |
15 | class TitleBar(QWidget):
16 | def __init__(self, parent, title=""):
17 | super().__init__(parent)
18 |
19 | self.parent().setWindowTitle(title)
20 | self.parent().setWindowFlags(Qt.FramelessWindowHint)
21 |
22 | setattr(self.parent(), "mousePressEvent", self.parentMousePressEvent)
23 | setattr(self.parent(), "mouseMoveEvent", self.parentMouseMoveEvent)
24 | setattr(self.parent(), "mouseReleaseEvent", self.parentMouseReleaseEvent)
25 |
26 | self._resizable = True
27 |
28 | self.setFixedHeight(32)
29 |
30 | self.styleDict = {
31 | "background-color" : (255, 255, 255),
32 |
33 | "font-family" : None,
34 | "font-size" : 12,
35 | "font-weight" : "regular",
36 | "color" : (0, 0, 0),
37 |
38 | "render-aa" : True,
39 | "font-subpixel-aa" : False
40 | }
41 |
42 | self.layout = QHBoxLayout()
43 | self.layout.setContentsMargins(14, 0, 0, 0)
44 | self.layout.setSpacing(0)
45 | self.setLayout(self.layout)
46 |
47 | self.title_lbl = QLabel(title)
48 | self.layout.addWidget(self.title_lbl)
49 |
50 | self.closeButton = StyledButton(text="✕")
51 | self.closeButton.setFixedSize(self.height()+19, self.height())
52 |
53 | self.maxButton = StyledButton(text="🗖")
54 | self.maxButton.setFixedSize(self.height()+19, self.height())
55 |
56 | self.minButton = StyledButton(text="🗕")
57 | self.minButton.setFixedSize(self.height()+19, self.height())
58 |
59 | self._styleControlButtons()
60 |
61 | self.closeButton.clicked.connect(self.parent().close)
62 |
63 | self.minButton.clicked.connect(self.parent().showMinimized)
64 |
65 | @self.maxButton.clicked.connect
66 | def slot():
67 | if self.parent().isMaximized():
68 | self.parent().showNormal()
69 | else:
70 | self.parent().showMaximized()
71 |
72 | self.layout.addWidget(self.minButton, alignment=Qt.AlignRight)
73 | self.layout.addWidget(self.maxButton)
74 | self.layout.addWidget(self.closeButton)
75 |
76 | self.pressing = False
77 |
78 | self.cursize = 9
79 | self.cur = None
80 | self.curs = {
81 | "southeast" : (1, 1),
82 | "east" : (1, 0),
83 | "south" : (0, 1)
84 | }
85 |
86 | self.ccurs = {
87 | "southeast" : Qt.SizeFDiagCursor,
88 | "northeast" : Qt.SizeBDiagCursor,
89 | "southwest" : Qt.SizeBDiagCursor,
90 | "northwest" : Qt.SizeFDiagCursor,
91 | "north" : Qt.SizeVerCursor,
92 | "south" : Qt.SizeVerCursor,
93 | "east" : Qt.SizeHorCursor,
94 | "west" : Qt.SizeHorCursor,
95 | }
96 |
97 | self.anims = list()
98 |
99 | self._styleControlButtons()
100 |
101 | def __repr__(self):
102 | return f""
103 |
104 | def update(self):
105 | for a in self.anims:
106 | a.update()
107 | super().update()
108 |
109 | def newAnimation(self, start=0, end=1, type=Animation.easeOutCubic):
110 | a = AnimationHandler(self, start, end, type)
111 | self.anims.append(a)
112 | return a
113 |
114 | def setWindowResizable(self, resizable):
115 | self._resizable = resizable
116 |
117 | if self._resizable:
118 | self.maxButton.show()
119 |
120 | else:
121 | self.maxButton.hide()
122 |
123 | def setStyleDict(self, styledict):
124 | for k in styledict:
125 | self.styleDict[k] = styledict[k]
126 | #self._styleControlButtons()
127 |
128 | def _styleControlButtons(self):
129 | self.closeButton.setStyleDict({
130 | "font-family" : self.styleDict["font-family"],
131 | "font-size" : self.styleDict["font-size"],
132 | "border-color" : (0, 0, 0, 0),
133 | "border-radius" : 0,
134 | "background-color" : (255, 255, 255)
135 | })
136 | self.closeButton.setStyleDict({
137 | "background-color" : (224, 0, 0),
138 | "color": (255, 255, 255)
139 | }, "hover")
140 | self.closeButton.setStyleDict({
141 | "background-color" : (255, 0, 0),
142 | "color" : (255, 255, 255)
143 | }, "press")
144 |
145 | self.maxButton.setStyleDict({
146 | "font-family" : self.styleDict["font-family"],
147 | "font-size" : self.styleDict["font-size"],
148 | "border-color" : (0, 0, 0, 0),
149 | "border-radius" : 0,
150 | "background-color" : (255, 255, 255)
151 | })
152 | self.maxButton.setStyleDict({
153 | "background-color" : (236, 236, 236),
154 | }, "hover")
155 | self.maxButton.setStyleDict({
156 | "background-color" : (218, 218, 218),
157 | }, "press")
158 |
159 | self.minButton.setStyleDict({
160 | "font-family" : self.styleDict["font-family"],
161 | "font-size" : self.styleDict["font-size"],
162 | "border-color" : (0, 0, 0, 0),
163 | "border-radius" : 0,
164 | "background-color" : (255, 255, 255)
165 | })
166 | self.minButton.setStyleDict({
167 | "background-color" : (236, 236, 236),
168 | }, "hover")
169 | self.minButton.setStyleDict({
170 | "background-color" : (218, 218, 218),
171 | }, "press")
172 |
173 | def setTitle(self, title):
174 | self.parent().setWindowTitle(title)
175 | self.title_lbl.setText(title)
176 |
177 | def title(self):
178 | return self.title_lbl.text()
179 |
180 | def parentMousePressEvent(self, event):
181 | self._start = self.mapToGlobal(event.pos())
182 | self._orw = self.parent().width()
183 | self._orh = self.parent().height()
184 | self._orx = self.parent().x()
185 | self._ory = self.parent().y()
186 |
187 | if event.x() > self.parent().width() - self.cursize:
188 | if event.y() > self.parent().height() - self.cursize:
189 | self.cur = "southeast"
190 |
191 | elif event.y() < self.cursize:
192 | self.cur = "northeast"
193 |
194 | else:
195 | self.cur = "east"
196 |
197 | elif event.x() < self.cursize:
198 | if event.y() > self.parent().height() - self.cursize:
199 | self.cur = "southwest"
200 |
201 | elif event.y() < self.cursize:
202 | self.cur = "northwest"
203 |
204 | else:
205 | self.cur = "west"
206 |
207 | elif event.y() > self.parent().height() - self.cursize:
208 | self.cur = "south"
209 |
210 | elif event.y() < self.cursize:
211 | self.cur = "north"
212 |
213 | else:
214 | self.cur = None
215 |
216 | def parentMouseReleaseEvent(self, event):
217 | self.cur = None
218 | QApplication.restoreOverrideCursor()
219 |
220 | def parentMouseMoveEvent(self, event):
221 | if self.cur is not None and self._resizable:
222 | QApplication.restoreOverrideCursor()
223 | QApplication.setOverrideCursor(self.ccurs[self.cur])
224 |
225 | end = self.mapToGlobal(event.pos())
226 | self._movement = end - self._start
227 |
228 | if self.cur in ("east", "southeast", "south"):
229 | x = self._movement.x() * self.curs[self.cur][0]
230 | y = self._movement.y() * self.curs[self.cur][1]
231 |
232 | if self.parent().width() + x < self.parent().minimumWidth():
233 | end.setX(end.x()-x)
234 | x = 0
235 |
236 | if self.parent().height() + y < self.parent().minimumHeight():
237 | end.setY(end.y()-y)
238 | y = 0
239 |
240 | self.parent().setGeometry(self.parent().x(),
241 | self.parent().y(),
242 | self._orw + x,
243 | self._orh + y)
244 | self.parent().update()
245 |
246 | self._orw += x
247 | self._orh += y
248 |
249 | elif self.cur == "northeast":
250 | x = self._movement.x()
251 | y = self._movement.y()
252 |
253 | self.parent().setGeometry(self.parent().x(),
254 | self._ory + y,
255 | self._orw + x,
256 | self.parent().height() - y)
257 | self.parent().setFixedSize(self._orw+x, self.parent().height()-y)
258 | self.parent().update()
259 |
260 | self._orw += x
261 | self._ory += y
262 |
263 | elif self.cur == "southwest":
264 | x = self._movement.x()
265 | y = self._movement.y()
266 |
267 | #if x > 2: x = 2
268 | #if x < -2: x = -2
269 |
270 | # self.parent().setFixedSize(self.parent().width()-x,
271 | # self._orh + y)
272 | #
273 | # self.parent().move(self._orx - x,
274 | # self.parent().y())
275 |
276 | self.parent().setGeometry(self._orx - x,
277 | self.parent().y(),
278 | self.parent().width()-x,
279 | self._orh + y)
280 | #self.parent().update()
281 |
282 | self._orx += x
283 | self._orh += y
284 | #self._ory += y
285 |
286 | #self.parent().setGeometry(self.parent().x(), self.parent().y(), self._orw+x, self.parent().height())
287 |
288 | self._start = end
289 |
290 | def paintEvent(self, event):
291 | pt = QPainter()
292 | pt.begin(self)
293 | pt.setRenderHint(QPainter.Antialiasing, on=self.styleDict["render-aa"])
294 |
295 | fnt = self.title_lbl.font()
296 | fnt.setPixelSize(self.styleDict["font-size"])
297 | if not self.styleDict["font-subpixel-aa"]: fnt.setStyleStrategy(QFont.NoSubpixelAntialias)
298 | if self.styleDict["font-family"]: fnt.setFamily(self.styleDict["font-family"])
299 | self.title_lbl.setFont(fnt)
300 |
301 | plt = self.title_lbl.palette()
302 | plt.setColor(self.title_lbl.foregroundRole(), QColor(*self.styleDict["color"]))
303 | self.title_lbl.setPalette(plt)
304 |
305 | brush = QBrush(QColor(*self.styleDict["background-color"]))
306 | pen = QPen(QColor(0, 0, 0, 0), 0)
307 | pt.setBrush(brush)
308 | pt.setPen(pen)
309 |
310 | pt.drawRect(0, 0, self.width(), self.height())
311 |
312 | pt.end()
313 |
314 | f = False
315 | for a in self.anims:
316 | if not a.done():
317 | f = True
318 | break
319 |
320 | if f: self.update()
321 |
322 | def mousePressEvent(self, event):
323 | if event.x() < self.parent().width() - 10:
324 | self.start = self.mapToGlobal(event.pos())
325 | self.pressing = True
326 | else:
327 | self.parentMousePressEvent(event)
328 |
329 | def mouseReleaseEvent(self, event):
330 | self.pressing = False
331 | self.parentMouseReleaseEvent(event)
332 |
333 | def mouseMoveEvent(self, event):
334 | if self.pressing:
335 | end = self.mapToGlobal(event.pos())
336 | self.movement = end - self.start
337 | self.parent().setGeometry(self.mapToGlobal(self.movement).x(),
338 | self.mapToGlobal(self.movement).y(),
339 | self.parent().width(),
340 | self.parent().height())
341 | self.start = end
342 | else:
343 | self.parentMouseMoveEvent(event)
344 |
--------------------------------------------------------------------------------
/pyqt5Custom/toast.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | from PyQt5.QtCore import Qt, pyqtSignal
7 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel
8 | from PyQt5.QtGui import QPainter, QPen, QBrush, QColor, QFont
9 |
10 | from .animation import Animation, AnimationHandler
11 | from .imagebox import ImageBox
12 | from .styledbutton import StyledButton
13 |
14 |
15 |
16 | class Toast(QWidget):
17 | def __init__(self, parent, text="", icon=None, closeButton=True):
18 | super().__init__(parent)
19 |
20 | self.setFixedHeight(45)
21 |
22 | w = self.width()
23 | h = self.height()
24 | ww = self.parent().width()
25 | hh = self.parent().height()
26 | self.setGeometry(ww/2-w/2, hh-h-5, w, h)
27 |
28 | self.styleDict = {
29 | "background-color" : (0, 0, 0, 180),
30 | "border-radius" : 16,
31 |
32 | "font-family" : None,
33 | "font-size" : 17,
34 | "color" : (255, 255, 255),
35 |
36 | "font-subpixel-aa" : False
37 | }
38 |
39 | self._closeButton = closeButton
40 |
41 | self.layout = QHBoxLayout()
42 | self.layout.setContentsMargins(15, 0, 7, 0)
43 | self.setLayout(self.layout)
44 |
45 | self.conwdt = QWidget()
46 | self.conlyt = QHBoxLayout()
47 | self.conlyt.setContentsMargins(0, 0, 0, 0)
48 | self.conwdt.setLayout(self.conlyt)
49 | self.layout.addWidget(self.conwdt, alignment=Qt.AlignLeft)
50 |
51 | self.close_btn = StyledButton("✕")
52 | self.layout.addWidget(self.close_btn)
53 | self.close_btn.setFixedSize(self.height()/1.6, self.height()/1.6)
54 | self.close_btn.setStyleDict({
55 | "border-color" : (0, 0, 0, 0),
56 | "background-color" : (0, 0, 0, 0),
57 | "color" : (255, 255, 255),
58 | "font-size" : 14,
59 | "color" : (255, 255, 255, 130),
60 | "border-radius" : 100,
61 | })
62 | self.close_btn.setStyleDict({
63 | "background-color" : (255, 255, 255, 8),
64 | "color" : (255, 255, 255, 255)
65 | }, "hover")
66 | self.close_btn.setStyleDict({
67 | "background-color" : (255, 255, 255, 15),
68 | "color" : (255, 255, 255, 147)
69 | }, "press")
70 |
71 | self.close_btn.clicked.connect(self.fall)
72 |
73 | self.text = text
74 | self.textLbl = QLabel(text)
75 | self.conlyt.addWidget(self.textLbl, alignment=Qt.AlignCenter)
76 |
77 | self._icon = None
78 | if icon is not None:
79 | self.setIcon(icon)
80 |
81 | self.risen = False
82 | self.hide()
83 |
84 | self.anim = AnimationHandler(self, 0, 1, Animation.easeOutQuart)
85 | self.anim.speed = 1.7
86 |
87 | def __repr__(self):
88 | return f""
89 |
90 | def rise(self, duration):
91 | if self.risen: return
92 |
93 | self.duration = duration
94 | self.risen = True
95 | self.anim.start()
96 | self.show()
97 | self.raise_()
98 | self.update()
99 |
100 | def fall(self):
101 | if not self.risen: return
102 |
103 | self.anim.start(reverse=True)
104 | self.risen = False
105 | self.update()
106 |
107 | def setStyleDict(self, styledict):
108 | for k in styledict:
109 | self.styleDict[k] = styledict[k]
110 |
111 | def setIcon(self, icon):
112 | if self._icon is not None:
113 | self._icon.deleteLater()
114 |
115 | if isinstance(icon, str):
116 | self._icon = ImageBox(icon)
117 | self._icon.setFixedSize(18, 18)
118 | if self.text:
119 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignVCenter|Qt.AlignRight)
120 | self.conlyt.removeItem(self.conlyt.itemAt(1))
121 | self.conlyt.addWidget(self.textLbl, alignment=Qt.AlignVCenter|Qt.AlignLeft)
122 | else:
123 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignRight)
124 |
125 | else:
126 | self._icon = icon
127 | self._icon.setFixedSize(18, 18)
128 | if self.text:
129 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignVCenter|Qt.AlignRight)
130 | self.conlyt.removeItem(self.conlyt.itemAt(1))
131 | self.conlyt.addWidget(self.textLbl, alignment=Qt.AlignVCenter|Qt.AlignLeft)
132 |
133 | else:
134 | self.conlyt.insertWidget(0, self._icon, alignment=Qt.AlignCenter)
135 |
136 | def setIconSize(self, width, height):
137 | self._icon.setFixedSize(width, height)
138 |
139 | def setText(self, text):
140 | self.textLbl.setText(text)
141 |
142 | def resizeEvent(self, event):
143 | w = self.width()
144 | h = self.height()
145 | ww = self.parent().width()
146 | hh = self.parent().height()
147 | self.setGeometry(ww/2-w/2, hh-h-5, w, h)
148 |
149 | def update(self):
150 | self.anim.update()
151 | super().update()
152 |
153 | def paintEvent(self, event):
154 | pt = QPainter()
155 | pt.begin(self)
156 | pt.setRenderHint(QPainter.Antialiasing, on=True)
157 |
158 | plt = self.textLbl.palette()
159 | plt.setColor(self.textLbl.foregroundRole(), QColor(*self.styleDict["color"]))
160 | self.textLbl.setPalette(plt)
161 |
162 | fnt = self.textLbl.font()
163 | fnt.setPixelSize(self.styleDict["font-size"])
164 | if not self.styleDict["font-subpixel-aa"]: fnt.setStyleStrategy(QFont.NoSubpixelAntialias)
165 | if self.styleDict["font-family"]: fnt.setFamily(self.styleDict["font-family"])
166 | self.textLbl.setFont(fnt)
167 |
168 | pt.setPen(QPen(QColor(0, 0, 0, 0)))
169 | pt.setBrush(QBrush(QColor(*self.styleDict["background-color"])))
170 | r = self.styleDict["border-radius"]
171 | if r > self.height() / 2: r = self.height() / 2
172 |
173 | pt.drawRoundedRect(0, 0, self.width(), self.height(), r, r)
174 |
175 | pt.end()
176 |
177 | if not self.anim.done():
178 | w = self.width()
179 | h = self.height()
180 | ww = self.parent().width()
181 | hh = self.parent().height()
182 |
183 | self.setGeometry(ww/2-w/2, hh-(self.anim.current()*(h+5))-0.01, w, h)
184 | #print((self.anim.current()*h))
185 |
186 | if not self.anim.done(): self.update()
187 | else:
188 | if self.isVisible() and not self.risen: self.hide()
189 |
--------------------------------------------------------------------------------
/pyqt5Custom/toggleswitch.py:
--------------------------------------------------------------------------------
1 | # PyQt5 Custom Widgets #
2 | # GPL 3.0 - Kadir Aksoy #
3 | # https://github.com/kadir014/pyqt5-custom-widgets #
4 |
5 |
6 | from PyQt5.QtCore import Qt, QEvent, pyqtSignal
7 | from PyQt5.QtWidgets import QWidget, QGraphicsOpacityEffect
8 | from PyQt5.QtGui import QColor, QPainter, QPen, QBrush
9 |
10 | from .animation import Animation, AnimationHandler
11 |
12 |
13 |
14 | class ToggleSwitch(QWidget):
15 |
16 | defaultStyles = ("win10", "ios", "android")
17 |
18 | toggled = pyqtSignal()
19 |
20 | def __init__(self, text="", style="win10", on=False):
21 | super().__init__()
22 |
23 | self.text = text
24 |
25 | self.on = on
26 |
27 | # TODO: find a better way for opacity
28 | self.opacity = QGraphicsOpacityEffect(self)
29 | self.opacity.setOpacity(1)
30 | self.setGraphicsEffect(self.opacity)
31 |
32 | if style not in ToggleSwitch.defaultStyles:
33 | raise Exception(f"'{style}' is not a default style.")
34 | self.style = style
35 |
36 |
37 | if self.style == "win10":
38 | self.onColor = QColor(0, 116, 208)
39 | self.offColor = QColor(0, 0, 0)
40 |
41 | self.handleAlpha = True
42 | self.handleColor = QColor(255, 255, 255)
43 |
44 | self.width = 35
45 | self.radius = 26
46 |
47 | elif self.style == "ios":
48 | self.onColor = QColor(73, 208, 96)
49 | self.offColor = QColor(250, 250, 250)
50 |
51 | self.handleAlpha = False
52 | self.handleColor = QColor(255, 255, 255)
53 |
54 | self.width = 21
55 | self.radius = 29
56 |
57 | elif self.style == "android":
58 | self.onColor = QColor(0, 150, 136)
59 | self.offColor = QColor(255, 255, 255)
60 |
61 | self.handleAlpha = True
62 | self.handleColor = QColor(255, 255, 255)
63 |
64 | self.width = 35
65 | self.radius = 26
66 |
67 | self.setMinimumSize(self.width + (self.radius*2) + (len(self.text)*10), self.radius+2)
68 |
69 | self.anim = AnimationHandler(self, 0, self.width, Animation.easeOutCirc)
70 | if self.on: self.anim.value = 1
71 |
72 | def __repr__(self):
73 | return f""
74 |
75 | def isToggled(self):
76 | return self.on
77 |
78 | def desaturate(self, color):
79 | cc = getattr(self, color)
80 | h = cc.hue()
81 | if h < 0: h = 0
82 | s = cc.saturation()//4
83 | if s > 255: s = 255
84 | c = QColor.fromHsv(h, s, cc.value())
85 | setattr(self, color, c)
86 |
87 | def saturate(self, color):
88 | cc = getattr(self, color)
89 | h = cc.hue()
90 | if h < 0: h = 0
91 | s = cc.saturation()*4
92 | if s > 255: s = 255
93 | c = QColor.fromHsv(h, s, cc.value())
94 | setattr(self, color, c)
95 |
96 | def update(self, *args, **kwargs):
97 | self.anim.update()
98 | super().update(*args, **kwargs)
99 |
100 | def mousePressEvent(self, event):
101 | if self.isEnabled():
102 | if self.on:
103 | self.on = False
104 | self.anim.start(reverse=True)
105 | else:
106 | self.on = True
107 | self.anim.start()
108 | self.update()
109 |
110 | self.toggled.emit()
111 |
112 | def changeEvent(self, event):
113 | if event.type() == QEvent.EnabledChange:
114 | if self.isEnabled():
115 | self.saturate("onColor")
116 | self.saturate("offColor")
117 | self.saturate("handleColor")
118 | self.opacity.setOpacity(1.00)
119 | else:
120 | self.desaturate("onColor")
121 | self.desaturate("offColor")
122 | self.desaturate("handleColor")
123 | self.opacity.setOpacity(0.4)
124 |
125 | self.update()
126 |
127 | else:
128 | super().changeEvent(event)
129 |
130 | def paintEvent(self, event):
131 | pt = QPainter()
132 | pt.begin(self)
133 | pt.setRenderHint(QPainter.Antialiasing)
134 |
135 | if self.style == "win10":
136 |
137 | if self.on:
138 | pen = QPen(self.onColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
139 | pt.setPen(pen)
140 | brush = QBrush(self.onColor)
141 | pt.setBrush(brush)
142 |
143 |
144 | r = self.radius
145 | w = self.width
146 |
147 | pt.drawChord(r, 1, r, r, 90*16, 180*16)
148 | pt.drawChord(r+w, 1, r, r, -90*16, 180*16)
149 | pt.drawRect(r+r//2, 1, w, r)
150 |
151 | if self.handleAlpha: pt.setBrush(pt.background())
152 | else: pt.setBrush(QBrush(self.handleColor))
153 | offset = r*0.4
154 | pt.drawEllipse(r+offset/2+self.anim.current() , 1+offset/2 , r-offset , r-offset)
155 |
156 | else:
157 | pen = QPen(self.offColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
158 | pt.setPen(pen)
159 |
160 | r = self.radius
161 | w = self.width
162 |
163 | pt.drawArc(r, 1, r, r, 90*16, 180*16)
164 | pt.drawArc(r+w, 1, r, r, -90*16, 180*16)
165 | pt.drawLine(r+r//2, 1, r+w+r//2, 1)
166 | pt.drawLine(r+r//2, r+1, r+w+r//2, r+1)
167 |
168 | brush = QBrush(self.offColor)
169 | pt.setBrush(brush)
170 | offset = r*0.4
171 | pt.drawEllipse(r+offset/2+self.anim.current() , offset/2+1 , r-offset , r-offset)
172 |
173 | elif self.style == "ios":
174 |
175 | if self.on:
176 | pen = QPen(self.onColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
177 | pt.setPen(pen)
178 | brush = QBrush(self.onColor)
179 | pt.setBrush(brush)
180 |
181 | r = self.radius
182 | w = self.width
183 |
184 | pt.drawChord(r, 1, r, r, 90*16, 180*16)
185 | pt.drawChord(r+w, 1, r, r, -90*16, 180*16)
186 | pt.drawRect(r+r//2, 1, w, r)
187 |
188 | if self.handleAlpha: pt.setBrush(pt.background())
189 | else: pt.setBrush(QBrush(self.handleColor))
190 | offset = r*0.025
191 | pt.drawEllipse(r+offset/2+self.anim.current() , 1+offset/2 , r-offset , r-offset)
192 |
193 | else:
194 | pen = QPen(self.offColor.darker(135), 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
195 | pt.setPen(pen)
196 | brush = QBrush(self.offColor)
197 | pt.setBrush(brush)
198 |
199 | r = self.radius
200 | w = self.width
201 |
202 | pt.drawChord(r, 1, r, r, 90*16, 180*16)
203 | pt.drawChord(r+w, 1, r, r, -90*16, 180*16)
204 | pt.drawRect(r+r//2, 1, w, r)
205 | pt.setPen(QPen(self.offColor))
206 | pt.drawRect(r+r//2-2, 2, w+4, r-2)
207 |
208 | if self.handleAlpha: pt.setBrush(pt.background())
209 | else: pt.setBrush(QBrush(self.handleColor))
210 | pt.setPen(QPen(self.handleColor.darker(160)))
211 | offset = r*0.025
212 | pt.drawEllipse(r+offset/2+self.anim.current() , 1+offset/2 , r-offset , r-offset)
213 |
214 | elif self.style == "android":
215 |
216 | if self.on:
217 | pen = QPen(self.onColor.lighter(145), 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
218 | pt.setPen(pen)
219 | brush = QBrush(self.onColor.lighter(145))
220 | pt.setBrush(brush)
221 |
222 | r = self.radius
223 | w = self.width
224 |
225 | pt.drawChord(r+r//4, 1+r//4, r//2, r//2, 90*16, 180*16)
226 | pt.drawChord(r+w+r//4, 1+r//4, r//2, r//2, -90*16, 180*16)
227 | pt.drawRect(r+r//2, 1+r//4, w, r//2)
228 |
229 | pt.setBrush(QBrush(self.onColor))
230 | pt.setPen(QPen(self.onColor))
231 | pt.drawEllipse(r+self.anim.current(), 1 , r, r)
232 |
233 | else:
234 | pen = QPen(self.offColor.darker(130), 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
235 | pt.setPen(pen)
236 | brush = QBrush(self.offColor.darker(130))
237 | pt.setBrush(brush)
238 |
239 | r = self.radius
240 | w = self.width
241 |
242 | pt.drawChord(r+r//4, 1+r//4, r//2, r//2, 90*16, 180*16)
243 | pt.drawChord(r+w+r//4, 1+r//4, r//2, r//2, -90*16, 180*16)
244 | pt.drawRect(r+r//2, 1+r//4, w, r//2)
245 |
246 | pt.setBrush(QBrush(self.offColor))
247 | pt.setPen(QPen(self.offColor.darker(140)))
248 | pt.drawEllipse(r+self.anim.current(), 1 , r, r)
249 |
250 | font = pt.font()
251 | pt.setFont(font)
252 | pt.setPen(QPen(Qt.black))
253 |
254 | pt.drawText(w+r*2+10, r//2+r//4, self.text)
255 |
256 | pt.end()
257 |
258 | if not self.anim.done(): self.update()
259 |
--------------------------------------------------------------------------------
/stylingref.md:
--------------------------------------------------------------------------------
1 | # Welcome to pyqt5Custom styling reference!
2 | TODO
3 |
--------------------------------------------------------------------------------