From 04480157c32ec2dec372d9b05e65f25e6f1607d0 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sun, 2 Aug 2020 12:34:28 +0300 Subject: [PATCH 1/5] New meta-tag '# ba_meta export keyboard' --- .idea/dictionaries/roman.xml | 7 + assets/.asset_manifest_public.json | 4 + assets/Makefile | 4 + assets/src/ba_data/python/ba/__init__.py | 1 + assets/src/ba_data/python/ba/_keyboard.py | 50 +++++ assets/src/ba_data/python/ba/_meta.py | 3 + .../python/bastd/ui/onscreenkeyboard.py | 192 +++++++++--------- assets/src/ba_data/python/keyboards.py | 61 ++++++ 8 files changed, 230 insertions(+), 92 deletions(-) create mode 100644 .idea/dictionaries/roman.xml create mode 100644 assets/src/ba_data/python/ba/_keyboard.py create mode 100644 assets/src/ba_data/python/keyboards.py diff --git a/.idea/dictionaries/roman.xml b/.idea/dictionaries/roman.xml new file mode 100644 index 00000000..120dcfa0 --- /dev/null +++ b/.idea/dictionaries/roman.xml @@ -0,0 +1,7 @@ + + + + maxlen + + + \ No newline at end of file diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json index e6bc81ae..ecfa5dee 100644 --- a/assets/.asset_manifest_public.json +++ b/assets/.asset_manifest_public.json @@ -1,4 +1,5 @@ [ + "ba_data/python/__pycache__/keyboards.cpython-38.opt-1.pyc", "ba_data/python/ba/__init__.py", "ba_data/python/ba/__pycache__/__init__.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_account.cpython-38.opt-1.pyc", @@ -28,6 +29,7 @@ "ba_data/python/ba/__pycache__/_general.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc", + "ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_lang.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc", @@ -87,6 +89,7 @@ "ba_data/python/ba/_general.py", "ba_data/python/ba/_hooks.py", "ba_data/python/ba/_input.py", + "ba_data/python/ba/_keyboard.py", "ba_data/python/ba/_lang.py", "ba_data/python/ba/_level.py", "ba_data/python/ba/_lobby.py", @@ -497,6 +500,7 @@ "ba_data/python/efro/json.py", "ba_data/python/efro/terminal.py", "ba_data/python/efro/util.py", + "ba_data/python/keyboards.py", "server/__pycache__/ballisticacore_server.cpython-38.opt-1.pyc", "server/ballisticacore_server.py" ] \ No newline at end of file diff --git a/assets/Makefile b/assets/Makefile index 31615346..037d59b8 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -177,6 +177,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ build/ba_data/python/ba/_general.py \ build/ba_data/python/ba/_hooks.py \ build/ba_data/python/ba/_input.py \ + build/ba_data/python/ba/_keyboard.py \ build/ba_data/python/ba/_lang.py \ build/ba_data/python/ba/_level.py \ build/ba_data/python/ba/_lobby.py \ @@ -380,6 +381,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ build/ba_data/python/bastd/ui/trophies.py \ build/ba_data/python/bastd/ui/url.py \ build/ba_data/python/bastd/ui/watch.py \ + build/ba_data/python/keyboards.py \ build/server/ballisticacore_server.py SCRIPT_TARGETS_PYC_PUBLIC = \ @@ -411,6 +413,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ build/ba_data/python/ba/__pycache__/_general.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc \ + build/ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_lang.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc \ @@ -614,6 +617,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ build/ba_data/python/bastd/ui/__pycache__/trophies.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/__pycache__/url.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/__pycache__/watch.cpython-38.opt-1.pyc \ + build/ba_data/python/__pycache__/keyboards.cpython-38.opt-1.pyc \ build/server/__pycache__/ballisticacore_server.cpython-38.opt-1.pyc # Rule to copy src asset scripts to dst. diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 6fa61a4c..a511f9c1 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -78,6 +78,7 @@ from ba._gameutils import (GameTip, animate, animate_array, show_damage_count, timestring, cameraflash) from ba._general import (WeakCall, Call, existing, Existable, verify_object_death, storagename) +from ba._keyboard import Keyboard from ba._level import Level from ba._lobby import Lobby, Chooser from ba._math import normalized_color, is_point_in_box, vec3validate diff --git a/assets/src/ba_data/python/ba/_keyboard.py b/assets/src/ba_data/python/ba/_keyboard.py new file mode 100644 index 00000000..3b703a0d --- /dev/null +++ b/assets/src/ba_data/python/ba/_keyboard.py @@ -0,0 +1,50 @@ +# Copyright (c) 2011-2020 Eric Froemling +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- +"""On-screen Keyboard related functionality.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import List, Tuple, Dict + + +class Keyboard: + """Chars definitions for on-screen keyboard. + + Category: App Classes + + Keyboards are discoverable by the meta-tag system + and the user can select which one they want to use. + On-screen keyboard uses chars from active ba.Keyboard. + Attributes: + chars + Used for row/column lengths. + pages + Extra chars like emojis. + nums + The 'num' page. + """ + + chars: List[Tuple[str, ...]] + pages: Dict[str, Tuple[str, ...]] + nums: Tuple[str, ...] diff --git a/assets/src/ba_data/python/ba/_meta.py b/assets/src/ba_data/python/ba/_meta.py index ecc5d727..684bf9e9 100644 --- a/assets/src/ba_data/python/ba/_meta.py +++ b/assets/src/ba_data/python/ba/_meta.py @@ -46,6 +46,7 @@ class ScanResults: """Final results from a metadata scan.""" games: List[str] = field(default_factory=list) plugins: List[str] = field(default_factory=list) + keyboards: List[str] = field(default_factory=list) errors: str = '' warnings: str = '' @@ -289,6 +290,8 @@ class DirectoryScan: self.results.games.append(classname) elif exporttype == 'plugin': self.results.plugins.append(classname) + elif exporttype == 'keyboard': + self.results.keyboards.append(classname) else: self.results.warnings += ( 'Warning: ' + str(subpath) + diff --git a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py index a87af751..3e3d9990 100644 --- a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -37,8 +37,6 @@ class OnScreenKeyboardWindow(ba.Window): """Simple built-in on-screen keyboard.""" def __init__(self, textwidget: ba.Widget, label: str, max_chars: int): - # pylint: disable=too-many-locals - # pylint: disable=too-many-statements self._target_text = textwidget self._width = 700 self._height = 400 @@ -88,22 +86,44 @@ class OnScreenKeyboardWindow(ba.Window): force_internal_editing=True, always_show_carat=True) - self._shift_button = None - self._double_press_shift = False - self._num_mode_button = None - self._emoji_button = None - self._char_keys: List[ba.Widget] = [] - self._mode = 'normal' - self._last_mode = 'normal' - - v = self._height - 180 - key_width = 46 - key_height = 46 self._key_color_lit = (1.4, 1.2, 1.4) - self._key_color = key_color = (0.69, 0.6, 0.74) - self._key_color_dark = key_color_dark = (0.55, 0.55, 0.71) + self._key_color = (0.69, 0.6, 0.74) + self._key_color_dark = (0.55, 0.55, 0.71) + + self._shift_button: Optional[ba.Widget] = None + self._backspace_button: Optional[ba.Widget] = None + self._space_button: Optional[ba.Widget] = None + self._double_press_shift = False + self._num_mode_button: Optional[ba.Widget] = None + self._emoji_button: Optional[ba.Widget] = None + self._char_keys: List[ba.Widget] = [] + self._keyboard_index = 0 + self._last_space_press = 0.0 + self._double_space_interval = 0.3 + + self._keyboard: ba.Keyboard + self._chars: List[str] + self._modes: List[str] + self._mode: str + self._mode_index: int + self._load_keyboard() + + def _load_keyboard(self) -> None: + # pylint: disable=too-many-locals + self._keyboard = self._get_keyboard() + # We want to get just chars without column data, etc. + self._chars = [j for i in self._keyboard.chars for j in i] + self._modes = ['normal'] + list(self._keyboard.pages) + self._mode_index = 0 + self._mode = self._modes[self._mode_index] + + v = self._height - 180.0 + key_width = 46 * 10 / len(self._keyboard.chars[0]) + key_height = 46 * 3 / len(self._keyboard.chars) key_textcolor = (1, 1, 1) - row_starts = (69, 95, 151) + row_starts = (69.0, 95.0, 151.0) + key_color = self._key_color + key_color_dark = self._key_color_dark self._click_sound = ba.getsound('click01') @@ -114,16 +134,12 @@ class OnScreenKeyboardWindow(ba.Window): # dummy data just used for row/column lengths... we don't actually # set things until refresh - chars: List[Tuple[str, ...]] = [ - ('q', 'u', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'), - ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'), - ('z', 'x', 'c', 'v', 'b', 'n', 'm') - ] + chars: List[Tuple[str, ...]] = self._keyboard.chars for row_num, row in enumerate(chars): h = row_starts[row_num] # shift key before row 3 - if row_num == 2: + if row_num == 2 and self._shift_button is None: self._shift_button = ba.buttonwidget( parent=self._root_widget, position=(h - key_width * 2.0, v), @@ -155,17 +171,21 @@ class OnScreenKeyboardWindow(ba.Window): # Add delete key at end of third row. if row_num == 2: - ba.buttonwidget(parent=self._root_widget, - position=(h + 4, v), - size=(key_width * 1.8, key_height), - autoselect=True, - enable_sound=False, - repeat=True, - textcolor=key_textcolor, - color=key_color_dark, - label=charstr(SpCh.DELETE), - button_type='square', - on_activate_call=self._del) + if self._backspace_button is not None: + self._backspace_button.delete() + + self._backspace_button = ba.buttonwidget( + parent=self._root_widget, + position=(h + 4, v), + size=(key_width * 1.8, key_height), + autoselect=True, + enable_sound=False, + repeat=True, + textcolor=key_textcolor, + color=key_color_dark, + label=charstr(SpCh.DELETE), + button_type='square', + on_activate_call=self._del) v -= (key_height + 9) # Do space bar and stuff. if row_num == 2: @@ -196,17 +216,19 @@ class OnScreenKeyboardWindow(ba.Window): button_type='square', ) btn1 = self._num_mode_button - btn2 = ba.buttonwidget(parent=self._root_widget, - position=(210, v - 12), - size=(key_width * 6.1, key_height + 15), - extra_touch_border_scale=0.3, - enable_sound=False, - autoselect=True, - textcolor=key_textcolor, - color=key_color_dark, - label=ba.Lstr(resource='spaceKeyText'), - on_activate_call=ba.Call( - self._type_char, ' ')) + if self._space_button is None: + self._space_button = ba.buttonwidget( + parent=self._root_widget, + position=(210, v - 12), + size=(key_width * 6.1, key_height + 15), + extra_touch_border_scale=0.3, + enable_sound=False, + autoselect=True, + textcolor=key_textcolor, + color=key_color_dark, + label=ba.Lstr(resource='spaceKeyText'), + on_activate_call=ba.Call(self._type_char, ' ')) + btn2 = self._space_button btn3 = self._emoji_button ba.widget(edit=btn1, right_widget=btn2, left_widget=btn3) ba.widget(edit=btn2, @@ -220,14 +242,19 @@ class OnScreenKeyboardWindow(ba.Window): self._refresh() + def _get_keyboard(self) -> ba.Keyboard: + assert ba.app.metascan is not None + path = ba.app.metascan.keyboards[self._keyboard_index] + classname = path.split('.')[-1] + module = path[:-len(classname) - 1] + keyboard = getattr(__import__(module), classname) + assert isinstance(keyboard, ba.Keyboard) + return keyboard + def _refresh(self) -> None: chars: Optional[List[str]] = None if self._mode in ['normal', 'caps']: - chars = [ - 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', - 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', - 'n', 'm' - ] + chars = list(self._chars) if self._mode == 'caps': chars = [c.upper() for c in chars] ba.buttonwidget(edit=self._shift_button, @@ -241,13 +268,12 @@ class OnScreenKeyboardWindow(ba.Window): ba.buttonwidget(edit=self._emoji_button, color=self._key_color_dark, label=charstr(SpCh.LOGO_FLAT), - on_activate_call=self._emoji_mode) - elif self._mode == 'num': - chars = [ - '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', - ':', ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', - '\'', '_' - ] + on_activate_call=self._next_mode) + else: + if self._mode == 'num': + chars = list(self._keyboard.nums) + else: + chars = list(self._keyboard.pages[self._mode]) ba.buttonwidget(edit=self._shift_button, color=self._key_color_dark, label='', @@ -258,29 +284,7 @@ class OnScreenKeyboardWindow(ba.Window): ba.buttonwidget(edit=self._emoji_button, color=self._key_color_dark, label=charstr(SpCh.LOGO_FLAT), - on_activate_call=self._emoji_mode) - - elif self._mode in ['emoji', 'emoji2']: - chars = [ - '💣', '💥', '🙂', '😄', '😆', '😅', '😂', '☺', '😀', '😉', '😇', '😎', - '😰', '😠', '😈', '😨', '😛', '😜', '😝', '😐', '😑', '😵', '😬', '😡', - '😌', '😍' - ] - if self._mode == 'emoji2': - chars = [ - '😔', '😥', '😭', '😖', '😓', '😉', '😴', '😷', '👋', '💯', '🙏', '💪', - '👀', '💬', '💀', '☠', '💩', '👻', '👽', '👾', '❤', '💛', '💚', '💙', - '💜', '💔' - ] - ba.buttonwidget(edit=self._shift_button, - color=self._key_color_lit if self._mode == 'emoji2' - else self._key_color_dark, - label=charstr(SpCh.SHIFT), - on_activate_call=self._emoji_mode_2) - ba.buttonwidget(edit=self._emoji_button, - color=self._key_color_lit, - label=charstr(SpCh.LOGO_FLAT), - on_activate_call=self._emoji_mode) + on_activate_call=self._next_mode) for i, btn in enumerate(self._char_keys): assert chars is not None @@ -302,22 +306,17 @@ class OnScreenKeyboardWindow(ba.Window): self._mode = 'num' self._refresh() - def _emoji_mode(self) -> None: + def _next_mode(self) -> None: ba.playsound(self._click_sound) - if self._mode in ['normal', 'caps', 'num']: - self._last_mode = self._mode - self._mode = 'emoji' - elif self._mode == 'emoji' or self._mode == 'emoji2': - self._mode = self._last_mode + self._mode_index = (self._mode_index + 1) % len(self._modes) + self._mode = self._modes[self._mode_index] self._refresh() - def _emoji_mode_2(self) -> None: - ba.playsound(self._click_sound) - if self._mode == 'emoji': - self._mode = 'emoji2' - elif self._mode == 'emoji2': - self._mode = 'emoji' - self._refresh() + def _next_keyboard(self) -> None: + assert ba.app.metascan is not None + self._keyboard_index = (self._keyboard_index + 1) % len( + ba.app.metascan.keyboards) + self._load_keyboard() def _shift(self) -> None: ba.playsound(self._click_sound) @@ -340,6 +339,15 @@ class OnScreenKeyboardWindow(ba.Window): def _type_char(self, char: str) -> None: ba.playsound(self._click_sound) + if char.isspace(): + if (ba.time(ba.TimeType.REAL) - self._last_space_press < + self._double_space_interval): + self._last_space_press = 0 + self._next_keyboard() + self._del() # We typed unneeded space around 1s ago. + return + self._last_space_press = ba.time(ba.TimeType.REAL) + # operate in unicode so we don't do anything funky like chop utf-8 # chars in half txt = cast(str, ba.textwidget(query=self._text_field)) diff --git a/assets/src/ba_data/python/keyboards.py b/assets/src/ba_data/python/keyboards.py new file mode 100644 index 00000000..abcad17b --- /dev/null +++ b/assets/src/ba_data/python/keyboards.py @@ -0,0 +1,61 @@ +# Copyright (c) 2011-2020 Eric Froemling +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- +"""Defines a default keyboards.""" + +# ba_meta require api 6 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba + +if TYPE_CHECKING: + from typing import Iterable, List + + +def split(chars: Iterable[ba.SpecialChar], + maxlen: int) -> List[List[ba.SpecialChar]]: + """Returns ba.SpecialChar groups with a fixed number of elements""" + result = [] + shatter: List[ba.SpecialChar] = [] + for i in chars: + if len(shatter) < maxlen: + shatter.append(i) + else: + result.append(shatter) + shatter = [i] + return result + + +# ba_meta export keyboard +class EnglishKeyboard(ba.Keyboard): + """Default English keyboard.""" + chars = [('q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'), + ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'), + ('z', 'x', 'c', 'v', 'b', 'n', 'm')] + nums = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', ':', + ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', '\'', '_') + pages = { + f'emoji{i}': tuple(ba.charstr(char) for char in page) + for i, page in enumerate(split(ba.SpecialChar, len(nums))) + } From 3919cfbe379e84ac7ec69fd963a6319705916287 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sun, 2 Aug 2020 12:57:39 +0300 Subject: [PATCH 2/5] remove built-in special chars from keyboard --- assets/src/ba_data/python/keyboards.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/assets/src/ba_data/python/keyboards.py b/assets/src/ba_data/python/keyboards.py index abcad17b..79647195 100644 --- a/assets/src/ba_data/python/keyboards.py +++ b/assets/src/ba_data/python/keyboards.py @@ -30,21 +30,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Iterable, List - - -def split(chars: Iterable[ba.SpecialChar], - maxlen: int) -> List[List[ba.SpecialChar]]: - """Returns ba.SpecialChar groups with a fixed number of elements""" - result = [] - shatter: List[ba.SpecialChar] = [] - for i in chars: - if len(shatter) < maxlen: - shatter.append(i) - else: - result.append(shatter) - shatter = [i] - return result + from typing import Dict, Tuple # ba_meta export keyboard @@ -55,7 +41,4 @@ class EnglishKeyboard(ba.Keyboard): ('z', 'x', 'c', 'v', 'b', 'n', 'm')] nums = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', ':', ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', '\'', '_') - pages = { - f'emoji{i}': tuple(ba.charstr(char) for char in page) - for i, page in enumerate(split(ba.SpecialChar, len(nums))) - } + pages: Dict[str, Tuple[str, ...]] = {} From 6a2471cf773d056829ef34a82c15e8f14ad6990e Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sun, 2 Aug 2020 14:26:49 +0300 Subject: [PATCH 3/5] fix --- assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py index 3e3d9990..b5061621 100644 --- a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -30,7 +30,7 @@ from ba import charstr from ba import SpecialChar as SpCh if TYPE_CHECKING: - from typing import List, Tuple, Optional + from typing import List, Tuple, Optional, Type class OnScreenKeyboardWindow(ba.Window): @@ -242,13 +242,13 @@ class OnScreenKeyboardWindow(ba.Window): self._refresh() - def _get_keyboard(self) -> ba.Keyboard: + def _get_keyboard(self) -> Type[ba.Keyboard]: assert ba.app.metascan is not None path = ba.app.metascan.keyboards[self._keyboard_index] classname = path.split('.')[-1] module = path[:-len(classname) - 1] keyboard = getattr(__import__(module), classname) - assert isinstance(keyboard, ba.Keyboard) + assert issubclass(keyboard, ba.Keyboard) return keyboard def _refresh(self) -> None: From 448eafd13fc0efd7caa0729c6060ef3d4e43793c Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sun, 2 Aug 2020 15:28:11 +0300 Subject: [PATCH 4/5] type-checking fix --- assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py index b5061621..1ca2d0ba 100644 --- a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -30,7 +30,7 @@ from ba import charstr from ba import SpecialChar as SpCh if TYPE_CHECKING: - from typing import List, Tuple, Optional, Type + from typing import List, Tuple, Optional class OnScreenKeyboardWindow(ba.Window): @@ -242,13 +242,13 @@ class OnScreenKeyboardWindow(ba.Window): self._refresh() - def _get_keyboard(self) -> Type[ba.Keyboard]: + def _get_keyboard(self) -> ba.Keyboard: assert ba.app.metascan is not None path = ba.app.metascan.keyboards[self._keyboard_index] classname = path.split('.')[-1] module = path[:-len(classname) - 1] - keyboard = getattr(__import__(module), classname) - assert issubclass(keyboard, ba.Keyboard) + keyboard = getattr(__import__(module), classname)() + assert isinstance(keyboard, ba.Keyboard) return keyboard def _refresh(self) -> None: From 9b041fe51afe76b5d256f33c50e8f2fc99dd9a32 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Mon, 3 Aug 2020 11:11:29 +0300 Subject: [PATCH 5/5] add standard emojis and some screen messages --- assets/src/ba_data/python/ba/_keyboard.py | 3 ++ .../python/bastd/ui/onscreenkeyboard.py | 12 +++++++ assets/src/ba_data/python/keyboards.py | 33 +++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/assets/src/ba_data/python/ba/_keyboard.py b/assets/src/ba_data/python/ba/_keyboard.py index 3b703a0d..04d627f0 100644 --- a/assets/src/ba_data/python/ba/_keyboard.py +++ b/assets/src/ba_data/python/ba/_keyboard.py @@ -37,6 +37,8 @@ class Keyboard: and the user can select which one they want to use. On-screen keyboard uses chars from active ba.Keyboard. Attributes: + name + Displays when user selecting this keyboard. chars Used for row/column lengths. pages @@ -45,6 +47,7 @@ class Keyboard: The 'num' page. """ + name: str chars: List[Tuple[str, ...]] pages: Dict[str, Tuple[str, ...]] nums: Tuple[str, ...] diff --git a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py index 1ca2d0ba..53ab84c2 100644 --- a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -228,6 +228,12 @@ class OnScreenKeyboardWindow(ba.Window): color=key_color_dark, label=ba.Lstr(resource='spaceKeyText'), on_activate_call=ba.Call(self._type_char, ' ')) + ba.textwidget(parent=self._root_widget, + h_align='center', + position=(210, v - 70), + size=(key_width * 6.1, key_height + 15), + text='Double press space to change keyboard', + scale=0.75) btn2 = self._space_button btn3 = self._emoji_button ba.widget(edit=btn1, right_widget=btn2, left_widget=btn3) @@ -317,6 +323,12 @@ class OnScreenKeyboardWindow(ba.Window): self._keyboard_index = (self._keyboard_index + 1) % len( ba.app.metascan.keyboards) self._load_keyboard() + if len(ba.app.metascan.keyboards) < 2: + ba.playsound(ba.getsound('error')) + ba.screenmessage('No other keyboards available', color=(1, 0, 0)) + else: + ba.screenmessage(f'Switching keyboard to "{self._keyboard.name}"', + color=(0, 1, 0)) def _shift(self) -> None: ba.playsound(self._click_sound) diff --git a/assets/src/ba_data/python/keyboards.py b/assets/src/ba_data/python/keyboards.py index 79647195..12d49560 100644 --- a/assets/src/ba_data/python/keyboards.py +++ b/assets/src/ba_data/python/keyboards.py @@ -30,15 +30,44 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Dict, Tuple + from typing import Iterable, List, Tuple, Dict + + +def split(chars: Iterable[str], maxlen: int) -> List[List[str]]: + """Returns char groups with a fixed number of elements""" + result = [] + shatter: List[str] = [] + for i in chars: + if len(shatter) < maxlen: + shatter.append(i) + else: + result.append(shatter) + shatter = [i] + if shatter: + while len(shatter) < maxlen: + shatter.append('') + result.append(shatter) + return result + + +def generate_emojis(maxlen: int) -> List[List[str]]: + """Generates a lot of UTF8 emojis prepared for ba.Keyboard pages""" + all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen) + all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen) + all_emojis += split([chr(i) for i in range(0x1F680, 0x1F6C1)], maxlen) + return all_emojis # ba_meta export keyboard class EnglishKeyboard(ba.Keyboard): """Default English keyboard.""" + name = 'English' chars = [('q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'), ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'), ('z', 'x', 'c', 'v', 'b', 'n', 'm')] nums = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', ':', ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', '\'', '_') - pages: Dict[str, Tuple[str, ...]] = {} + pages: Dict[str, Tuple[str, ...]] = { + f'emoji{i}': tuple(page) + for i, page in enumerate(generate_emojis(len(nums))) + }