├── .github
└── workflows
│ └── pythonpackage.yml
├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── pygame_vkeyboard
├── DejaVuSans.ttf
├── __init__.py
├── examples
│ ├── __init__.py
│ ├── azerty.py
│ ├── numeric.py
│ ├── resize.py
│ └── textinput.py
├── vkeyboard.py
├── vkeys.py
├── vrenderers.py
└── vtextinput.py
├── screenshot
├── vkeyboard_azerty.png
├── vkeyboard_numeric.gif
└── vkeyboard_textinput.gif
├── setup.py
└── tests
└── test_examples.py
/.github/workflows/pythonpackage.yml:
--------------------------------------------------------------------------------
1 | name: Python package
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Set up Python
13 | uses: actions/setup-python@v1
14 | with:
15 | python-version: '3.x'
16 | - name: Install SDL
17 | run: sudo apt update && sudo apt install -y libsdl-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsmpeg-dev libportmidi-dev libavformat-dev libswscale-dev
18 | - name: Install Python tools
19 | run: python -m pip install --upgrade pip && python -m pip install -U setuptools wheel twine pygame
20 | - name: Build sdist and wheel
21 | run: python setup.py sdist bdist_wheel
22 | - name: Publish to PyPi
23 | env:
24 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
25 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
26 | run: twine check dist/* && twine upload --skip-existing dist/*
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.pyc
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
91 | /example.py
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE.txt
3 | include pygame_vkeyboard/*.ttf
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pygame-vkeyboard
2 |
3 | [](https://github.com/Faylixe/pygame_vkeyboard/actions) [](https://badge.fury.io/py/pygame-vkeyboard) [](https://pypi.org/project/pygame-vkeyboard)
4 |
5 | Visual keyboard for Pygame engine. Aims to be easy to use as highly customizable as well.
6 |
7 |
8 |
9 |
10 |
11 | |
12 |
13 | |
14 |
15 | |
16 |
17 |
18 |
19 |
20 | ## Install
21 |
22 | ```bash
23 | pip install pygame-vkeyboard
24 | ```
25 |
26 | ## Basic usage
27 |
28 | ``VKeyboard`` only require a pygame surface to be displayed on and a text consumer function, as in the following example :
29 |
30 | ```python
31 | from pygame_vkeyboard import *
32 |
33 | # Initializes your window object or surface your want
34 | # vkeyboard to be displayed on top of.
35 | surface = ...
36 |
37 | def consumer(text):
38 | print('Current text : %s' % text)
39 |
40 | # Initializes and activates vkeyboard
41 | layout = VKeyboardLayout(VKeyboardLayout.AZERTY)
42 | keyboard = VKeyboard(surface, consumer, layout)
43 | ```
44 |
45 | The keyboard has the following optional parameters:
46 |
47 | - **show_text**: display a text bar with the current text
48 | - **renderer** : define a custom renderer (see chapter below)
49 | - **special_char_layout**: define a custom layout for special characters
50 | - **joystick_navigation**: enable navigation using a joystick
51 |
52 | ## Event management
53 |
54 | A ``VKeyboard`` object handles the following pygame event :
55 |
56 | - **MOUSEBUTTONDOWN**
57 | - **MOUSEBUTTONUP**
58 | - **FINGERDOWN**
59 | - **FINGERUP**
60 | - **KEYDOWN**
61 | - **KEYUP**
62 | - **JOYHATMOTION**
63 | - **JOYBUTTONDOWN**
64 | - **JOYBUTTONUP**
65 |
66 | In order to process those events, keyboard instance event handling method should be called like in the following example:
67 |
68 | ```python
69 | while True:
70 |
71 | events = pygame.event.get()
72 |
73 | # Update internal variables
74 | keyboard.update(events)
75 |
76 | # Draw the keyboard
77 | keyboard.draw(surface)
78 |
79 | #
80 | # Perform other tasks here
81 | #
82 |
83 | # Update the display
84 | pygame.display.flip()
85 | ```
86 |
87 | It will update key state accordingly as the keyboard buffer as well.
88 | The buffer modification will be notified through the keyboard text consumer function.
89 |
90 | The **global performances can be improved avoiding to flip the entire display**
91 | at each loop by using the ``pygame.display.update()`` function.
92 |
93 | ```python
94 | while True:
95 |
96 | # Draw the keyboard
97 | rects = keyboard.draw(surface)
98 |
99 | # Update only the dirty rectangles of the display
100 | pygame.display.update(rects)
101 | ```
102 |
103 | **Note:** the ``surface`` parameter of the ``draw()`` method is optional, it is used to clear/hide the keyboard when it is necessary and may be mandatory if the surface has changed.
104 |
105 | ## Customize layout
106 |
107 | The keyboard layout is the model that indicates keys are displayed and how they are dispatched
108 | across the keyboard space. It consists in a ``VKeyboardLayout`` object which is built using list of string,
109 | each string corresponding to a keyboard key row. ``VkeyboardLayout`` constructor signature is defined as following :
110 |
111 | ```python
112 | def __init__(self, model, key_size=None, padding=5, height_ratio=None, allow_uppercase=True, allow_special_chars=True, allow_space=True)
113 | ```
114 |
115 | If the **key_size** or **height_ratio** parameters are not provided, they will be computed dynamically regarding of
116 | the target surface the keyboard will be rendered into (**height_ratio** is 50% by default).
117 |
118 | In order to only display a numerical ``Vkeyboard`` for example, you can use a custom layout like this :
119 |
120 | ```python
121 | model = ['123', '456', '789', '0']
122 | layout = VKeyboardLayout(model)
123 | ```
124 |
125 | ## Custom rendering using VKeyboardRenderer
126 |
127 | If you want to customize keyboard rendering you could provide a ``VKeyboardRenderer`` instance at ``VKeyboard``construction.
128 |
129 | ```python
130 | keyboard = VKeyboard(surface, consumer, layout, renderer=VKeyboardRenderer.DARK)
131 | ```
132 |
133 | Here is the list of default renderers provided with ``pygame-vkeyboard``:
134 |
135 | - VKeyboardRenderer.DEFAULT
136 | - VKeyboardRenderer.DARK
137 |
138 | A custom ``VKeyboardRenderer`` can be built using following constructor :
139 |
140 | ```python
141 | renderer = VKeyboardRenderer(
142 | # Key font name/path.
143 | 'arial',
144 | # Text color for key and text box (one per state: released, pressed).
145 | ((0, 0, 0), (255, 255, 255)),
146 | # Text box cursor color.
147 | (0, 0, 0),
148 | # Color to highlight the selected key.
149 | (20, 200, 98),
150 | # Keyboard background color.
151 | (50, 50, 50),
152 | # Key background color (one per state, as for the text color).
153 | ((255, 255, 255), (0, 0, 0)),
154 | # Text input background color.
155 | (220, 220, 220),
156 | # Optional special key text color (one per state, as for the text color).
157 | ((0, 250, 0), (255, 255, 255)),
158 | # Optional special key background color (one per state, as for the text color).
159 | ((255, 255, 255), (0, 0, 0)),
160 | )
161 | ```
162 |
163 | Please note that the default renderer implementation require a unicode font.
164 |
165 | You can also create your own renderer. Just override ``VKeyboardRenderer``class and override any of the following methods :
166 |
167 | - **draw_background(surface)**: Draws the background of the keyboard.
168 | - **draw_text(surface, text)**: Draws the text of the text input box.
169 | - **draw_cursor(surface, cursor)**: Draws the cursor of the text input box.
170 | - **draw_character_key(surface, key, special=False)**: Draws a key based on character value.
171 | - **draw_space_key(surface, key)**: Draws space bar.
172 | - **draw_back_key(surface, key)**: Draws back key.
173 | - **draw_uppercase_key(surface, key)**: Draw uppercase switch key.
174 | - **draw_special_char_key(surface, key)**: Draw special character switch key.
175 |
176 | ## Getting/Setting data
177 |
178 | Several information can be retrieved from the keyboard:
179 |
180 | ```python
181 | keyboard = VKeyboard(...)
182 |
183 | # Get a pygame.Rect object in which the keyboard is included.
184 | keyboard.get_rect()
185 |
186 | # Get the current text.
187 | keyboard.get_text()
188 |
189 | # Set the current text (clear the existing one).
190 | keyboard.set_text("Hello world!")
191 |
192 | # Enable the keyboard, it will be displayed on next keyboard.draw() call.
193 | keyboard.enable()
194 |
195 | # Return True if the keyboard is enabled (thus displayed at screen).
196 | keyboard.is_enabled()
197 |
198 | # Disable and hide the keyboard (keyboard.update() and keyboard.draw() have no effect).
199 | keyboard.disable()
200 | ```
201 |
202 | ## Run examples
203 |
204 | Several examples are provided with the **pygame_vkeyboard** library.
205 | To run the examples, simply execute these commands in a terminal:
206 |
207 | ```bash
208 | python -m pygame_vkeyboard.examples.azerty
209 | python -m pygame_vkeyboard.examples.numeric
210 | python -m pygame_vkeyboard.examples.textinput
211 | python -m pygame_vkeyboard.examples.resize
212 | ```
213 |
214 | ## Contributing
215 |
216 | If you develop you own renderer please share it ! I will keep a collection of rendering class in this repository.
217 | Don't hesitate to report bug, feedback, suggestion into the repository issues section.
218 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/DejaVuSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Faylixe/pygame-vkeyboard/3385a8d5a5290c0a622d830c1383c7f0258becc5/pygame_vkeyboard/DejaVuSans.ttf
--------------------------------------------------------------------------------
/pygame_vkeyboard/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """Visual keyboard highly customizable for pygame."""
5 |
6 | from .vkeyboard import VKeyboardLayout, VKeyboard
7 | from .vrenderers import VKeyboardRenderer
8 |
9 | __version__ = '2.0.9'
10 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/examples/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """ Example package. """
5 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/examples/azerty.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """Simple keyboard usage using AZERTY layout."""
5 |
6 | # pylint: disable=import-error
7 | import pygame
8 | import pygame_vkeyboard as vkboard
9 | # pylint: enable=import-error
10 |
11 |
12 | def on_key_event(text):
13 | """ Print the current text. """
14 | print('Current text:', text)
15 |
16 |
17 | def main(test=False):
18 | """ Main program.
19 |
20 | :param test: Indicate function is being tested
21 | :type test: bool
22 | :return: None
23 | """
24 |
25 | # Init pygame
26 | pygame.init()
27 | screen = pygame.display.set_mode((500, 400))
28 |
29 | # Create keyboard
30 | layout = vkboard.VKeyboardLayout(vkboard.VKeyboardLayout.AZERTY, height_ratio=1)
31 | keyboard = vkboard.VKeyboard(screen, on_key_event, layout)
32 |
33 | clock = pygame.time.Clock()
34 |
35 | # Main loop
36 | while True:
37 | clock.tick(100) # Ensure not exceed 100 FPS
38 |
39 | for event in pygame.event.get():
40 | keyboard.on_event(event)
41 | if event.type == pygame.QUIT:
42 | print("Average FPS: ", clock.get_fps())
43 | exit()
44 |
45 | # Flip the entire surface
46 | pygame.display.flip()
47 |
48 | # At first loop returns
49 | if test:
50 | break
51 |
52 |
53 | if __name__ == '__main__':
54 | main()
55 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/examples/numeric.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """Simple keyboard usage using custom numeric layout."""
5 |
6 | # pylint: disable=import-error
7 | import pygame
8 | import pygame_vkeyboard as vkboard
9 | # pylint: enable=import-error
10 |
11 |
12 | def on_key_event(text):
13 | """ Print the current text. """
14 | print('Current text:', text)
15 |
16 |
17 | def main(test=False):
18 | """ Main program.
19 |
20 | :param test: Indicate function is being tested
21 | :type test: bool
22 | :return: None
23 | """
24 |
25 | # Init pygame
26 | pygame.init()
27 | screen = pygame.display.set_mode((400, 400))
28 |
29 | # Create keyboard
30 | model = ['123', '456', '789', '*0#']
31 | layout = vkboard.VKeyboardLayout(model,
32 | key_size=30,
33 | padding = 15,
34 | height_ratio=0.8,
35 | allow_uppercase=False,
36 | allow_special_chars=False,
37 | allow_space=False)
38 | keyboard = vkboard.VKeyboard(screen, on_key_event, layout,
39 | joystick_navigation=True)
40 |
41 | # Main loop
42 | while True:
43 |
44 | events = pygame.event.get()
45 |
46 | for event in events:
47 | if event.type == pygame.QUIT:
48 | exit()
49 |
50 | keyboard.update(events)
51 | rects = keyboard.draw(screen)
52 |
53 | # Flip only the updated area
54 | pygame.display.update(rects)
55 |
56 | # At first loop returns
57 | if test:
58 | break
59 |
60 |
61 | if __name__ == '__main__':
62 | main()
63 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/examples/resize.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """Simple keyboard usage using QWERTY layout and input text."""
5 |
6 | # pylint: disable=import-error
7 | import pygame
8 | import pygame_vkeyboard as vkboard
9 | # pylint: enable=import-error
10 |
11 |
12 | def on_key_event(text):
13 | """ Print the current text. """
14 | print('Current text:', text)
15 |
16 |
17 | def main(test=False):
18 | """ Main program.
19 |
20 | :param test: Indicate function is being tested
21 | :type test: bool
22 | :return: None
23 | """
24 |
25 | # Init pygame
26 | pygame.init()
27 | screen = pygame.display.set_mode((500, 300), pygame.RESIZABLE)
28 | screen.fill((100, 100, 100))
29 |
30 | # Create keyboard
31 | layout = vkboard.VKeyboardLayout(vkboard.VKeyboardLayout.QWERTY,
32 | allow_special_chars=False,
33 | allow_space=False)
34 | keyboard = vkboard.VKeyboard(screen,
35 | on_key_event,
36 | layout,
37 | renderer=vkboard.VKeyboardRenderer.DARK,
38 | show_text=True,
39 | joystick_navigation=True)
40 |
41 | clock = pygame.time.Clock()
42 |
43 | # Main loop
44 | while True:
45 | clock.tick(100) # Ensure not exceed 100 FPS
46 |
47 | events = pygame.event.get()
48 |
49 | for event in events:
50 | if event.type == pygame.QUIT:
51 | print("Average FPS: ", clock.get_fps())
52 | exit()
53 | if event.type == pygame.VIDEORESIZE:
54 | screen.fill((100, 100, 100))
55 |
56 | keyboard.update(events)
57 | rects = keyboard.draw(screen)
58 |
59 | # Flip only the updated area
60 | pygame.display.update(rects)
61 |
62 | # At first loop returns
63 | if test:
64 | break
65 |
66 |
67 | if __name__ == '__main__':
68 | main()
69 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/examples/textinput.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """Simple keyboard usage using QWERTY layout and input text."""
5 |
6 | # pylint: disable=import-error
7 | import pygame
8 | import pygame_vkeyboard as vkboard
9 | # pylint: enable=import-error
10 |
11 |
12 | def on_key_event(text):
13 | """ Print the current text. """
14 | print('Current text:', text)
15 |
16 |
17 | def main(test=False):
18 | """ Main program.
19 |
20 | :param test: Indicate function is being tested
21 | :type test: bool
22 | :return: None
23 | """
24 |
25 | # Init pygame
26 | pygame.init()
27 | screen = pygame.display.set_mode((300, 400))
28 | screen.fill((20, 100, 100))
29 |
30 | # Create keyboard
31 | layout = vkboard.VKeyboardLayout(vkboard.VKeyboardLayout.QWERTY)
32 | keyboard = vkboard.VKeyboard(screen,
33 | on_key_event,
34 | layout,
35 | renderer=vkboard.VKeyboardRenderer.DARK,
36 | show_text=True,
37 | joystick_navigation=True)
38 |
39 | clock = pygame.time.Clock()
40 |
41 | # Main loop
42 | while True:
43 | clock.tick(100) # Ensure not exceed 100 FPS
44 |
45 | events = pygame.event.get()
46 |
47 | for event in events:
48 | if event.type == pygame.QUIT:
49 | print("Average FPS: ", clock.get_fps())
50 | exit()
51 |
52 | keyboard.update(events)
53 | rects = keyboard.draw(screen)
54 |
55 | # Flip only the updated area
56 | pygame.display.update(rects)
57 |
58 | # At first loop returns
59 | if test:
60 | break
61 |
62 |
63 | if __name__ == '__main__':
64 | main()
65 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/vkeyboard.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """
5 | Visual keyboard for Pygame engine. Aims to be easy to use
6 | as highly customizable as well.
7 |
8 | ``VKeyboard`` only require a pygame surface to be displayed
9 | on and a text consumer function, as in the following example :
10 |
11 | ```python
12 | from pygame_vkeyboard import VKeyboard, VKeyboardLayout
13 |
14 | # Initializes your window object or surface your want
15 | # vkeyboard to be displayed on top of.
16 | surface = ...
17 |
18 | def consume(text):
19 | print(repr('Current text : %s' % text))
20 |
21 | # Initializes and activates vkeyboard
22 | layout = VKeyboardLayout(VKeyboardLayout.AZERTY)
23 | keyboard = VKeyboard(window, consumer, layout)
24 | keyboard.enable()
25 | ```
26 | """
27 |
28 | import logging
29 | import pygame # pylint: disable=import-error
30 |
31 | from . import vkeys
32 | from .vrenderers import VKeyboardRenderer
33 | from .vtextinput import VTextInput, VBackground
34 |
35 |
36 | LOGGER = logging.getLogger(__name__)
37 |
38 | # Joystick controls
39 | JOYHAT_UP = (0, 1)
40 | JOYHAT_LEFT = (-1, 0)
41 | JOYHAT_RIGHT = (1, 0)
42 | JOYHAT_DOWN = (0, -1)
43 |
44 |
45 | def synchronize_layouts(surface_size, *layouts):
46 | """Synchronizes given layouts by normalizing height by using
47 | max height of given layouts to avoid transistion dirty effects.
48 |
49 | Parameters
50 | ----------
51 | surface_size:
52 | Target surface size on which layout will be displayed.
53 | layouts:
54 | All layouts to synchronize
55 | """
56 | for layout in layouts:
57 | layout.configure_bound(surface_size)
58 |
59 | layout_ref = min(layouts, key=lambda l: l.key_size)
60 |
61 | for layout in layouts:
62 | if layout.key_size != layout_ref.key_size:
63 | logging.warning(
64 | 'Normalizing layout%s key size to %spx (from layout%s)',
65 | layouts.index(layout),
66 | layout_ref.key_size,
67 | layouts.index(layout_ref))
68 | layout.key_size = layout_ref.key_size
69 | if layout.size != layout_ref.size:
70 | logging.warning(
71 | 'Normalizing layout%s size to %s*%spx (from layout%s)',
72 | layouts.index(layout),
73 | layout_ref.size[0], layout_ref.size[1],
74 | layouts.index(layout_ref))
75 |
76 | # Compute all internal values of the layout
77 | layout.set_size(layout_ref.size, surface_size)
78 |
79 |
80 | class VKeyRow(object):
81 | """A VKeyRow defines a keyboard row which is composed of a list of
82 | VKey.
83 |
84 | This class aims to be created internally after parsing a keyboard
85 | layout model. It is used to optimize collision detection, by first
86 | checking row collision, then internal row key detection.
87 | """
88 |
89 | def __init__(self):
90 | """Default row constructor. """
91 | self.keys = []
92 | self.height = 0
93 | self.position = (0, 0)
94 | self.space = None
95 |
96 | def add_key(self, key, first=False):
97 | """Adds the given key to this row.
98 |
99 | Parameters
100 | ----------
101 | key:
102 | Key to be added to this row.
103 | first:
104 | Flag that indicates if key is added at the beginning or at the end.
105 | """
106 | if first:
107 | self.keys = [key] + self.keys
108 | else:
109 | self.keys.append(key)
110 | if isinstance(key, vkeys.VSpaceKey):
111 | self.space = key
112 |
113 | def set_size(self, position, size, padding):
114 | """Row size setter. The size correspond to the row height, since the
115 | row width is constraint to the surface width the associated keyboard
116 | belongs. Once size is settled, the size for each child keys is
117 | associated.
118 |
119 | Parameters
120 | ----------
121 | position:
122 | Position of this row.
123 | size:
124 | Size of the row (height)
125 | padding:
126 | Padding between key.
127 | """
128 | self.height = size
129 | self.position = position
130 | x = position[0]
131 | for key in self.keys:
132 | key.set_size(size, size)
133 | key.set_position(x, position[1])
134 | x += padding + key.rect.width
135 |
136 | def __len__(self):
137 | """len() operator overload.
138 |
139 | Returns
140 | -------
141 | len:
142 | Number of keys thi row contains.
143 | """
144 | return len(self.keys)
145 |
146 |
147 | class VKeyboardLayout(object):
148 | """Keyboard layout class.
149 |
150 | A keyboard layout is built using layout model which consists in an
151 | list of supported character. Such list item as simple string containing
152 | characters assigned to a row.
153 |
154 | An erasing key is inserted automatically to the first row.
155 |
156 | If `allow_uppercase` flag is `True`, then an upper case key will be
157 | inserted at the beginning of the second row.
158 |
159 | If `allow_special_chars` flag is `True`, then an special
160 | characters / number key will be inserted at the beginning of the third row.
161 | Pressing this key will switch the associated keyboard current layout.
162 | """
163 |
164 | # AZERTY Layout.
165 | AZERTY = ['1234567890', 'azertyuiop', 'qsdfghjklm', 'wxcvbn']
166 |
167 | # QWERTY Layout.
168 | QWERTY = ['1234567890', 'qwertyuiop', 'asdfghjkl', 'zxcvbnm']
169 |
170 | # Number only layout.
171 | NUMBER = ['123', '456', '789', '0']
172 |
173 | # TODO : Insert special characters layout which include number.
174 | SPECIAL = [u'&é"\'(§è!çà)', u'°_-^$¨*ù`%£', u',;:=?.@+<>#', u'€[]{}/\\|']
175 |
176 | def __init__(self,
177 | model,
178 | key_size=None,
179 | padding=5,
180 | height_ratio=None,
181 | allow_uppercase=True,
182 | allow_special_chars=True,
183 | allow_space=True):
184 | """Default constructor. Initializes layout rows.
185 |
186 | Parameters
187 | ----------
188 | model:
189 | Layout model to use.
190 | key_size:
191 | Size of the key, dynamically computed if not specified.
192 | padding:
193 | Padding between key (work horizontally as vertically).
194 | height_ratio:
195 | Ratio (0.2 to 1) of the surface height to recover, dynamically
196 | computed if not specified.
197 | allow_uppercase:
198 | Boolean flag that indicates usage of upper case switching key.
199 | allow_special_chars:
200 | Boolean flag that indicates usage of special char switching key.
201 | allow_space:
202 | Boolean flag that indicates usage of space bar.
203 |
204 | Raises
205 | ------
206 | ValueError
207 | If the layout model is empty.
208 | """
209 | self.position = None
210 | self.size = None
211 | self.rows = []
212 | self.sprites = pygame.sprite.LayeredDirty()
213 | self.padding = padding
214 | self.height_ratio = height_ratio
215 | self.selection = None
216 | self.allow_space = allow_space
217 | self.allow_uppercase = allow_uppercase
218 | self.allow_special_chars = allow_special_chars
219 | for model_row in model:
220 | row = VKeyRow()
221 | for value in model_row:
222 | key = vkeys.VKey(value)
223 | row.add_key(key)
224 | self.sprites.add(key, layer=1)
225 | self.rows.append(row)
226 | self.max_length = len(max(self.rows, key=len))
227 | if self.max_length == 0:
228 | raise ValueError('Empty layout model provided')
229 | if height_ratio is not None and (height_ratio < 0.2 or height_ratio > 1):
230 | raise ValueError('Surface height ratio shall be from 0.2 to 1')
231 |
232 | self._key_size = key_size
233 | self._key_size_computed = None
234 |
235 | @property
236 | def key_size(self):
237 | return self._key_size or self._key_size_computed
238 |
239 | @key_size.setter
240 | def key_size(self, key_size):
241 | self._key_size = key_size
242 |
243 | def hide(self):
244 | """Hide all keys."""
245 | for sprite in self.sprites:
246 | sprite.visible = 0
247 |
248 | def show(self):
249 | """Show all keys."""
250 | for sprite in self.sprites:
251 | sprite.visible = 1
252 |
253 | def configure_renderer(self, renderer):
254 | """Configure the keys with the given renderer.
255 |
256 | Parameters
257 | ----------
258 | renderer:
259 | Renderer instance this layout uses.
260 | """
261 | for key in self.sprites.get_sprites_from_layer(1):
262 | key.renderer = renderer
263 |
264 | def configure_special_keys(self, keyboard):
265 | """Configures specials key if needed.
266 |
267 | Parameters
268 | ----------
269 | keyboard:
270 | Keyboard instance this layout belong.
271 | """
272 | special_row = VKeyRow()
273 | max_length = self.max_length
274 | i = len(self.rows) - 1
275 | current_row = self.rows[i]
276 |
277 | # Create special keys list
278 | special_keys = [vkeys.VBackKey()]
279 | if self.allow_uppercase:
280 | special_keys.append(
281 | vkeys.VUppercaseKey(keyboard.on_uppercase, keyboard))
282 | if self.allow_special_chars:
283 | special_keys.append(
284 | vkeys.VSpecialCharKey(keyboard.on_special_char, keyboard))
285 | self.sprites.add(*special_keys, layer=1)
286 |
287 | # Dispatch special keys in the layout
288 | while special_keys:
289 | first = False
290 | while special_keys and len(current_row) < max_length:
291 | current_row.add_key(special_keys.pop(0), first=first)
292 | first = not first
293 | if i > 0:
294 | i -= 1
295 | current_row = self.rows[i]
296 | else:
297 | break
298 | if self.allow_space:
299 | space_length = len(current_row) - len(special_keys)
300 | special_row.add_key(vkeys.VSpaceKey(space_length))
301 | self.sprites.add(special_row.space, layer=1)
302 | first = True
303 |
304 | # Adding left to the special bar.
305 | while special_keys:
306 | special_row.add_key(special_keys.pop(0), first=first)
307 | first = not first
308 |
309 | if special_row:
310 | self.rows.append(special_row)
311 |
312 | def configure_bound(self, surface_size):
313 | """Compute keyboard bound regarding of this layout. If `key_size` is
314 | `None`, then it will compute it regarding of the given surface_size.
315 |
316 | Parameters
317 | ----------
318 | surface_size:
319 | Size of the surface this layout will be rendered on.
320 | """
321 | nb_rows = len(self.rows)
322 | if self._key_size is None:
323 | self._key_size_computed = int(
324 | (surface_size[0] - (self.padding * (self.max_length + 1)))
325 | / self.max_length)
326 |
327 | height = self.key_size * nb_rows + self.padding * (nb_rows + 1)
328 | if height > surface_size[1] * (self.height_ratio or 0.5):
329 | self._key_size_computed = int((surface_size[1] * (self.height_ratio or 0.5)
330 | - (self.padding * (nb_rows + 1))) / nb_rows)
331 | height = self.key_size * nb_rows + self.padding * (nb_rows + 1)
332 | if self._key_size:
333 | self._key_size = self._key_size_computed
334 | LOGGER.warning('Computed layout height outbound target surface,'
335 | ' reducing key_size to %spx', self.key_size)
336 | elif self.height_ratio is not None:
337 | height = surface_size[1] * self.height_ratio
338 | self.set_size((surface_size[0], int(height)), surface_size)
339 |
340 | def set_size(self, size, surface_size):
341 | """Sets the size of this layout, and updates
342 | position, and rows accordingly.
343 |
344 | Parameters
345 | ----------
346 | size:
347 | Size of this layout.
348 | surface_size:
349 | Target surface size on which layout will be displayed.
350 | """
351 | self.size = size
352 | self.position = (0, surface_size[1] - self.size[1])
353 |
354 | y = self.position[1] + (self.size[1] - len(self.rows) * self.key_size
355 | - (len(self.rows) + 1) * self.padding) // 2
356 | y += self.padding
357 | for row in self.rows:
358 | nb_keys = len(row)
359 | width = (nb_keys * self.key_size) + ((nb_keys + 1) * self.padding)
360 | x = (surface_size[0] - width) // 2 + self.padding
361 | if row.space:
362 | x -= ((row.space.length - 1) * self.key_size) / 2
363 | row.set_size((x, y), self.key_size, self.padding)
364 | y += self.padding + self.key_size
365 |
366 | def set_uppercase(self, uppercase):
367 | """Sets layout uppercase state.
368 |
369 | Parameters
370 | ----------
371 | uppercase:
372 | True if uppercase, False otherwise.
373 | """
374 | for key in self.sprites.get_sprites_from_layer(1):
375 | key.set_uppercase(uppercase)
376 |
377 | def get_key(self, value):
378 | """Retrieve if any key with the given value
379 |
380 | Parameters
381 | ----------
382 | value:
383 | Value to find among keys.
384 |
385 | Returns
386 | -------
387 | key:
388 | The located key if any with the given value, None otherwise.
389 | """
390 | for key in self.sprites.get_sprites_from_layer(1):
391 | if key.value == value:
392 | return key
393 | return None
394 |
395 | def get_key_at(self, position):
396 | """Retrieve if any key is located at the given position
397 |
398 | Parameters
399 | ----------
400 | position:
401 | Position to check key at.
402 |
403 | Returns
404 | -------
405 | key:
406 | The located key if any at the given position, None otherwise.
407 | """
408 | for sprite in self.sprites.get_sprites_at(position):
409 | if isinstance(sprite, vkeys.VKey):
410 | return sprite
411 | return None
412 |
413 | def get_key_closest(self, key, loop_row=True, loop_col=True):
414 | """Retrieve the keys closest to the given one. It returns
415 | a dictionary with closest keys and their position relative
416 | to the given key (diff row, diff column).
417 |
418 | Parameters
419 | ----------
420 | key:
421 | The key to locate.
422 | loop_row:
423 | Loop to the start/end of the row when right/left overflow.
424 | loop_col:
425 | Loop to the start/end of the column when bottom/top overflow.
426 |
427 | Returns
428 | -------
429 | keys_dict:
430 | The located keys on the left, top, right, bottom.
431 | """
432 | center = (0, 0)
433 | left = (0, -1)
434 | top = (-1, 0)
435 | right = (0, 1)
436 | bottom = (1, 0)
437 | keys_dict = {center: key,
438 | left: None, top: None, right: None, bottom: None}
439 |
440 | for row_index, r in enumerate(self.rows):
441 | for key_index, k in enumerate(r.keys):
442 | if key == k:
443 | if key_index - 1 >= 0 or loop_row:
444 | keys_dict[left] = r.keys[(key_index - 1) % len(r.keys)]
445 | if key_index + 1 < len(r.keys) or loop_row:
446 | keys_dict[right] = r.keys[(key_index + 1) % len(r.keys)]
447 |
448 | if row_index - 1 >= 0 or loop_col:
449 | prev_row = self.rows[(row_index - 1) % len(self.rows)]
450 | keys_dict[top] = prev_row\
451 | .keys[min(key_index, len(prev_row) - 1)]
452 | if row_index + 1 < len(self.rows) or loop_col:
453 | next_row = self.rows[(row_index + 1) % len(self.rows)]
454 | keys_dict[bottom] = next_row\
455 | .keys[min(key_index, len(next_row) - 1)]
456 | return keys_dict
457 |
458 |
459 | class VKeyboard(object):
460 | """
461 | Virtual Keyboard class.
462 |
463 | A virtual keyboard consists in a VKeyboardLayout that acts as
464 | the keyboard model and a VKeyboardRenderer which is in charge
465 | of drawing keyboard component to screen.
466 | """
467 |
468 | def __init__(self,
469 | surface,
470 | text_consumer,
471 | main_layout,
472 | show_text=False,
473 | joystick_navigation=False,
474 | renderer=VKeyboardRenderer.DEFAULT,
475 | special_char_layout=None):
476 | """ Default constructor.
477 |
478 | Parameters
479 | ----------
480 | surface:
481 | Surface this keyboard will be displayed at.
482 | text_consumer:
483 | Consumer that process text for each update.
484 | main_layout:
485 | First displayed layout of this keyboard.
486 | show_text:
487 | Display the current text in a text box.
488 | joystick_navigation:
489 | Handle joystick events and arrow keys.
490 | renderer:
491 | Keyboard renderer instance, using VKeyboardRenderer.DEFAULT
492 | if not specified.
493 | special_char_layout:
494 | Alternative layout to use, using VKeyboardLayout.SPECIAL
495 | if not specified.
496 | """
497 | self.surface = surface
498 | self.text_consumer = text_consumer
499 | self.renderer = renderer
500 | self.state = 1 # Enabled by default
501 | self.last_pressed = None
502 | self.uppercase = False
503 | self.special_char = False
504 | self.joystick_navigation = joystick_navigation
505 |
506 | # Setup background as a DirtySprite
507 | self.eraser = None
508 | self.background = VBackground(self.surface.get_rect().size,
509 | self.renderer)
510 |
511 | # Setup the layouts
512 | self.layout = main_layout
513 | self.layouts = [main_layout]
514 | if self.layout.allow_special_chars:
515 | if not special_char_layout:
516 | special_char_layout = VKeyboardLayout(
517 | VKeyboardLayout.SPECIAL,
518 | key_size=self.layout.key_size,
519 | padding=self.layout.padding,
520 | height_ratio=self.layout.height_ratio,
521 | allow_uppercase=self.layout.allow_uppercase,
522 | allow_special_chars=self.layout.allow_special_chars,
523 | allow_space=self.layout.allow_space)
524 | self.layouts.append(special_char_layout)
525 |
526 | for layout in self.layouts:
527 | layout.configure_special_keys(self)
528 | layout.configure_renderer(self.renderer)
529 | layout.sprites.add(self.background, layer=0)
530 |
531 | # Setup the text input box
532 | self.show_text = show_text
533 | self.input = VTextInput((0, 0), (10, 10), renderer=self.renderer)
534 |
535 | self.set_size(*surface.get_size())
536 |
537 | if self.show_text:
538 | self.input.enable()
539 |
540 | # Setup de joystick
541 | if self.joystick_navigation:
542 | if not pygame.joystick.get_init():
543 | pygame.joystick.init()
544 | for i in range(pygame.joystick.get_count()):
545 | pygame.joystick.Joystick(i).init()
546 |
547 | def set_layout(self, layout):
548 | """Sets the layout this keyboard work with.
549 | Keyboard is invalidate by this action and redraw itself.
550 |
551 | Parameters
552 | ----------
553 | layout:
554 | Layout to set.
555 | """
556 | self.layout.hide()
557 | self.layout = layout
558 | self.layout.show()
559 |
560 | def set_eraser(self, surface):
561 | """Setup the surface used to hide/clear the keyboard.
562 | """
563 | self.eraser = surface.copy()
564 | for layout in self.layouts:
565 | layout.sprites.clear(surface, self.eraser)
566 |
567 | def set_size(self, width, height):
568 | """Resize the keyboard according to the surface size and the parameters
569 | of the layout(s).
570 |
571 | Parameters
572 | ----------
573 | width:
574 | Keyboard width.
575 | height:
576 | Keyboard height.
577 | """
578 | synchronize_layouts((width, height), *self.layouts)
579 | self.background.set_rect(*self.layout.position + self.layout.size)
580 |
581 | for layout in self.layouts:
582 | if layout.sprites.get_clip() != self.background.rect:
583 | # Changing the clipping area will force update of all
584 | # sprites without using "dirty mechanism"
585 | layout.sprites.set_clip(self.background.rect)
586 |
587 | self.input.set_line_rect(self.layout.position[0],
588 | self.layout.position[1] - self.layout.key_size,
589 | self.layout.size[0],
590 | self.layout.key_size)
591 |
592 | def set_text(self, text):
593 | """Set the current text in the internal buffer.
594 |
595 | Parameters
596 | ----------
597 | text:
598 | Text to be set.
599 | """
600 | self.input.set_text(text)
601 |
602 | def get_text(self):
603 | """Return the current text of the internal buffer."""
604 | return self.input.text
605 |
606 | def enable(self):
607 | """Set this keyboard as active."""
608 | self.state = 1
609 | self.layout.show()
610 | if self.show_text:
611 | self.input.enable()
612 |
613 | def is_enabled(self):
614 | """Return True if this keyboard is active."""
615 | return self.state == 1
616 |
617 | def disable(self):
618 | """Set this keyboard as non active."""
619 | self.state = 0
620 | self.layout.hide()
621 | self.input.disable()
622 |
623 | def get_rect(self):
624 | """Return keyboard rect."""
625 | rect = self.background.rect
626 | if self.show_text:
627 | rect = rect.union(self.input.get_rect())
628 | return rect
629 |
630 | def draw(self, surface=None, force=False):
631 | """Draw the virtual keyboard.
632 |
633 | This method is optimized to be called at each loop of the
634 | main application. It uses DirtySprite to update only parts
635 | of the screen that need to be refreshed.
636 |
637 | The first call to this method will setup the "eraser" surface that
638 | will be used to redraw dirty parts of the screen.
639 |
640 | The `force` parameter shall be used if the surface has been redrawn:
641 | it reset the eraser and redraw all sprites.
642 |
643 | Parameters
644 | ----------
645 | surface:
646 | Surface this keyboard will be displayed at.
647 | force:
648 | Force the drawing of the entire surface (time consuming).
649 |
650 | Returns
651 | -------
652 | rects:
653 | List of updated area.
654 | """
655 | surface = surface or self.surface
656 |
657 | # Check if surface has been resized
658 | if self.eraser and surface.get_rect() != self.eraser.get_rect():
659 | force = True # To force creating new eraser
660 | self.set_size(*surface.get_size())
661 |
662 | # Setup eraser
663 | if not self.eraser or force:
664 | self.set_eraser(surface)
665 |
666 | rects = self.layout.sprites.draw(surface)
667 | rects += self.input.draw(surface, force)
668 |
669 | if force:
670 | self.layout.sprites.repaint_rect(self.background.rect)
671 | return rects
672 |
673 | def update(self, events):
674 | """Pygame events processing callback method.
675 |
676 | Parameters
677 | ----------
678 | events:
679 | List of events to process.
680 | """
681 | if self.state == 1:
682 | self.layout.sprites.update(events)
683 | self.input.update(events)
684 |
685 | for event in events:
686 | if event.type == pygame.MOUSEBUTTONDOWN\
687 | and event.button in (1, 2, 3):
688 | # Don't consider the mouse wheel (button 4 & 5)
689 | key = self.layout.get_key_at(event.pos)
690 | if key:
691 | self.on_key_down(key)
692 | self.on_select(0, 0, key)
693 | elif self.input.get_rect().collidepoint(event.pos)\
694 | and self.layout.selection:
695 | self.layout.selection.set_selected(0)
696 | self.layout.selection = None
697 | self.input.set_selected(1)
698 | elif event.type == pygame.FINGERDOWN:
699 | display_size = pygame.display.get_surface().get_size()
700 | finger_pos = (event.x * display_size[0], event.y * display_size[1])
701 | key = self.layout.get_key_at(finger_pos)
702 | if key:
703 | self.on_key_down(key)
704 | self.on_select(0, 0, key)
705 | elif self.input.get_rect().collidepoint(finger_pos)\
706 | and self.layout.selection:
707 | self.layout.selection.set_selected(0)
708 | self.layout.selection = None
709 | self.input.set_selected(1)
710 | elif event.type == pygame.KEYDOWN:
711 | key = self.layout.get_key(event.unicode or event.key)
712 | if key:
713 | self.on_key_down(key)
714 | self.on_select(0, 0, key)
715 | elif event.key == pygame.K_LEFT and not self.input.selected:
716 | self.on_select(0, -1)
717 | elif event.key == pygame.K_UP:
718 | self.on_select(-1, 0)
719 | elif event.key == pygame.K_RIGHT and\
720 | not self.input.selected:
721 | self.on_select(0, 1)
722 | elif event.key == pygame.K_DOWN:
723 | self.on_select(1, 0)
724 | elif event.key == pygame.K_RETURN and self.layout.selection:
725 | self.on_key_down(self.layout.selection)
726 | elif event.type == pygame.JOYHATMOTION:
727 | if event.value == JOYHAT_LEFT and\
728 | not self.input.selected:
729 | self.on_select(0, -1)
730 | elif event.value == JOYHAT_UP:
731 | self.on_select(-1, 0)
732 | elif event.value == JOYHAT_RIGHT and\
733 | not self.input.selected:
734 | self.on_select(0, 1)
735 | elif event.value == JOYHAT_DOWN:
736 | self.on_select(1, 0)
737 | elif event.type == pygame.JOYBUTTONDOWN\
738 | and event.button == 0 and self.layout.selection:
739 | # Select button pressed
740 | self.on_key_down(self.layout.selection)
741 |
742 | def on_select(self, increment_row, increment_col, key=None):
743 | """"Change the currently selected key.
744 |
745 | Parameters
746 | ----------
747 | increment_row:
748 | Of how many rows to move (-1 to go up, 1 to go down).
749 | increment_col:
750 | Of how many columns to move (-1 to go left, 1 to go right).
751 | key:
752 | The key to start from, by default the currently selected one.
753 | """
754 | if self.joystick_navigation:
755 | if not self.layout.selection:
756 | if key:
757 | self.layout.selection = key
758 | elif increment_row > 0:
759 | self.layout.selection = self.layout.rows[0].keys[0]
760 | else:
761 | self.layout.selection = self.layout.rows[-1].keys[0]
762 | self.layout.selection.set_selected(1)
763 | self.input.set_selected(0)
764 | return
765 |
766 | closest = self.layout.get_key_closest(
767 | key or self.layout.selection,
768 | loop_col=not self.input.is_enabled())
769 |
770 | self.layout.selection.set_selected(0)
771 | self.layout.selection = None
772 | if closest[(increment_row, increment_col)]:
773 | self.layout.selection = closest[(increment_row, increment_col)]
774 | self.layout.selection.set_selected(1)
775 | else:
776 | self.input.set_selected(1)
777 |
778 | def on_event(self, event):
779 | """Deprecated method, only for backward compatibility."""
780 | self.update((event,))
781 | self.draw()
782 |
783 | def on_uppercase(self):
784 | """Uppercase key press handler."""
785 | self.uppercase = not self.uppercase
786 | for layout in self.layouts:
787 | layout.set_uppercase(self.uppercase)
788 |
789 | def on_special_char(self):
790 | """Special char key press handler."""
791 | if len(self.layouts) > 1:
792 | self.special_char = not self.special_char
793 | if self.special_char:
794 | self.set_layout(self.layouts[1])
795 | else:
796 | self.set_layout(self.layouts[0])
797 |
798 | def on_key_down(self, key):
799 | """Process key down event by pressing the given key.
800 |
801 | Parameters
802 | ----------
803 | key:
804 | Key that receives the key down event.
805 | """
806 | if isinstance(key, vkeys.VBackKey):
807 | self.input.delete_at_cursor()
808 | else:
809 | text = key.update_buffer('')
810 | if text:
811 | self.input.add_at_cursor(text)
812 |
813 | if not isinstance(key, vkeys.VActionKey):
814 | self.text_consumer(self.input.text)
815 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/vkeys.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """
5 | Module for keys definitions. It contains the following classes:
6 |
7 | - `VKey` : base class for all keys
8 | - `VSpaceKey` : *space* key definition
9 | - `VBackKey` : *delete* key definition
10 |
11 | - `VActionKey` : base class for key without effect on text
12 | - `VUppercaseKey` : *shift* key definition
13 | - `VSpecialCharKey`: change the keyboard layout
14 | """
15 |
16 | import pygame # pylint: disable=import-error
17 |
18 |
19 | class VKey(pygame.sprite.DirtySprite):
20 | """
21 | Simple key holder class.
22 |
23 | Holds key information (its value), as its state, size / position.
24 | State attributes with their default values:
25 |
26 | pressed = 0
27 | If set to 0, the key is released.
28 | If set to 1, the key is pressed.
29 |
30 | selected = 0
31 | If set to 0, the key is selectable but not selected.
32 | If set to 1, the key is selected.
33 | """
34 |
35 | def __init__(self, value, symbol=None):
36 | """Default key constructor.
37 |
38 | Parameters
39 | ----------
40 | value:
41 | Value of this key.
42 | symbol:
43 | Visual representation of the key displayed to the screen
44 | (equal to the value if not given).
45 | """
46 | super(VKey, self).__init__()
47 | self.pressed = 0
48 | self.selected = 0
49 | self.value = value
50 | self.symbol = symbol
51 | self.rect = pygame.Rect((0, 0), (10, 10))
52 | self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32)
53 | self.renderer = None
54 | self.pressed_key = None
55 |
56 | def __str__(self):
57 | """Key representation when using str() or print()"""
58 | if self.symbol:
59 | return self.symbol
60 | return self.value
61 |
62 | def set_position(self, x, y):
63 | """Set the key position.
64 |
65 | Parameters
66 | ----------
67 | x:
68 | Position x.
69 | y:
70 | Position y.
71 | """
72 | if self.rect.topleft != (x, y):
73 | self.rect.topleft = (x, y)
74 | self.dirty = 1
75 |
76 | def set_size(self, width, height):
77 | """Set the key size.
78 |
79 | Parameters
80 | ----------
81 | width:
82 | Background width.
83 | height:
84 | Background height.
85 | """
86 | if self.rect.size != (width, height):
87 | self.rect.size = (width, height)
88 | self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32)
89 | self.renderer.draw_key(self.image, self)
90 | self.dirty = 1
91 |
92 | def set_uppercase(self, uppercase):
93 | """Set key uppercase state and redraws it.
94 |
95 | Parameters
96 | ----------
97 | uppercase:
98 | True if uppercase, False otherwise.
99 | """
100 | if uppercase:
101 | new_value = self.value.upper()
102 | else:
103 | new_value = self.value.lower()
104 | if new_value != self.value:
105 | self.value = new_value
106 | self.renderer.draw_key(self.image, self)
107 | self.dirty = 1
108 |
109 | def set_pressed(self, state):
110 | """Set the key pressed state (1 for pressed 0 for released)
111 | and redraws it.
112 |
113 | Parameters
114 | ----------
115 | state:
116 | New key state.
117 | """
118 | if self.pressed != int(state):
119 | self.pressed = int(state)
120 | self.renderer.draw_key(self.image, self)
121 | self.dirty = 1
122 |
123 | def set_selected(self, state):
124 | """Set the key selection state (1 for selected else 0)
125 | and redraws it.
126 |
127 | Parameters
128 | ----------
129 | state:
130 | New key state.
131 | """
132 | if self.selected != int(state):
133 | self.selected = int(state)
134 | self.renderer.draw_key(self.image, self)
135 | self.dirty = 1
136 |
137 | def update(self, events):
138 | """Pygame events processing callback method.
139 |
140 | Parameters
141 | ----------
142 | events:
143 | List of events to process.
144 | """
145 | for event in events:
146 | if event.type == pygame.MOUSEBUTTONDOWN\
147 | and event.button in (1, 2, 3):
148 | # Don't consider the mouse wheel (button 4 & 5):
149 | if self.rect.collidepoint(event.pos):
150 | self.set_pressed(1)
151 | elif event.type == pygame.MOUSEBUTTONUP\
152 | and event.button in (1, 2, 3):
153 | # Don't consider the mouse wheel (button 4 & 5):
154 | self.set_pressed(0)
155 | elif event.type == pygame.FINGERDOWN:
156 | display_size = pygame.display.get_surface().get_size()
157 | finger_pos = (event.x * display_size[0], event.y * display_size[1])
158 | if self.rect.collidepoint(finger_pos):
159 | self.set_pressed(1)
160 | elif event.type == pygame.FINGERUP:
161 | self.set_pressed(0)
162 | elif event.type == pygame.KEYDOWN:
163 | if event.unicode and event.unicode == self.value:
164 | self.set_pressed(1)
165 | self.pressed_key = event.key
166 | elif event.key == self.value:
167 | self.set_pressed(1)
168 | self.pressed_key = event.key
169 | elif event.key == pygame.K_RETURN and self.selected:
170 | self.set_pressed(1)
171 | self.pressed_key = event.key
172 | elif event.type == pygame.KEYUP and self.pressed_key is not None:
173 | self.set_pressed(0)
174 | self.pressed_key = None
175 | elif event.type == pygame.JOYBUTTONDOWN and event.button == 0\
176 | and self.selected: # Select button pressed
177 | self.set_pressed(1)
178 | elif event.type == pygame.JOYBUTTONUP and event.button == 0\
179 | and self.selected: # Select button released
180 | self.set_pressed(0)
181 |
182 | def update_buffer(self, string):
183 | """Text update method.
184 |
185 | Aims to be called internally when a key collision has been detected.
186 | Updates and returns the given buffer using this key value.
187 |
188 | Parameters
189 | ----------
190 | string:
191 | Buffer to be updated.
192 |
193 | Returns
194 | -------
195 | string:
196 | Updated buffer value.
197 | """
198 | return string + self.value
199 |
200 |
201 | class VSpaceKey(VKey):
202 | """Custom key for spacebar. """
203 |
204 | def __init__(self, length):
205 | """Default constructor.
206 |
207 | Parameters
208 | ----------
209 | length:
210 | Key length.
211 | """
212 | VKey.__init__(self, ' ', u'space')
213 | self.length = length
214 |
215 | def set_uppercase(self, uppercase):
216 | """Nothing to do on upper case action."""
217 | pass
218 |
219 | def set_size(self, width, height):
220 | """Sets the size of this key.
221 |
222 | Parameters
223 | ----------
224 | width:
225 | Background width.
226 | height:
227 | Background height.
228 | """
229 | super(VSpaceKey, self).set_size(width * self.length, height)
230 |
231 |
232 | class VBackKey(VKey):
233 | """Custom key for back. """
234 |
235 | def __init__(self):
236 | """Default constructor. """
237 | VKey.__init__(self, u'\x7f', u'\u2190')
238 |
239 | def set_uppercase(self, uppercase):
240 | """Nothing to do on upper case action."""
241 | pass
242 |
243 | def update_buffer(self, string):
244 | """Text update method. Removes last character.
245 |
246 | Parameters
247 | ----------
248 | string:
249 | Buffer to be updated.
250 |
251 | Returns
252 | -------
253 | string:
254 | Updated buffer value.
255 | """
256 | return string[:-1]
257 |
258 |
259 | class VActionKey(VKey):
260 | """
261 | A VActionKey is a key that trigger an action
262 | rather than updating the buffer when pressed.
263 | """
264 |
265 | def __init__(self, action, state_holder, symbol, activated_symbol):
266 | """Default constructor.
267 |
268 | Parameters
269 | ----------
270 | action:
271 | Delegate action called when this key is pressed.
272 | state_holder:
273 | Holder for this key state (activated or not).
274 | """
275 | super(VActionKey, self).__init__('', symbol)
276 | self.action = action
277 | self.state_holder = state_holder
278 | self.activated_symbol = activated_symbol
279 | self.activated = False
280 |
281 | def __str__(self):
282 | """Key representation when using str() or print()"""
283 | if self.is_activated():
284 | self.activated = True
285 | return self.activated_symbol
286 | return self.symbol
287 |
288 | def update(self, events):
289 | """Check if state holder has changed."""
290 | super(VActionKey, self).update(events)
291 | if self.activated != self.is_activated() and not self.dirty:
292 | self.activated = self.is_activated()
293 | self.renderer.draw_key(self.image, self)
294 | self.dirty = 1
295 |
296 | def set_uppercase(self, uppercase):
297 | """Nothing to do on upper case action."""
298 | pass
299 |
300 | def set_pressed(self, state):
301 | """Nothing to do on upper case action."""
302 | prev_state = self.pressed
303 | super(VActionKey, self).set_pressed(state)
304 | if prev_state != self.pressed and self.pressed == 0:
305 | # The key is getting unpressed
306 | self.action()
307 |
308 | def is_activated(self):
309 | """Indicates if this key is activated.
310 |
311 | Returns
312 | -------
313 | is_activated: bool
314 | True if activated, False otherwise.
315 | """
316 | raise NotImplementedError(
317 | "Method 'is_activated' have to be overwritten")
318 |
319 | def update_buffer(self, string):
320 | """Do not update text but trigger the delegate action.
321 |
322 | Parameters
323 | ----------
324 | string:
325 | Not used, just to match parent interface.
326 |
327 | Returns
328 | -------
329 | string:
330 | Buffer provided as parameter.
331 | """
332 | return string
333 |
334 |
335 | class VUppercaseKey(VActionKey):
336 | """Action key for the uppercase switch. """
337 |
338 | def __init__(self, action, state_holder):
339 | super(VUppercaseKey, self).__init__(action,
340 | state_holder,
341 | u'\u21e7',
342 | u'\u21ea')
343 | self.value = pygame.K_LSHIFT
344 |
345 | def is_activated(self):
346 | """Indicates if this key is activated.
347 |
348 | Returns
349 | -------
350 | is_activated: bool
351 | True if activated, False otherwise.
352 | """
353 | return self.state_holder.uppercase
354 |
355 |
356 | class VSpecialCharKey(VActionKey):
357 | """Action key for the special char switch. """
358 |
359 | def __init__(self, action, state_holder):
360 | super(VSpecialCharKey, self).__init__(action,
361 | state_holder,
362 | u'#',
363 | u'Ab')
364 |
365 | def is_activated(self):
366 | """Indicates if this key is activated.
367 |
368 | Returns
369 | -------
370 | is_activated: bool
371 | True if activated, False otherwise.
372 | """
373 | return self.state_holder.special_char
374 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/vrenderers.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """
5 | Renderer for the keyboard and the text box used to display
6 | the current text.
7 | """
8 |
9 | import os.path as osp
10 | import pygame # pylint: disable=import-error
11 |
12 | from . import vkeys
13 |
14 |
15 | def fit_font(font_name, max_height):
16 | """Set the size of the font to fit the given height.
17 |
18 | This function uses the binary search algorithm to go faster
19 | than a one-by-one try.
20 |
21 | Parameters
22 | ----------
23 | font_name:
24 | Path to font file for rendering key.
25 | max_height:
26 | Height to fit.
27 | """
28 | font = pygame.font.Font(font_name, 1)
29 |
30 | # Ensure a large panel of characters heights
31 | text = "?/|!()§&@0123456789azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN" # noqa
32 |
33 | start = 1
34 | end = max_height * 2
35 |
36 | while start < end:
37 | k = (start + end) // 2
38 | font = pygame.font.Font(font_name, k)
39 | height = font.size(text)[1]
40 | if height > max_height:
41 | end = k
42 | else:
43 | start = k + 1
44 | if start < end:
45 | # Run garbage collector, to avoid opening too many files
46 | del font
47 |
48 | return font
49 |
50 |
51 | def draw_round_rect(surface, color, rect, radius=0.1, width=0):
52 | """Draw a rounded rectangle.
53 |
54 | Parameters
55 | ----------
56 | surface:
57 | Surface to draw on.
58 | color:
59 | RGBA tuple color to draw with, the alpha value is optional.
60 | rect:
61 | Rectangle to draw, position and dimensions.
62 | radius:
63 | Used for drawing rectangle with rounded corners. The supported range is
64 | [0, 1] with 0 representing a rectangle without rounded corners.
65 | width:
66 | Line thickness (0 to fill the rectangle).
67 | """
68 | rect = pygame.Rect(rect)
69 | if len(color) == 4:
70 | alpha = color[-1]
71 | color = color[:3] + (0,)
72 | else:
73 | alpha = 255
74 | color += (0,)
75 |
76 | shape = pygame.Surface(rect.size, pygame.SRCALPHA)
77 |
78 | circle = pygame.Surface([min(rect.size) * 3] * 2, pygame.SRCALPHA)
79 | if width > 0:
80 | pygame.draw.arc(circle, (0, 0, 0), circle.get_rect(),
81 | 1.571, 3.1415, width * 8)
82 | else:
83 | pygame.draw.ellipse(circle, (0, 0, 0), circle.get_rect(), 0)
84 | circle = pygame.transform.smoothscale(circle,
85 | [int(min(rect.size) * radius)] * 2)
86 |
87 | i = 1
88 | shape_rect = shape.get_rect()
89 | for pos in ('topleft', 'topright', 'bottomleft', 'bottomright'):
90 | r = circle.get_rect(**{pos: getattr(shape_rect, pos)})
91 | shape.blit(circle, r)
92 | if width > 0:
93 | circle = pygame.transform.rotate(circle, -i * 90)
94 | i += 1
95 |
96 | hrect = shape_rect.inflate(0, -circle.get_height() + 1)
97 | vrect = shape_rect.inflate(-circle.get_width() + 1, 0)
98 | if width > 0:
99 | hrect.width = width
100 | vrect.height = width
101 | shape.fill((0, 0, 0), hrect)
102 | shape.fill((0, 0, 0), vrect)
103 | hrect.right = shape_rect.right
104 | vrect.bottom = shape_rect.bottom
105 | shape.fill((0, 0, 0), hrect)
106 | shape.fill((0, 0, 0), vrect)
107 | else:
108 | shape.fill((0, 0, 0), hrect)
109 | shape.fill((0, 0, 0), vrect)
110 |
111 | shape.fill(color, special_flags=pygame.BLEND_RGBA_MAX)
112 | shape.fill((255, 255, 255, alpha), special_flags=pygame.BLEND_RGBA_MIN)
113 |
114 | return surface.blit(shape, rect)
115 |
116 |
117 | class VKeyboardRenderer(object):
118 | """
119 | A VKeyboardRenderer is in charge of keyboard rendering.
120 |
121 | It handles keyboard rendering properties such as color or padding,
122 | and provides several rendering methods.
123 |
124 | .. note::
125 | A DEFAULT and DARK styles are available as class attribute.
126 | """
127 |
128 | DEFAULT = None
129 | DARK = None
130 |
131 | def __init__(self,
132 | font_name,
133 | text_color,
134 | cursor_color,
135 | selection_color,
136 | background_color,
137 | background_key_color,
138 | background_input_color,
139 | text_special_key_color=None,
140 | background_special_key_color=None):
141 | """VKeyboardStyle default constructor.
142 |
143 | Some parameters take a list of color tuples, one per state.
144 | The states are: (released, pressed)
145 |
146 | Parameters
147 | ----------
148 | font_name:
149 | Path to font file for rendering key.
150 | text_color:
151 | List of RGB tuples for text color (one tuple per state).
152 | cursor_color:
153 | RGB tuple for cursor color of text input.
154 | selection_color:
155 | RGB tuple for selected key color.
156 | background_color:
157 | RGB tuple for background colo r for text.
158 | background_key_color:
159 | List of RGB tuples for key background color (one tuple per state).
160 | background_input_color:
161 | RGB tuple for background color of the text input.
162 | text_special_key_color:
163 | List of RGB tuples for special key text color (one tuple per state).
164 | background_special_key_color:
165 | List of RGB tuples for special key background color (one tuple per
166 | state).
167 | """
168 | self.font = None
169 | self.font_height = None
170 | self.font_input = None
171 | self.font_input_height = None
172 | self.font_name = font_name
173 | self.text_color = text_color
174 | self.cursor_color = cursor_color
175 | self.selection_color = selection_color
176 | self.background_color = background_color
177 | self.background_key_color = background_key_color
178 | self.background_input_color = background_input_color
179 |
180 | self.text_special_key_color = text_special_key_color
181 | self.background_special_key_color = background_special_key_color
182 |
183 | def get_text_width(self, text):
184 | """Return the width of the given text in the text input box.
185 |
186 | Parameters
187 | ----------
188 | text:
189 | Text to evaluate.
190 | """
191 | return self.font_input.size(text)[0]
192 |
193 | def truncate(self, text, max_width, start=0, nearest=False):
194 | """Truncate the given text in order to fit the maximum
195 | given width.
196 |
197 | This function uses the binary search algorithm to go faster
198 | than a one-by-one try.
199 |
200 | Parameters
201 | ----------
202 | text:
203 | Text to split.
204 | max_width:
205 | Maximum authorized width of the text (according to font).
206 | start:
207 | Index for searching the text part with correct width.
208 | nearest:
209 | If True, the returned text can have a width higher than
210 | the ``max_width`` to reduce abs(max_width - width).
211 |
212 | Returns
213 | -------
214 | (part, width):
215 | Truncated text and its rendered width.
216 | """
217 | width = 0
218 | end = len(text)
219 |
220 | if end < start:
221 | return text, self.get_text_width(text)
222 |
223 | while start < end:
224 | k = (start + end) // 2
225 | new_width = self.get_text_width(text[:k+1])
226 | if new_width > max_width:
227 | end = k
228 | else:
229 | width = new_width
230 | start = k + 1
231 |
232 | if nearest:
233 | next_width = self.get_text_width(text[:start+1])
234 | if abs(max_width - next_width) < abs(max_width - width):
235 | return text[:start+1], next_width
236 |
237 | return text[:start], width
238 |
239 | def draw_background(self, surface):
240 | """Default drawing method for background.
241 |
242 | Background is drawn as a simple rectangle filled using this
243 | style background color attribute.
244 |
245 | Parameters
246 | ----------
247 | surface:
248 | Surface background should be drawn in.
249 | """
250 | surface.fill(self.background_color)
251 |
252 | def draw_cursor(self, surface, cursor):
253 | """Default drawing method for cursor of the text input box.
254 |
255 | Cursor is drawn as a simple rectangle filled using the
256 | cursor color attribute.
257 |
258 | Parameters
259 | ----------
260 | surface:
261 | Surface representing the cursor.
262 | cursor:
263 | Cursor object.
264 | """
265 | if cursor.selected:
266 | surface.fill(self.selection_color)
267 | else:
268 | surface.fill(self.cursor_color)
269 |
270 | def draw_text(self, surface, text):
271 | """Default drawing method for text input box.
272 |
273 | Draw the text.
274 |
275 | Parameters
276 | ----------
277 | surface:
278 | Surface on which the text is drawn.
279 | text:
280 | Target text to be drawn.
281 | """
282 | if self.font_input_height != surface.get_height():
283 | # Resize font to fit the surface
284 | self.font_input = fit_font(self.font_name, surface.get_height())
285 | self.font_input_height = surface.get_height()
286 |
287 | surface.fill(self.background_input_color)
288 | surface.blit(self.font_input.render(text, 1,
289 | self.text_color[0]), (0, 0))
290 |
291 | def draw_key(self, surface, key):
292 | """Default drawing method for key.
293 |
294 | Draw the key accordingly to it type.
295 |
296 | Parameters
297 | ----------
298 | surface:
299 | Surface background should be drawn in.
300 | key:
301 | Target key to be drawn.
302 | """
303 | if isinstance(key, vkeys.VSpaceKey):
304 | self.draw_space_key(surface, key)
305 | elif isinstance(key, vkeys.VBackKey):
306 | self.draw_back_key(surface, key)
307 | elif isinstance(key, vkeys.VUppercaseKey):
308 | self.draw_uppercase_key(surface, key)
309 | elif isinstance(key, vkeys.VSpecialCharKey):
310 | self.draw_special_char_key(surface, key)
311 | else:
312 | self.draw_character_key(surface, key)
313 |
314 | def draw_character_key(self, surface, key, special=False):
315 | """Default drawing method for key.
316 |
317 | Key is drawn as a simple rectangle filled using this
318 | cell style background color attribute. Key value is printed
319 | into drawn cell using internal font.
320 |
321 | Parameters
322 | ----------
323 | surface:
324 | Surface key background should be drawn in.
325 | key:
326 | Target key to be drawn.
327 | special:
328 | Boolean flag that indicates if the drawn key should use
329 | special background color if available.
330 | """
331 | rect = surface.get_rect().inflate(-2, -2)
332 | if self.font_height != rect.height:
333 | # Resize font to fit the surface
334 | self.font = fit_font(self.font_name, rect.height)
335 | self.font_height = rect.height
336 |
337 | background_color = self.background_key_color[key.pressed]
338 | if special and self.background_special_key_color:
339 | background_color = self.background_special_key_color[key.pressed]
340 |
341 | text_color = self.text_color[key.pressed]
342 | if special and self.text_special_key_color and not key.pressed:
343 | # Key is not pressed, color according to activated state
344 | state = getattr(key, 'activated', key.pressed)
345 | text_color = self.text_special_key_color[state]
346 |
347 | surface.fill(self.background_color)
348 | draw_round_rect(surface, background_color, rect, 0.4)
349 | if key.selected:
350 | draw_round_rect(surface, self.selection_color, surface.get_rect(), 0.4, 1)
351 |
352 | text = self.font.render(str(key), 1, text_color)
353 | x = (key.rect.width - text.get_width()) // 2
354 | y = (key.rect.height - text.get_height()) // 2
355 | surface.blit(text, (x, y))
356 |
357 | def draw_space_key(self, surface, key):
358 | """Default drawing method space key.
359 |
360 | Key is drawn as a simple rectangle filled using this
361 | cell style background color attribute. Key value is printed
362 | into drawn cell using internal font.
363 |
364 | Parameters
365 | ----------
366 | surface:
367 | Surface background should be drawn in.
368 | key:
369 | Target key to be drawn.
370 | """
371 | self.draw_character_key(surface, key, False)
372 |
373 | def draw_back_key(self, surface, key):
374 | """Default drawing method for back key. Drawn as character key.
375 |
376 | Parameters
377 | ----------
378 | surface:
379 | Surface background should be drawn in.
380 | key:
381 | Target key to be drawn.
382 | """
383 | self.draw_character_key(surface, key, True)
384 |
385 | def draw_uppercase_key(self, surface, key):
386 | """Default drawing method for uppercase key. Drawn as character key.
387 |
388 | Parameters
389 | ----------
390 | surface:
391 | Surface background should be drawn in.
392 | key:
393 | Target key to be drawn.
394 | """
395 | self.draw_character_key(surface, key, True)
396 |
397 | def draw_special_char_key(self, surface, key):
398 | """Default drawing method for special char key.
399 | Drawn as character key.
400 |
401 | Parameters
402 | ----------
403 | surface:
404 | Surface background should be drawn in.
405 | key:
406 | Target key to be drawn.
407 | """
408 | self.draw_character_key(surface, key, True)
409 |
410 |
411 | # Default keyboard renderer
412 | VKeyboardRenderer.DEFAULT = VKeyboardRenderer(
413 | osp.join(osp.dirname(__file__), 'DejaVuSans.ttf'),
414 | text_color=((0, 0, 0), (255, 255, 255)),
415 | cursor_color=(0, 0, 0),
416 | selection_color=(0, 0, 200),
417 | background_color=(255, 255, 255),
418 | background_key_color=((255, 255, 255), (0, 0, 0)),
419 | background_input_color=(220, 220, 220),
420 | background_special_key_color=((180, 180, 180), (0, 0, 0))
421 | )
422 |
423 | VKeyboardRenderer.DARK = VKeyboardRenderer(
424 | osp.join(osp.dirname(__file__), 'DejaVuSans.ttf'),
425 | text_color=((182, 183, 184), (255, 255, 255)),
426 | cursor_color=(255, 255, 255),
427 | selection_color=(124, 183, 62),
428 | background_color=(0, 0, 0),
429 | background_key_color=((59, 56, 54), (47, 48, 51)),
430 | background_input_color=(80, 80, 80),
431 | text_special_key_color=((182, 183, 184), (124, 183, 62)),
432 | background_special_key_color=((35, 33, 30), (47, 48, 51))
433 | )
434 |
--------------------------------------------------------------------------------
/pygame_vkeyboard/vtextinput.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """
5 | Text box to display the current text. The mouse events are
6 | supported to move the cursor at the desired place.
7 | """
8 |
9 | import pygame # pylint: disable=import-error
10 |
11 | from .vrenderers import VKeyboardRenderer
12 |
13 |
14 | class VBackground(pygame.sprite.DirtySprite):
15 | """Background of the text input box. It is used to create
16 | borders by making its size a litle bit larger than the
17 | lines width and the sum of lines heights.
18 | """
19 |
20 | def __init__(self, size, renderer):
21 | """Default constructor.
22 |
23 | Parameters
24 | ----------
25 | size:
26 | Size tuple (width, height) of the background.
27 | renderer:
28 | Renderer used to draw the background.
29 | """
30 | super(VBackground, self).__init__()
31 | self.renderer = renderer
32 | self.image = pygame.Surface(size, pygame.SRCALPHA, 32)
33 | self.rect = pygame.Rect((0, 0), size)
34 |
35 | self.renderer.draw_background(self.image)
36 |
37 | def set_rect(self, x, y, width, height):
38 | """Set the background absolute position and size.
39 |
40 | Parameters
41 | ----------
42 | x:
43 | Position x.
44 | y:
45 | Position y.
46 | width:
47 | Background width.
48 | height:
49 | Background height.
50 | """
51 | if self.rect.topleft != (x, y):
52 | self.rect.topleft = (x, y)
53 | self.dirty = 1
54 | if self.rect.size != (width, height):
55 | self.rect.size = (width, height)
56 | self.image = pygame.Surface((width, height), pygame.SRCALPHA, 32)
57 | self.renderer.draw_background(self.image)
58 | self.dirty = 1
59 |
60 |
61 | class VCursor(pygame.sprite.DirtySprite):
62 | """Handles the cursor.
63 |
64 | The ``index`` represente the absolute position which is the number
65 | of characters before it, all lines included.
66 | """
67 |
68 | def __init__(self, size, renderer):
69 | """Default constructor.
70 |
71 | Parameters
72 | ----------
73 | size:
74 | Size tuple (width, height) of the cursor.
75 | renderer:
76 | Renderer used to draw the cursor.
77 | """
78 | super(VCursor, self).__init__()
79 | self.renderer = renderer
80 | self.image = pygame.Surface(size, pygame.SRCALPHA, 32)
81 | self.rect = self.image.get_rect()
82 | self.index = 0
83 | self.selected = 0
84 |
85 | # Blink management
86 | self.clock = pygame.time.Clock()
87 | self.switch_ms = 400
88 | self.switch_counter = 0
89 |
90 | self.renderer.draw_cursor(self.image, self)
91 |
92 | def set_position(self, position):
93 | """Set the cursor absolute position.
94 |
95 | Parameters
96 | ----------
97 | position:
98 | Position tuple (x, y).
99 | """
100 | if self.rect.topleft != position:
101 | self.rect.topleft = position
102 | self.dirty = 1
103 |
104 | def set_size(self, width, height):
105 | """Set the cursor size.
106 |
107 | Parameters
108 | ----------
109 | width:
110 | Background width.
111 | height:
112 | Background height.
113 | """
114 | if self.rect.size != (width, height):
115 | self.rect.size = (width, height)
116 | self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32)
117 | self.renderer.draw_cursor(self.image, self)
118 | self.dirty = 1
119 |
120 | def set_index(self, index):
121 | """Move the cursor at the given index.
122 |
123 | Parameters
124 | ----------
125 | index:
126 | Absolute (sum all lines) cursor index.
127 | """
128 | if index != self.index:
129 | self.index = index
130 | self.dirty = 1
131 |
132 | def set_selected(self, state):
133 | """Set the key selection state (1 for selected else 0)
134 | and redraws it.
135 |
136 | Parameters
137 | ----------
138 | state:
139 | New key state.
140 | """
141 | if self.selected != int(state):
142 | self.selected = int(state)
143 | self.renderer.draw_cursor(self.image, self)
144 | self.dirty = 1
145 |
146 | def update(self, events):
147 | """Toggle visibility of the cursor."""
148 | self.clock.tick()
149 | self.switch_counter += self.clock.get_time()
150 | if self.switch_counter >= self.switch_ms:
151 | self.switch_counter %= self.switch_ms
152 | self.visible = int(not self.visible)
153 | self.dirty = 1
154 |
155 |
156 | class VLine(pygame.sprite.DirtySprite):
157 | """Handles a line of text. A line can be fed until the text
158 | width reaches the line width.
159 |
160 | By default, when the line is empty, its ``visible`` attribute
161 | is set to 0 to hide the line.
162 | """
163 |
164 | def __init__(self, size, renderer, always_visible=False):
165 | """Default constructor.
166 |
167 | Parameters
168 | ----------
169 | size:
170 | Size tuple (width, height) of the line.
171 | renderer:
172 | Renderer used to draw the line.
173 | always_visible:
174 | If True, the line will be never hidden even if it is empty.
175 | """
176 | super(VLine, self).__init__()
177 | self.renderer = renderer
178 | self.image = pygame.Surface(size, pygame.SRCALPHA, 32)
179 | self.rect = pygame.Rect((0, 0), size)
180 | self.text = ''
181 | self.full = False
182 | self.always_visible = always_visible
183 |
184 | self.renderer.draw_text(self.image, '')
185 |
186 | def __len__(self):
187 | """Return the number of characters in the line."""
188 | return len(self.text)
189 |
190 | def set_position(self, position):
191 | """Set the line absolute position.
192 |
193 | Parameters
194 | ----------
195 | position:
196 | Position tuple (x, y).
197 | """
198 | if self.rect.topleft != position:
199 | self.rect.topleft = position
200 | self.dirty = 1
201 |
202 | def set_size(self, width, height):
203 | """Set the line size.
204 |
205 | Parameters
206 | ----------
207 | width:
208 | Background width.
209 | height:
210 | Background height.
211 | """
212 | if self.rect.size != (width, height):
213 | self.rect.size = (width, height)
214 | self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32)
215 | self.renderer.draw_text(self.image, self.text)
216 | self.dirty = 1
217 |
218 | def clear(self):
219 | """Clear the current text."""
220 | if self.text:
221 | self.text = ''
222 | self.full = False
223 | self.renderer.draw_text(self.image, '')
224 | if not self.always_visible:
225 | self.visible = 0
226 | else:
227 | self.dirty = 1
228 | return self.text
229 |
230 | def feed(self, text):
231 | """Feed the line with the given text. The current text is
232 | cleared if an empty string is given.
233 |
234 | Parameters
235 | ----------
236 | text:
237 | Text to feed in.
238 |
239 | Returns
240 | -------
241 | remain:
242 | Return the remaining text if the line is full.
243 | """
244 | if not text:
245 | return self.clear()
246 | elif self.text:
247 | if text.startswith(self.text):
248 | if self.full:
249 | return text[len(self.text):]
250 | else:
251 | self.text = ''
252 |
253 | self.text, _ = self.renderer.truncate(text,
254 | self.rect.width,
255 | len(self.text))
256 | if text[len(self.text):]:
257 | self.full = True
258 | else:
259 | self.full = False
260 | self.dirty = 1
261 | self.visible = 1 # Show line
262 | self.renderer.draw_text(self.image, self.text)
263 | return text[len(self.text):]
264 |
265 |
266 | class VTextInput(object):
267 | """Handles the text input box.
268 | """
269 |
270 | def __init__(self,
271 | position,
272 | size,
273 | border=2,
274 | renderer=VKeyboardRenderer.DEFAULT):
275 | """Default constructor.
276 |
277 | Parameters
278 | ----------
279 | position:
280 | Position tuple (x, y)
281 | size:
282 | Size tuple (width, height) of the text input.
283 | border:
284 | Border thickness.
285 | renderer:
286 | Text input renderer instance, using VTextInputRenderer.DEFAULT
287 | if not specified.
288 | """
289 | self.state = 0
290 | self.selected = 0
291 | self.position = position
292 | self.size = size # One ligne size
293 | self.text = ''
294 | self.text_margin = border
295 | self.renderer = renderer
296 |
297 | # Define background sprites
298 | self.eraser = None
299 | self.background = VBackground(size, renderer)
300 | self.sprites = pygame.sprite.LayeredDirty(self.background)
301 |
302 | # Initialize first line
303 | line = VLine((self.size[0] - 2 * self.text_margin,
304 | self.size[1]), renderer, True)
305 | self.sprites.add(line, layer=1)
306 |
307 | # Initialize cursor
308 | self.cursor = VCursor((2, self.size[1] - self.text_margin * 2), renderer)
309 | self.sprites.add(self.cursor, layer=2)
310 |
311 | self.disable()
312 |
313 | self.set_line_rect(*self.position + self.size)
314 |
315 | def set_eraser(self, surface):
316 | """Setup the surface used to hide/clear the text input.
317 | """
318 | self.eraser = surface.copy()
319 | self.sprites.clear(surface, self.eraser)
320 |
321 | def enable(self):
322 | """Set this text input as active."""
323 | self.state = 1
324 | self.cursor.visible = 1
325 | self.background.visible = 1
326 | self.sprites.get_sprites_from_layer(1)[0].visible = 1
327 |
328 | def is_enabled(self):
329 | """Return True if this keyboard is active."""
330 | return self.state == 1
331 |
332 | def disable(self):
333 | """Set this text input as non active."""
334 | self.state = 0 # Disabled by default
335 | self.cursor.visible = 0
336 | self.background.visible = 0
337 | for line in self.sprites.get_sprites_from_layer(1):
338 | line.visible = 0
339 |
340 | def set_selected(self, state):
341 | """Set the input box selection state (1 for selected else 0)
342 | and redraws it.
343 |
344 | Parameters
345 | ----------
346 | state:
347 | New key state.
348 | """
349 | self.selected = int(state)
350 | self.cursor.set_selected(state)
351 |
352 | def get_rect(self):
353 | """Return text input rect."""
354 | return self.background.rect
355 |
356 | def set_line_rect(self, x, y, width, height):
357 | """Set the input text (one line) absolute position and size.
358 |
359 | Parameters
360 | ----------
361 | x:
362 | Position x.
363 | y:
364 | Position y.
365 | width:
366 | Background width.
367 | height:
368 | Background height.
369 | """
370 | self.size = (width, height)
371 | self.position = (x, y)
372 | self.background.set_rect(self.position[0],
373 | self.position[1] - 2 * self.text_margin,
374 | width,
375 | height + 2 * self.text_margin)
376 |
377 | for line in self.sprites.get_sprites_from_layer(1):
378 | line.clear()
379 | line.set_position((self.position[0] + self.text_margin,
380 | self.position[1] - self.text_margin))
381 | line.set_size(self.size[0] - 2 * self.text_margin, self.size[1])
382 | self.update_lines()
383 |
384 | self.cursor.set_position((self.position[0] + self.text_margin,
385 | self.position[1]))
386 | self.cursor.set_size(2, self.size[1] - self.text_margin * 2)
387 |
388 | # Setup new the surface where to draw
389 | clip_rect = pygame.Rect(self.position[0], 0,
390 | self.background.rect.width,
391 | self.background.rect.bottom)
392 | if self.sprites.get_clip() != clip_rect:
393 | # Changing the clipping area will force update of all
394 | # sprites without using "dirty mechanism"
395 | self.sprites.set_clip(clip_rect)
396 |
397 | def draw(self, surface, force):
398 | """Draw the text input box.
399 |
400 | Parameters
401 | ----------
402 | surface:
403 | Surface on which the VTextInput is drawn.
404 | force:
405 | Force the drawing of the entire surface (time consuming).
406 | """
407 | # Check if surface has been resized
408 | if self.eraser and surface.get_rect() != self.eraser.get_rect():
409 | force = True # To force creating new eraser
410 |
411 | # Setup eraser
412 | if not self.eraser or force:
413 | self.set_eraser(surface)
414 |
415 | if force:
416 | self.sprites.repaint_rect(self.background.rect)
417 | return self.sprites.draw(surface)
418 |
419 | def update(self, events):
420 | """Pygame events processing callback method.
421 |
422 | Parameters
423 | ----------
424 | events:
425 | List of events to process.
426 | """
427 | if self.state == 1:
428 | self.sprites.update(events)
429 | for event in events:
430 | if event.type == pygame.KEYUP and self.cursor.selected:
431 | if event.key == pygame.K_LEFT:
432 | self.increment_cursor(-1)
433 | elif event.key == pygame.K_RIGHT:
434 | self.increment_cursor(1)
435 | elif event.key == pygame.K_HOME:
436 | self.cursor.index = 0
437 | self.increment_cursor(0)
438 | elif event.key == pygame.K_END:
439 | self.cursor.index = 0
440 | self.increment_cursor(len(self.text))
441 | if event.type == pygame.MOUSEBUTTONDOWN\
442 | and event.button in (1, 2, 3):
443 | # Don't consider the mouse wheel (button 4 & 5):
444 | self.set_cursor(event.pos)
445 | if event.type == pygame.FINGERDOWN:
446 | display_size = pygame.display.get_surface().get_size()
447 | finger_pos = (event.x * display_size[0], event.y * display_size[1])
448 | self.set_cursor(finger_pos)
449 |
450 | def update_lines(self):
451 | """Update lines content with the current text."""
452 | if self.state == 1:
453 | remain = self.text
454 |
455 | # Update existing line with text
456 | for line in self.sprites.get_sprites_from_layer(1):
457 | remain = line.feed(remain)
458 |
459 | # Create new lines if necessary
460 | while remain:
461 | line = VLine((self.size[0] - 2 * self.text_margin,
462 | self.size[1]), self.renderer)
463 | self.sprites.add(line, layer=1)
464 | remain = line.feed(remain)
465 |
466 | # Update lines positions
467 | i = 0
468 | for line in reversed(self.sprites.get_sprites_from_layer(1)):
469 | if line.visible:
470 | x = self.position[0] + self.text_margin
471 | y = self.position[1] - i * self.size[1] - self.text_margin
472 | line.set_position((x, y))
473 | i += 1
474 |
475 | self.background.set_rect(self.position[0],
476 | line.rect.y - self.text_margin,
477 | self.size[0],
478 | i * self.size[1] + 2 * self.text_margin)
479 |
480 | def set_text(self, text):
481 | """Overwrite the current text with the given one. The cursor is
482 | moved at the end of the text.
483 |
484 | Parameters
485 | ----------
486 | text:
487 | New text.
488 | """
489 | self.text = text
490 | self.update_lines()
491 | self.cursor.index = 0
492 | self.increment_cursor(len(text))
493 |
494 | def add_at_cursor(self, text):
495 | """Add a text whereever the cursor is currently located.
496 |
497 | Parameters
498 | ----------
499 | text:
500 | Single char or text to append.
501 | """
502 | if self.cursor.index < len(self.text):
503 | # Inserting in the text
504 | prefix = self.text[:self.cursor.index]
505 | suffix = self.text[self.cursor.index:]
506 | self.text = prefix + text + suffix
507 | else:
508 | self.text += text
509 | self.update_lines()
510 | self.increment_cursor(1)
511 |
512 | def delete_at_cursor(self):
513 | """Delete a character before the cursor position."""
514 | if self.cursor.index == 0:
515 | return
516 | prefix = self.text[:self.cursor.index - 1]
517 | suffix = self.text[self.cursor.index:]
518 | self.text = prefix + suffix
519 | self.update_lines()
520 | self.increment_cursor(-1)
521 |
522 | def increment_cursor(self, step):
523 | """Move the cursor of one or more steps (but not beyond the
524 | text length).
525 |
526 | Parameters
527 | ----------
528 | step:
529 | From how many characters the cursor shall move.
530 | """
531 | pos = max(0, self.cursor.index + step)
532 | self.cursor.set_index(min(pos, len(self.text)))
533 |
534 | # Update cursor position
535 | chars_counter = 0
536 | for line in self.sprites.get_sprites_from_layer(1):
537 | if chars_counter + len(line) >= self.cursor.index:
538 | idx = self.cursor.index - chars_counter
539 | x = self.text_margin + self.renderer.get_text_width(
540 | line.text[:idx])
541 | self.cursor.set_position((x, line.rect.y + self.text_margin))
542 | break
543 | chars_counter += len(line)
544 |
545 | def set_cursor(self, position):
546 | """Move cursor to char nearest position (x, y).
547 |
548 | Parameters
549 | ----------
550 | position:
551 | Absolute position (x, y) on the surface.
552 | """
553 | for collide in self.sprites.get_sprites_at(position):
554 | if isinstance(collide, VLine):
555 | text, width = self.renderer.truncate(
556 | collide.text,
557 | position[0] - collide.rect.left,
558 | nearest=True)
559 | self.cursor.set_position((
560 | width + self.text_margin,
561 | collide.rect.y + self.text_margin))
562 | chars_counter = 0
563 | for line in self.sprites.get_sprites_from_layer(1):
564 | if line == collide:
565 | self.cursor.set_index(chars_counter + len(text))
566 | return
567 | chars_counter += len(line)
568 |
--------------------------------------------------------------------------------
/screenshot/vkeyboard_azerty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Faylixe/pygame-vkeyboard/3385a8d5a5290c0a622d830c1383c7f0258becc5/screenshot/vkeyboard_azerty.png
--------------------------------------------------------------------------------
/screenshot/vkeyboard_numeric.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Faylixe/pygame-vkeyboard/3385a8d5a5290c0a622d830c1383c7f0258becc5/screenshot/vkeyboard_numeric.gif
--------------------------------------------------------------------------------
/screenshot/vkeyboard_textinput.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Faylixe/pygame-vkeyboard/3385a8d5a5290c0a622d830c1383c7f0258becc5/screenshot/vkeyboard_textinput.gif
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | """ Distribution script. """
5 |
6 | import sys
7 | from os.path import abspath, dirname, join
8 | from setuptools import setup, find_packages
9 | if sys.version_info[0] == 2:
10 | from io import open # Support for encoding on Python 2.x
11 |
12 | import pygame_vkeyboard
13 |
14 | here = dirname(abspath(__file__))
15 | with open(join(here, 'README.md'), 'r', encoding='utf-8') as stream:
16 | readme = stream.read()
17 |
18 | setup(
19 | name='pygame-vkeyboard',
20 | version=pygame_vkeyboard.__version__,
21 | description=pygame_vkeyboard.__doc__,
22 | long_description=readme,
23 | long_description_content_type='text/markdown',
24 | author='Felix Voituret, Antoine Rousseaux',
25 | author_email='felix@voituret.fr, anxuae-prog@yahoo.fr',
26 | url='https://github.com/Faylixe/pygame-vkeyboard',
27 | download_url='https://pypi.org/project/pygame-vkeyboard/#files',
28 | license='Apache License 2.0',
29 | keywords=['pygame', 'keyboard'],
30 | classifiers=[
31 | 'Programming Language :: Python :: 2',
32 | 'Programming Language :: Python :: 3',
33 | 'Topic :: Software Development :: Libraries :: pygame'
34 | ],
35 | packages=find_packages(),
36 | include_package_data=True,
37 | python_requires='>=2.7',
38 | install_requires=[
39 | 'pygame',
40 | ],
41 | setup_requires=[
42 | 'setuptools>=41.0.1',
43 | 'wheel>=0.33.4'
44 | ],
45 | options={
46 | 'bdist_wheel':
47 | {'universal': True} # Support Python 2.x
48 | },
49 | )
50 |
--------------------------------------------------------------------------------
/tests/test_examples.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf8
3 |
4 | from pygame_vkeyboard.examples import azerty, numeric, textinput
5 |
6 |
7 | def test_example_azerty():
8 | """Run the ASERTY example."""
9 | azerty.main(test=True)
10 |
11 |
12 | def test_example_numeric():
13 | """Run the NUMERIC example."""
14 | numeric.main(test=True)
15 |
16 |
17 | def test_example_textinput():
18 | """Run the TEXTINPUT example."""
19 | textinput.main(test=True)
20 |
--------------------------------------------------------------------------------