Kivy Window.request_keyboard only working on the second screen - python

So I am making an app in kivy python and it has 2 screens that I want the app to have keyboard access to. So i have implemented the standard request keyboard as given on the Kivy documentation. But it is only working on Screen2 and not on screen1. Plus, when I am on screen1, it still implements the on_keyboard_down() of screen2.
Below is a reproducible code with the same problem.
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<screen1>:
Label:
text: "Screen 1"
Button:
text: "go s2"
on_press: root.c1()
<screen2>:
Label:
text: "Screen 2"
Button:
text: "go s1"
on_press: root.c2()
""")
class screen1(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.request_keyboard()
def request_keyboard(self):
print("s1")
self._keyboard = Window.request_keyboard(self._keyboard_closed, self, "text")
if self._keyboard.widget:
pass
self._keyboard.bind(on_key_down=self._on_keyboard_down)
self._keyboard.bind(on_key_up=self._on_keyboard_up)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == "escape":
app = App.get_running_app()
app.stop()
else:
#self.encry_times +=1
pressed_letter = keycode[1].upper()
c = pressed_letter
print("s1" + c)
return True
def _on_keyboard_up(self, keyboard, keycode):
pass
def c1(self):
eni_app.screen_manager.current = "s2"
class screen2(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.request_keyboard()
def request_keyboard(self):
print("s2")
self._keyboard = Window.request_keyboard(self._keyboard_closed, self, "text")
if self._keyboard.widget:
pass
self._keyboard.bind(on_key_down=self._on_keyboard_down)
self._keyboard.bind(on_key_up=self._on_keyboard_up)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == "escape":
app = App.get_running_app()
app.stop()
else:
#self.encry_times +=1
pressed_letter = keycode[1].upper()
c = pressed_letter
print("s2" + c)
return True
def _on_keyboard_up(self, keyboard, keycode):
pass
def c2(self):
eni_app.screen_manager.current = "s1"
class TestApp(App):
def build(self):
self.screen_manager = ScreenManager(transition = FadeTransition())
self.s1_page = screen1()
screen = Screen(name = "s1")
screen.add_widget(self.s1_page)
self.screen_manager.add_widget(screen)
self.s2_page = screen2()
screen = Screen(name = "s2")
screen.add_widget(self.s2_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
if __name__ == '__main__':
eni_app = TestApp()
eni_app.run()
I have tried adding a on_enter function but it completely blocks any access to the keyboard. on_pre_enter has a similar effect on the code.
The expectation from the code above is: it should print s1+ (whatever key is pressed) and on screen2 it should press s2+(whatever key is pressed)

Related

kivy ToggleButton : How can I trigger a method on the state changement?

Here is my actual code. It does what I want except when I change the state with a script, it doesn't give the information. It only gives the information when the change is made with a user interaction.
from kivymd.app import MDApp
from kivy.lang import Builder
KV = '''
BoxLayout:
ToggleButton:
id: toggle_button
text: "click on me"
'''
class ExampleApp(MDApp):
loading_layout = None
def build(self):
screen = Builder.load_string(KV)
screen.ids["toggle_button"].bind(on_press=lambda instance: self.on_click(instance))
print("I change the state and I want to be aware")
screen.ids["toggle_button"].state = "down"
print("on_click method isn't enough :(")
return screen
def on_click(self, instance):
print("State changed !", instance.state)
ExampleApp().run()
Question: How can I link a function to the state changement and not only on the click ?
What you could do is take advantage of the on_state method of the ToggleButton class. Here is an example:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.togglebutton import ToggleButton
KV = '''
BoxLayout:
CustomToggle:
id: toggle_button
text: "click on me"
'''
class CustomToggle(ToggleButton):
def on_state(self, *args):
print('State changed!', self.state)
def on_press(self):
print("Button pressed!", self.state)
class ExampleApp(MDApp):
loading_layout = None
def build(self):
screen = Builder.load_string(KV)
screen.ids["toggle_button"].state = "down"
return screen
ExampleApp().run()
The on_state method is automatically bound to the state attribute, so that whenever the state is changed the function is called.

RippleButton class is working in one Python script but not while working with screens

While I run this script rippleexample2.py with rippleexample2.kv, the buttons should have ripple effects upon pressing, but it doesn't work.
I know the RippleButton class is working fine in ctmbtn.py, when a button is pressed here, there is ripple effect. I don't know what is wrong here. Perhaps binding function?
rippleexample2.py
from kivy.app import App
from kivy.uix.touchripple import TouchRippleBehavior
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import (StringProperty, NumericProperty, ObjectProperty, ListProperty, DictProperty, BooleanProperty)
class RippleButton(TouchRippleBehavior, Button):
isRippled = BooleanProperty(False)
def __init__(self, **kwargs):
super(RippleButton, self).__init__(**kwargs)
def on_touch_down(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and not self.isRippled:
self.isRippled = True
self.ripple_show(touch)
return super(RippleButton, self).on_touch_down(touch)
def on_touch_up(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and self.isRippled:
self.isRippled = False
self.ripple_fade()
return super(RippleButton, self).on_touch_up(touch)
class Login(Screen):
pass
class MainScreen(Screen):
pass
class ScreenManager(ScreenManager):
pass
Login = Builder.load_file("rippleexample2.kv")
class SimpleKivy4(App):
def build(self):
return Login
if __name__ == "__main__":
SimpleKivy4().run()
rippleexample2.kv
ScreenManager:
Login:
MainScreen:
<Login>:
name:"login"
RippleButton:
text:'Login'
font_size: 24
size_hint: (.4,.25)
on_release: app.root.current = "main"
<MainScreen>:
name: "main"
Button:
text: 'back'
on_release: app.root.current = "login"
ctmbtn.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import (StringProperty, NumericProperty, ObjectProperty,
ListProperty, DictProperty, BooleanProperty)
from kivy.uix.touchripple import TouchRippleBehavior
class RippleButton(TouchRippleBehavior, Button):
isRippled = BooleanProperty(False)
def __init__(self, **kwargs):
super(RippleButton, self).__init__(**kwargs)
def on_touch_down(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and not self.isRippled:
self.isRippled = True
self.ripple_show(touch)
return super(RippleButton, self).on_touch_down(touch)
def on_touch_up(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and self.isRippled:
self.isRippled = False
self.ripple_fade()
return super(RippleButton, self).on_touch_up(touch)
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.add_widget(RippleButton(text='btn 1'))
cb = CustomBtn()
cb.bind(pressed=self.btn_pressed)
self.add_widget(cb)
self.add_widget(RippleButton(text='btn 2'))
def btn_pressed(self, instance, pos):
print ('pos: printed from root widget: {pos}'.format(pos=pos))
class CustomBtn(Widget):
pressed = ListProperty([0, 0])
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.pressed = touch.pos
# we consumed the touch. return False here to propagate
# the touch further to the children.
return True
return super(CustomBtn, self).on_touch_down(touch)
def on_pressed(self, instance, pos):
print ('pressed at {pos}'.format(pos=pos))
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The Touch Ripple code is still experimental, and it is not visible when using it in kv language for Button widget.
Actually, the Button touch ripple animation on interaction works when using kv language. It is just not visible because the background_color (r, g, b, a) of Button widget defaults to [1, 1, 1, 1] or grey color with no transparency. Whereby 'a' (alpha compositing or transparency) is 1 i.e. not transparent.
The ripple_color defaults to [1., 1., 1., .5] or grey color with half transparency.
Solution
The temporary work around is to change the alpha compositing or transparency of Button widget to 0.5.
Please refer to my example for Kivy RippleButton

Recapture keyboard focus in Kivy after clicking out of a `TextInput` object

The following code creates a Kivy app with a single screen, MainScreen, displaying a Label and TextInput object.
If you give some keyboard input when the app first loads, it prints in the console with information about which key was pressed. However, if you then click in the TextInput object the keyboard refocuses so you can type in the TextInput object. However, once you click back out of the TextInput object, e.g. on the label or background, and press more keys, the same console printing operation doesn't execute; it seems that the keyboard doesn't 'defocus' from the TextInput object.
How do I recapture keyboard focus after clicking out of a TextInput object?
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.textinput import TextInput
kv = '''ScreenManagement:
MainScreen:
<MainScreen>:
name: "main"
BoxLayout:
Label:
text: "Label"
font_size: 20
size_hint: 0.2,0.1
TextInput
input_filter: 'float'
font_size: 20
hint_text: "Input Text"
size_hint: 0.2,0.1'''
class MainScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
print('INFO: The key', keycode, 'has been pressed')
return True # return True to accept the key. Otherwise, it will be used by the system.
class ScreenManagement(ScreenManager):
pass
class MainApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == "__main__":
MainApp().run()
One possible solution is to use the on_touch_down event and reconfigure the keyboard if you press a section that does not contain a TextInput:
class MainScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.config_keyboard()
def on_touch_down(self, touch):
self.isTextInput = False
def filter(widget):
for child in widget.children:
filter(child)
if isinstance(widget, TextInput) and widget.collide_point(*touch.pos):
self.isTextInput = True
widget.on_touch_down(touch)
filter(self)
if not self.isTextInput and self._keyboard is None:
self.config_keyboard()
def config_keyboard(self):
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
print('INFO: The key', keycode, 'has been pressed')
return True # return True to accept the key. Otherwise, it will be used by the system.

usage of DragBehavior in Kivy

I'm new for python and kivy, and struggling with them.
I wanted to implement drag and drop function (a function that when a image is dragged to another image and dropped on it, do something.) with kivy.
What I want to do are the following three things;
know the id of the element that the user has been holding now.
know the id of the element that the user is on hover.
know the id of the element which was under the pointer when the user has dropped what he had.
I thought this would be useful to deal with them, so I wrote down the python script and .kivy file following. However, it dosen't work as I intended.
there are many problems which I don't know how to solve;
print(self.id) returns None.
Even though I am dragging only 1 element, on_touch_up prints None twice.
once you dragged and dropped a image, the image cannot be dragged anymore.
python file:
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.behaviors import DragBehavior
from kivy.properties import BooleanProperty
from kivy.properties import ObjectProperty
from kivy.factory import Factory
from kivy.core.window import Window
class HoverBehavior(object):
"""Hover behavior.
:Events:
`on_enter`
Fired when mouse enter the bbox of the widget.
`on_leave`
Fired when the mouse exit the widget
"""
hovered = BooleanProperty(False)
border_point = ObjectProperty(None)
'''Contains the last relevant point received by the Hoverable. This can
be used in `on_enter` or `on_leave` in order to know where was dispatched the event.
'''
def __init__(self, **kwargs):
self.register_event_type('on_enter')
self.register_event_type('on_leave')
Window.bind(mouse_pos=self.on_mouse_pos)
super(HoverBehavior, self).__init__(**kwargs)
def on_mouse_pos(self, *args):
if not self.get_root_window():
return # do proceed if I'm not displayed <=> If have no parent
pos = args[1]
# Next line to_widget allow to compensate for relative layout
inside = self.collide_point(*self.to_widget(*pos))
if self.hovered == inside:
# We have already done what was needed
return
self.border_point = pos
self.hovered = inside
if inside:
self.dispatch('on_enter')
else:
self.dispatch('on_leave')
def on_enter(self):
pass
def on_leave(self):
pass
Factory.register('HoverBehavior', HoverBehavior)
class DraggableImage(DragBehavior, HoverBehavior, Image):
def __init__(self, **args):
super(DraggableImage, self).__init__(**args)
self.source_file = ""
self.is_on_hover = False
def on_enter(self):
self.source_file = self.source
self.source = "green.png"
self.is_on_hover = True
print(self.id)
def on_leave(self):
self.source = self.source_file
self.is_on_hover = False
print(self.id)
def on_touch_up(self, touch):
if self.is_on_hover:
print(self.id)
class TestApp(App):
def build(self):
pass
if __name__ == '__main__':
TestApp().run()
test.kivy:
<DraggableImage>
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 10000000
drag_distance: 0
BoxLayout:
orientation: "horizontal"
DraggableImage:
id: "left_left"
source: "red.png"
DraggableImage:
id: "left_right"
source: "yellow.png"
DraggableImage:
id: "right_left"
source: "red.png"
DraggableImage:
id: "right_right"
source: "yellow.png"
Create a StringProperty named, name for example. Don't use id, because id is used in kv.
Change your class and kv as so:
from kivy.properties import StringProperty
class DraggableImage(DragBehavior, HoverBehavior, Image):
name = StringProperty("")
def __init__(self, **args):
super(DraggableImage, self).__init__(**args)
self.source_file = ""
self.is_on_hover = False
def on_enter(self):
self.source_file = self.source
self.source = "green.png"
self.is_on_hover = True
print(self.name)
def on_leave(self):
self.source = self.source_file
self.is_on_hover = False
print(self.name)
def on_touch_up(self, touch):
if self.is_on_hover:
print(self.name)
KV = """
<DraggableImage>:
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 10000000
drag_distance: 0
BoxLayout:
orientation: "horizontal"
DraggableImage:
name: "left_left"
source: "red.png"
DraggableImage:
name: "left_right"
source: "yellow.png"
DraggableImage:
name: "right_left"
source: "red.png"
DraggableImage:
name: "right_right"
source: "yellow.png"
"""

Kivy ToggleButtonBehavior

I try to build a toggle between two or more labels like radio buttons. It work so far with the ToggleButtonBehavior to change the state between the group but when I click the selected item it get deselected and should not do it.
class SelectButton(ToggleButtonBehavior, Label):
active = BooleanProperty(False)
def __init__(self, **kwargs):
super(SelectButton, self).__init__(**kwargs)
self.text='off'
self.color=[1,0,1,1]
self.bind(state=self.toggle)
def toggle(self, instance, value):
if self.state == 'down':
self.text = 'on'
else:
self.text = 'off'
Is there a way to get a behavior like a radio button?
There is a allow_no_selection property:
This specifies whether the widgets in a group allow no selection i.e.
everything to be deselected.
allow_no_selection is a BooleanProperty and defaults to True
After setting it to False and using a group everything starts to work as intended:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.properties import BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
<MyWidget>:
SelectButton:
state: 'down'
text: 'on'
SelectButton
state: 'normal'
text: 'off'
SelectButton
state: 'normal'
text: 'off'
''')
class SelectButton(ToggleButtonBehavior, Label):
active = BooleanProperty(False)
def __init__(self, **kwargs):
super(SelectButton, self).__init__(**kwargs)
self.allow_no_selection = False
self.group = "mygroup"
self.color=[1,0,1,1]
self.bind(state=self.toggle)
def toggle(self, instance, value):
if self.state == 'down':
self.text = 'on'
else:
self.text = 'off'
class MyWidget(BoxLayout):
pass
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()

Categories