С помощью Джона Андерсона я смог найти полное решение, переназначив клавиши цифровой клавиатуры на обычные клавиши, когда numlock выключен:
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivymd.app import MDApp
from kivymd.uix.textfield import MDTextField
numpad_map = dict(
numpad1=(279, 'end' ), #257
numpad2=(274, 'down' ), #258
numpad3=(281, 'pagedown'), #259
numpad4=(276, 'left' ), #260
numpad6=(275, 'right' ), #262
numpad7=(278, 'home' ), #263
numpad8=(273, 'up' ), #264
numpad9=(280, 'pageup' ), #265
numpaddecimal=(127, 'delete'), #266
)
def fix_numpad(keycode, modifiers):
"""This function actually remap numpad arrows to regular arrows if numlock is off"""
keynum, keylbl = keycode
if 'numlock' not in modifiers and keylbl in numpad_map:
keycode = numpad_map[keylbl]
return keycode
class TextInput2(TextInput):
def keyboard_on_key_down(self, window, keycode, text, modifiers):
keycode = fix_numpad(keycode, modifiers)
return super().keyboard_on_key_down(window, keycode, text, modifiers)
class MDTextField2(MDTextField):
def keyboard_on_key_down(self, window, keycode, text, modifiers):
keycode = fix_numpad(keycode, modifiers)
return super().keyboard_on_key_down(window, keycode, text, modifiers)
KV = """
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.bg_normal
TextInput2:
MDTextField2:
"""
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
EDIT: Копаем дальше в исходном коде я понял, что MDTextField имеет множество подклассов и наследуется от FixedHintTextInput, который наследуется от TextInput. Итак, я нашел универсальный способ исправления TextInput. Я не уверен, что это самое надежное решение, но я считаю его несколько элегантным, потому что оно исправляет поведение всех виджетов без изменения имен классов в нашем layout (пока исходный код kivy не будет исправлен). Вот код:
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivymd.app import MDApp
def wrap_class_function(class_type, fn_attr_name, fn_wrapper_maker):
"""Replaces a method at the Class-level (class function) so that it affects
all instance methods (current and future, including subclasses)"""
old_fn = getattr(class_type, fn_attr_name) # Get original class function
new_fn = fn_wrapper_maker(old_fn) # Create new wrapper around it
new_fn.__name__ = fn_attr_name # Set the __name__ of wrapper (so bindings still work)
setattr(class_type, fn_attr_name, new_fn) # Replace old function with new one
def keyboard_on_key_down_wrapper_maker(original_fn):
"""Creates a wrapper function that applies the numpad patch, to be used
directly on TextInput base class (to fix all subclasses)"""
numpad_map = dict(
numpad1=(279, 'end' ), #257
numpad2=(274, 'down' ), #258
numpad3=(281, 'pagedown'), #259
numpad4=(276, 'left' ), #260
numpad6=(275, 'right' ), #262
numpad7=(278, 'home' ), #263
numpad8=(273, 'up' ), #264
numpad9=(280, 'pageup' ), #265
numpaddecimal=(127, 'delete'), #266
)
def keyboard_on_key_down_wrapper(self, window, keycode, text, modifiers):
key, key_str = keycode
if 'numlock' not in modifiers and key_str in numpad_map:
keycode = numpad_map[key_str]
return original_fn(self, window, keycode, text, modifiers)
return keyboard_on_key_down_wrapper
wrap_class_function(TextInput, 'keyboard_on_key_down', keyboard_on_key_down_wrapper_maker)
KV = """
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.bg_normal
TextInput:
MDTextField: # This widget inherits TextInput patch
"""
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()