Image display blank upon being updated - python

I am using a timer to update the image on the UI after every second. After starting the UI the first image is being displayed. After the timer thread kicks in to display the next image, the widget is displayed blank (i cannot see the image). I can see the image if i start the index from a different value. I do not have an issue with the images. I am unable to update the image on the UI screen. Can anyone help me figure out the issue?
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.image import AsyncImage
from kivy.properties import StringProperty, ListProperty, ObjectProperty
import json, timer
import threading
data = json.load(open('demotest.json'))
Builder.load_file('dv_demo.kv')
class DVDemo(Widget):
message = StringProperty()
image = StringProperty('')
sms = ListProperty()
idx = 0
def __init__(self, **kwargs):
super(DVDemo, self).__init__(**kwargs)
self.update_data_index(self.idx)
def schedule_screens(self):
self.idx = self.idx + 1
if self.idx >= len(data):
time.sleep(500)
temp = threading.Thread(target=self.update_data_index, args=(self.idx,))
threading.Timer(1, temp.start).start()
def update_data_index(self, idx):
# self.image = data[idx]['picture_url']
self.message = data[idx]['message']
self.sms = data[idx]['sms']
message_layout = self.ids.message_panel
message_btn = Button(text=self.message, background_color=[0, 1, 1, 1])
message_layout.add_widget(message_btn)
sms_layout = self.ids.sms_panel
sms_layout.clear_widgets()
for sms_msg in self.sms:
sms_btn = Button(text=sms_msg['sms'], background_color=[0, 0, 2, 1])
sms_layout.add_widget(sms_btn)
self.ids.image_object.source = data[idx]['picture_url']
self.schedule_screens()
class DVDemoApp(App):
def build(self):
self.title = 'DV Demo App'
return DVDemo()
if __name__ == "__main__":
DVDemoApp().run()
<ColoredLabel#Label>:
text_size: self.size
halign: 'left'
valign: 'top'
padding: 4, 4
bold: True
color: (.6, .6, .6, 1)
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.pos
size: self.width - sp(2), self.height - sp(2)
<DVDemo>:
FloatLayout:
id: demo_panels
size: root.width - 50, root.height - 50
pos: root.x + 25, root.y + 25
BoxLayout:
id: message_panel
pos_hint: {"x":0, "top":1}
size_hint: 0.5, 1
orientation: 'vertical'
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.pos
size: self.width - sp(2), self.height - sp(2)
#ColoredLabel:
# id: message_panel
# text: root.message
# pos_hint: {"x":0, "top":1}
# size_hint: 0.5, 1
BoxLayout:
id: image_panel
#text: "Image"
pos_hint: {"x":0.5, "top":1}
size_hint: 0.5, 0.5
Image:
id: image_object
source: root.image
center_x: self.parent.center_x
center_y: self.parent.center_y
size: self.parent.height,self.parent.width
BoxLayout:
id: sms_panel
pos_hint: {"x":0.5, "top":0.5}
size_hint: 0.5, 0.5
orientation: 'vertical'
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.pos
size: self.width - sp(2), self.height - sp(2)

Setting the GUI properties must be done on the main thread. Conveniently, there is a Clock.schedule_once() method that schedules a call on the main thread after a delay. So, I would suggest modifying part of the DVDemo class as:
class DVDemo(Widget):
message = StringProperty()
image = StringProperty('')
sms = ListProperty()
idx = 0
def __init__(self, **kwargs):
super(DVDemo, self).__init__(**kwargs)
self.update_data_index()
def schedule_screens(self):
self.idx = self.idx + 1
Clock.schedule_once(self.update_data_index, 5)
def update_data_index(self, *args):
idx = self.idx
.
.
.
The above code uses Clock.schedule_once() and eliminates the passing of idx among methods, since it is an attribute of the DVDemo instance.

Related

Kivy recycleview spacing issue when on android

So I have managed to get widgets of varying height into a recycleview and items can be added, this looked perfect on my PC but was very wrong when running on android. I re-built the app with buildozer to make sure it wasn't something to do with that, I put together the demo program seen below and ran it on both platforms, and experienced the same result. I have used the kivy inspector and not been able to see any values that are not what I didn't manually program. And unless I have missed one I have used dp(X) or 'Xdp' whenever needed.
Any help or tips will be apreciated, thank you :)
main.py
from random import randint
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty, NumericProperty
class NewPostGrid(BoxLayout):
votes_ = StringProperty()
message_id_ = StringProperty()
text_ = StringProperty()
group_ = StringProperty()
_size = ListProperty()
class SizeLabel(Label):
pass
class RV(RecycleView):
distance_to_top = NumericProperty()
scrollable_distance = NumericProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
App.get_running_app().rv_data_list = []
def generate_post(self): # This is only to test posts with different line height
e = ['Test post ID: ', str(App.get_running_app().message_id_num)]
for i in range(randint(1, 8)): e.append('\n')
e.append('end of post')
return "".join(e)
def add(self):
l = len(App.get_running_app().rv_data_list)
text = self.generate_post()
sl = SizeLabel(text=text)
sl.texture_update()
print(sl.text)
App.get_running_app().rv_data_list.extend([{'message_id_': str(App.get_running_app().message_id_num),
'text_': text,
'_size': sl.texture_size,
'group_': str(App.get_running_app().message_id_num),
'votes_': str(20)}])
App.get_running_app().message_id_num = App.get_running_app().message_id_num + 1
def on_scrollable_distance(self, *args):
if self.scroll_y > 0:
self.scroll_y = (self.scrollable_distance - self.distance_to_top) / self.scrollable_distance
def on_scroll_y(self, *args):
self.distance_to_top = (1 - self.scroll_y) * self.scrollable_distance
#def adjust_vote_state(self, id_):
# for d in self.data:
# if d['text_'] == id_.text_:
# d['state'] == id_.ids.button_up.state
# id_.state = id_.ids.button_up.state
class DemoApp(App):
# One post format = {'message_id':0, 'text':'post_test_here','_size':[0,0], '_group':str(0), '_score':20}
# Text fromat string = [font=Nunito-Bold.ttf][color=161616]Someone:[/color][/font]\n
message_id_num = 0
rv_data_list = ListProperty()
pending_data = ListProperty()
def build(self):
#Clock.schedule_interval(self.add_log, .01)
Clock.schedule_interval(self.flush_pending_data, .250)
def up_vote(self, button, mode): # Not part of the problem
if button.state == 'down':
if mode == 'all':
print("+1 upvote for message index:" + str(button.parent.parent.message_id) + ' in all posts')
else:
print("+1 upvote for message index:" + str(button.parent.parent.message_id) + ' in top posts')
def down_vote(self, button, mode): # Not part of the problem
if button.state == 'down':
if mode == 'all':
print("-1 upvote for message index:" + str(button.parent.parent.message_id) + ' in all posts')
else:
print("-1 upvote for message index:" + str(button.parent.parent.message_id) + ' in top posts')
def flush_pending_data(self, *args):
if self.pending_data:
pending_data, self.pending_data = self.pending_data, []
self.rv_data_list.extend(pending_data)
def generate_post(self): # This is only to test posts with different line height
e = ['Test post ID: ', str(self.message_id_num)]
for i in range(randint(1, 8)): e.append('\n')
e.append('end of post')
return "".join(e)
def add_log(self, dt):
for i in range(10):
text = self.generate_post()
sl = SizeLabel(text=text)
sl.texture_update()
self.pending_data.append({'message_id_': str(self.message_id_num),
'text_': text,
'_size': sl.texture_size,
'group_': str(self.message_id_num),
'votes_': str(20)})
self.message_id_num = self.message_id_num + 1
if __name__ == '__main__':
DemoApp().run()
demo.kv
<SizeLabel>:
padding: "10dp", "12dp"
size_hint: 0.9, None
height: self.texture_size[1]
font_size: "12dp"
text_size: self.width, None
color: 0,0,0,1
multiline: True
markup: True
<NewPostGrid>:
spacing: "6dp"
message_id: root.message_id_
BoxLayout:
id: voting_menu
orientation: "vertical"
spacing: "2dp"
size_hint: .2, None
height: label.height
ToggleButton:
id: button_up
on_state: app.up_vote(self, 'all')
group: root.group_
#state: root.vote_state_up
text: "UP"
color: (1,1,1,1) if self.state=='normal' else (.8,0,0,1)
font_size: "10dp"
size_hint: 1, .3
background_color: .2, .2, .2, 0
#on_release: app.root.ids.rv.adjust_vote_state(root)
canvas.before:
Color:
rgba: (.1,.1,.1,1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [6,]
canvas:
Color:
rgba: (.2,.2,.2,1)
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
Label:
id: vote_count
text: root.votes_
size_hint: 1, .4
multiline: False
ToggleButton:
id: button_down
on_state: app.down_vote(self, 'all')
group: root.group_
#state: root.vote_state_down
text: "DOWN"
color: (1,1,1,1) if self.state=='normal' else (.8,0,0,1)
font_size: "10dp"
size_hint: 1, .3
background_color: .2, .2, .2, 0
canvas.before:
Color:
rgba: (.1,.1,.1,1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [6,]
canvas:
Color:
rgba: (.2,.2,.2,1)
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
Label:
id: label
text: root.text_
padding: "10dp", "12dp"
size_hint: .9, None
height: self.texture_size[1]
font_size: "12dp"
text_size: self.width, None
color: 0,0,0,1
multiline: True
markup: True
#on_texture_size: root.update_message_size(root.message_id, self.texture_size)
pos: self.x, self.y
canvas.before:
Color:
rgba: (0.8, 0.8, 0.8, 1)
RoundedRectangle:
size: self.texture_size
radius: [5, 5, 5, 5]
pos: self.x, self.y
canvas:
Color:
rgba:0.8,0,0,1
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: 1, .1
orientation: 'horizontal'
Button:
text: 'Add widget to RV list'
on_release: rv.add()
ToggleButton:
id: active
text: 'active'
on_state: app.add_log(self)
RV:
id: rv
viewclass: 'NewPostGrid' # The view class is TwoButtons, defined above.
data: app.rv_data_list # the data is a list of dicts defined below in the RV class.
scroll_type: ['bars', 'content']
#on_scroll_y: app.fill_data(self, box)
scrollable_distance: box.height - self.height
bar_width: dp(2)
RecycleBoxLayout:
id: box
# This layout is used to hold the Recycle widgets
# default_size: None, dp(48) # This sets the height of the BoxLayout that holds a TwoButtons instance.
key_size: '_size'
default_size_hint: 1, None
size_hint_y: None
spacing: '16dp'
height: self.minimum_height # To scroll you need to set the layout height.
orientation: 'vertical'
padding: ['10dp', '20dp']
Image of app running on windows
Video of app running on android
Video of full app running (The code for the recycleview is identical)

Unable to access a particular id from kivy file to my python ERROR "AttributeError: 'super' object has no attribute '__getattr__'"

The main code <main.py>
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.behaviors import ButtonBehavior
class Background(Widget):
cloud_texture = ObjectProperty(None)
tree1_texture = ObjectProperty(None)
tree2_texture = ObjectProperty(None)
grass_texture = ObjectProperty(None)
bush_texture = ObjectProperty(None)
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.cloud_texture = Image(source="cloud.png").texture
self.cloud_texture.wrap = 'repeat'
self.cloud_texture.uvsize = (Window.width/ self.cloud_texture.width, -1)
self.tree1_texture = Image(source="tree_1.png").texture
self.tree1_texture.wrap = 'repeat'
self.tree1_texture.uvsize = (Window.width/ self.tree1_texture.width, -1)
self.tree2_texture = Image(source="tree_2.png").texture
self.tree2_texture.wrap = 'repeat'
self.tree2_texture.uvsize = (Window.width/ self.tree2_texture.width, -1)
self.grass_texture = Image(source="grass.png").texture
self.grass_texture.wrap = 'repeat'
self.grass_texture.uvsize = (Window.width/ self.grass_texture.width, -1)
self.bush_texture = Image(source="bush.png").texture
self.bush_texture.wrap = 'repeat'
self.bush_texture.uvsize = (Window.width/ self.bush_texture.width, -1)
def on_size(self, *args):
self.cloud_texture.uvsize = (self.width / self.cloud_texture.width, -1)
self.tree1_texture.uvsize = (self.width / self.tree1_texture.width, -1)
self.tree2_texture.uvsize = (self.width / self.tree2_texture.width, -1)
self.grass_texture.uvsize = (self.width / self.grass_texture.width, -1)
self.bush_texture.uvsize = (self.width / self.grass_texture.width, -1)
def scroll_textures(self, time_passed):
self.cloud_texture.uvpos = ((self.cloud_texture.uvpos[0] + time_passed/8.0)% Window.width , self.cloud_texture.uvpos[1])
self.tree1_texture.uvpos = ((self.tree1_texture.uvpos[0] + time_passed/2.0)% Window.width , self.tree1_texture.uvpos[1])
self.tree2_texture.uvpos = ((self.tree2_texture.uvpos[0] + time_passed/2.0)% Window.width , self.tree2_texture.uvpos[1])
self.grass_texture.uvpos = ((self.grass_texture.uvpos[0] + time_passed/8.0)% Window.width , self.grass_texture.uvpos[1])
self.bush_texture.uvpos = ((self.bush_texture.uvpos[0] + time_passed/8.0)% Window.width , self.bush_texture.uvpos[1])
texture = self.property('cloud_texture')
texture.dispatch(self)
texture = self.property('tree1_texture')
texture.dispatch(self)
texture = self.property('tree2_texture')
texture.dispatch(self)
texture = self.property('grass_texture')
texture.dispatch(self)
texture = self.property('bush_texture')
texture.dispatch(self)
print("scroll")
pass
def on_stop(self):
pass
class StartWindow(Screen):
def __init__(self, **kwargs):
super(StartWindow,self).__init__(**kwargs)
pass
class GameWindow(Screen):
def __init__(self, **kwargs):
super(GameWindow,self).__init__(**kwargs)
pass
class OptionsWindow(Screen):
def __init__(self, **kwargs):
super(OptionsWindow,self).__init__(**kwargs)
pass
class WindowManager(ScreenManager):
pass
class MainApp(App):
def __getattr__(self, attr):
return super().__getattr__(attr)
def on_start(self):
Clock.schedule_interval(self.root.ids.background.scroll_textures,1/60.)
pass
MainApp().run()
The line "Clock.schedule_interval(g.ids.background.scroll_textures,1/60.)" is not working. I am not understanding the fundamental way to call the id from the kv file.
main.kv file
WindowManager:
StartWindow:
GameWindow:
OptionsWindow:
<StartWindow>:
name: "start"
id: startwindow
FloatLayout:
cols:4
Image:
source: 'Flying_whale.png'
allow_stretch: True
keep_ratio: False
Button:
pos_hint:{"x":0,"y":0.50}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.3
text: "Start!"
id: set_event
on_press:
root.manager.transition.direction = "down"
root.on_start()
Button:
pos_hint:{"x":0.15,"y":0.25}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Options"
on_release:
root.manager.transition.direction = "right"
app.root.current = "options"
Button:
pos_hint:{"x":0.15,"y":0.10}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Exit"
<GameWindow>
name:"game"
id: gamewindow
FloatLayout:
Background:
id: background
canvas.before:
Rectangle:
size: self.size
pos: self.pos
source: "sky.png"
Rectangle:
size: self.width, 150
pos: self.pos[0],self.pos[1] + self.height - 138
texture: self.cloud_texture
Rectangle:
size: self.width, 160
pos: self.pos[0],self.pos[1]
texture: self.grass_texture
Rectangle:
size: self.width, 100
pos: self.pos[0]-10,self.pos[1] + 145
texture: self.tree1_texture
Rectangle:
size: self.width, 100
pos: self.pos[0]+30,self.pos[1] + 160
texture: self.tree2_texture
Rectangle:
size: self.width, 20
pos: self.pos[0],self.pos[1] + 155
texture: self.bush_texture
Label:
id: score
size_hint_y : None
height : 96
text: "0"
font_size: 40
Button:
pos_hint:{"x":0,"y":0.25}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.3
text: "Back"
id: set_event
on_release:
<OptionsWindow>:
name: "options"
FloatLayout:
cols:4
Image:
source: 'Flying_whale.png'
allow_stretch: True
keep_ratio: False
Button:
pos_hint:{"x":0.15,"y":0.20}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Back"
on_release:
root.manager.transition.direction = "left"
app.root.current = "start"
Button:
pos_hint:{"x":0.15,"y":0.35}
background_color: 0,0,0,0
color: 1,1,1,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Mode : Night / Day"
The error is given by:
AttributeError: 'super' object has no attribute 'getattr'
I know it is a simple understanding problem but I am not able to work around it. Thank you
The problem is that the id: background is defined in the GameWindow rule in the kv. According to the documentation:
When the kv file is processed, weakrefs to all the widgets tagged with
ids are added to the root widget’s ids dictionary.
While the term root widget is over used and has different meanings depending on context, here it means the rule that contains the id. So the background id will only appear in the ids of the GamWindow widget.
Knowing that, your questionable line of code can be replaced with:
Clock.schedule_interval(self.root.get_screen('game').ids.background.scroll_textures,1/60.)
The above code uses get_screen() to access the GameWindow.

Put Image in the Center of a Button - Kivy

stuck again and looking for assistance.
This time I am trying to center an image on a button along with the text below it.
Here's a snippet from my code so far
Button:
canvas:
Rectangle:
# set rects size, pos = size, pos of the button
size:50,50
pos:self.pos
source:'icons/home.png'
text:'NCERT\nSolutions'
background_normal: ''
background_color: rgba("#FFFFE0")
color:0,0,0,1
halign:'center'
on_release:
app.root.current='flamingowindow'
and this gives the following result
Objective: The icon should be in the middle of the button and the text below it.
I think this a a better solution. This code creates a custom Widget called ImageLabel that consists of an Image with a Label below it. The entire ImageLabel acts as a Button.
from kivy.lang import Builder
from kivy.properties import ListProperty, StringProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
class ImageLabel(ButtonBehavior, BoxLayout):
image_source = StringProperty('')
image_size = ListProperty([50, 50])
text = StringProperty('')
# stuff used by ButtonBehavior
background_color = ListProperty([1, 1, 1, 1])
background_normal = StringProperty(
'atlas://data/images/defaulttheme/button')
background_down = StringProperty(
'atlas://data/images/defaulttheme/button_pressed')
background_disabled_normal = StringProperty(
'atlas://data/images/defaulttheme/button_disabled')
background_disabled_down = StringProperty(
'atlas://data/images/defaulttheme/button_disabled_pressed')
border = ListProperty([16, 16, 16, 16])
Builder.load_string('''
<ImageLabel>:
orientation: 'vertical'
size_hint: None, None
height: self.image_size[1] + label.texture_size[1]
width: max(self.image_size[0], label.texture_size[0])
state_image: self.background_normal if self.state == 'normal' else self.background_down
disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
canvas:
Color:
rgba: self.background_color
BorderImage:
border: self.border
pos: self.pos
size: self.size
source: self.disabled_image if self.disabled else self.state_image
Image:
id: image
source: root.image_source
size: root.image_size
Label:
id: label
halign: 'center'
text: root.text
size: self.texture_size
''')
if __name__ == '__main__':
from kivy.app import App
gl = Builder.load_string('''
FloatLayout:
ImageLabel:
pos_hint: {'center_x':0.5, 'center_y': 0.5}
text: 'NCERT\\nSolutions'
image_source: 'atlas://data/images/defaulttheme/filechooser_folder'
image_size: 100, 100
on_release: app.button_callback()
''')
class ImageLabelTestApp(App):
def build(self):
return gl
def button_callback(self):
print('button pressed')
ImageLabelTestApp().run()
Here is a real hack to TRY to do what you want. Getting the icon where you want is easy to do using a little arithmetic, but positioning the text is a bit of trial and error:
#:set image_height 50
#:set text_height 30
Button:
canvas:
Rectangle:
# sets size, pos of the image
size: image_height, image_height
pos: self.pos[0] + (self.width - image_height)/2, self.pos[1] + self.height - image_height
source: 'icons/home.png'
font_size: text_height / 2.5
text: "\\n\\n\\nNCERT\\nSolutions"
halign: 'center'
size_hint_y: None
height: image_height + text_height
The trial and error involves text_height, the calculation of font_size, and how many newlines to put at the start of the text.

Is there a focus lost event dispatcher for a kivy TextInput?

Problem:
Is there a way to fire an event if a multiline = True TextInput lost its focus?
Background:
I have tried on_touch_up function. But it returns the instances of all my TextInputs, not just the current widget. I tried text_validate_unfocus = False, with the same result.
Code:
import kivy
kivy.require("1.10.1")
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.textinput import TextInput
from kivy.uix.bubble import Bubble
from kivy.lang import Builder
from kivy.storage.jsonstore import JsonStore
Builder.load_string('''
#: import Window kivy.core.window.Window
<Button>:
background_normal: ''
<Label>:
canvas.before:
Color:
rgba: (0,0.59,0.36,1)
Rectangle:
pos: self.pos
size: self.size
<TextInput>:
hint_text: 'Nuwe nota'
font_size: self.height / 4.5 if self.focus else self.height / 3
background_normal: ''
background_active: ''
foreground_color: (0,0.61,0.36,1) if self.focus else (0.71,0.75,0.71,1)
unfocus_on_touch: False
canvas.after:
Color:
rgb: (0,0,0,1)
Line:
points: self.pos[0] , self.pos[1], self.pos[0] + self.size[0], self.pos[1]
size_hint_y: None
height: Window.height / 6 if self.focus else Window.height / 12
<ChoiceBubble>:
orientation: 'horizontal'
size_hint: (None, None)
size: (160, 120)
pos_hint: {'top': 0.2, 'right': 0.8}
arrow_pos: 'top_left'
BubbleButton:
text: 'Save'
BubbleButton:
text: 'Encrypt..'
BubbleButton:
text: 'Delete'
on_release: root.del_txt_input()
<Notation>:
canvas:
Color:
rgba: (0,0.43,0.37,1)
Rectangle:
pos: self.pos
size: self.size
Label:
pos_hint: {'top': 1, 'right': 0.8}
size_hint: [0.8, None]
height: Window.height / 15
Button:
color: (0,0,0,1)
pos_hint: {'top': 1, 'right': 0.9}
size_hint: [0.1, None]
height: Window.height / 15
Image:
source: 'gear_2.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.5, self.parent.height/ 1.5
allow_stretch: True
Button:
color: (0,0,0,1)
pos_hint: {'top': 1, 'right': 1}
size_hint: [0.1, None]
height: Window.height / 15
on_release: root.add_input()
Image:
source: 'plus_text12354.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.5, self.parent.height/ 1.5
allow_stretch: True
ScrollView:
size_hint_y: None
size: Window.width, Window.height
pos_hint: {'top': 0.92, 'right': 1}
GridLayout:
id: text_holder
cols: 1
pos_hint: {'top': 0.92, 'right': 1}
padding: 4
size_hint_x: 1
size_hint_y: None
height: self.minimum_height
''')
class ChoiceBubble(Bubble):
pass
class TextInput(TextInput):
got_txt = ObjectProperty(None)
def on_touch_up(self, touch):
if not self.collide_point(*touch.pos):
self.text_validate_unfocus = False
note = Notation()
note.show_bubble
self.got_txt=note.que_txt_input(self)
return super(TextInput, self).on_touch_up(touch)
class Notation(FloatLayout):
which_txt = ObjectProperty(None)
new_txt = ObjectProperty(None)
cnt = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.the_file=JsonStore('txt_input.json')
self.cnt = self.the_file.count()
lst = self.the_file.keys
def add_input(self):
txt_hld = self.ids.text_holder
self.cnt += 1
self.new_txt = TextInput(id=str(self.cnt))
self.the_file.put(str(self.cnt), the_id=str(self.cnt), the_input='')
txt_hld.add_widget(self.new_txt)
def que_txt_input(self, instance):
self.which_txt = instance
print(instance.text, instance)
return instance
def del_txt_input(self):
print(self.which_txt)
def the_file(self, notestore):
self.notestore = notestore
def show_bubble(self):
self.add_widget(ChoiceBubble())
def get_store(self):
the_keys = list(self.the_file.keys)
print(the_keys)
return the_keys
class theNoteApp(App):
title = 'My Notes'
def build(self):
return Notation()
if __name__ == '__main__':
theNoteApp().run()
Desired Result:
Once the focus is lost I want a bubble widget to be added to the top of my root class. This will give the user the option to save, encrypt or delete the TextInput id and text that just lost its focus, to a JSON file.
Problems
Multiple instances of object, Notation. One from build() method (return Notation()) in App class, and another instance created in on_touch_up() method (note = Notation()) whenever on_touch_up event is fired. Those instances created in on_touch_up() method does not has a visible view i.e. it won't show up in the window.
AttributeError: 'ChoiceBubble' object has no attribute 'del_txt_input'
Text in ChoiceBubble not visible i.e. the default text color is white on white background.
Solutions
Use App.get_running_app().root to get the instantiated root.
Replace root.del_txt_input() with app.root.del_txt_input()
Add color: 0, 0, 0, 1 to class rule, <Label>:
Use on_focus event to display BubbleButton
Snippets - kv
<Label>:
color: 0, 0, 0, 1
...
<ChoiceBubble>:
...
BubbleButton:
text: 'Delete'
on_release: app.root.del_txt_input()
Snippets - py file
class TextInput(TextInput):
got_txt = ObjectProperty(None)
def on_focus(self, instance, value):
if not value: # defocused
note = App.get_running_app().root
note.show_bubble()
self.got_txt = note.que_txt_input(self)
Output
The on_focus event will fire when the focus boolean changes.
I have tried on_touch_up function. But it returns the instances of all my TextInputs, not just the current widget.
This is because you didn't write any code that would limit it to the widget you care about, you can do this if you want to.

Dropdown partially visible in Kivy (Python)

I am trying to create a generic menu bar in Kivy (GUI for Python) and I am having trouble with dropdown menus. They only partially appear and I don't know why (see under Submenu 1):
Here is my code if you want to check it :
#!/usr/bin/env python3
from kivy.app import App
#from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.dropdown import DropDown
from kivy.properties import ListProperty, ObjectProperty
#from kivy.uix.actionbar import ActionBar, ActionView, ActionGroup, ActionButton
class MenuItem(ButtonBehavior, Label):
'''Background color, in the format (r, g, b, a).'''
background_color_normal = ListProperty([0.2, 0.2, 0.2, 1])
background_color_down = ListProperty([0.3, 0.3, 0.3, 1])
background_color = ListProperty()
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
pass
class MenuSubmenu(MenuItem):
# The list of submenu items in dropdown menu
submenu = ObjectProperty(None)
def add_widget(self, submenu, **kwargs):
if isinstance(submenu, MenuDropDown):
self.submenu = submenu
super().add_widget(submenu, **kwargs)
def on_release(self, **kwargs):
super().on_release(**kwargs)
self.submenu.open(self)
class MenuDropDown(DropDown):
pass
class MenuButton(MenuItem):
pass
class MenuBar(BoxLayout):
'''Background color, in the format (r, g, b, a).'''
background_color = ListProperty([0.2, 0.2, 0.2, 1])
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
def __init__(self, **kwargs):
self.itemsList = []
super().__init__(**kwargs)
def add_widget(self, item, index=0):
if not isinstance(item, MenuItem):
raise TypeError("MenuBar accepts only MenuItem widgets")
super().add_widget(item, index)
if index == 0:
index = len(self.itemsList)
self.itemsList.insert(index, item)
class MenuApp(App):
def button(self, nb):
print("Button", nb, "triggered")
if __name__ == '__main__':
MenuApp().run()
And here the kv file :
#:kivy 1.0.9
<MenuItem>:
background_color: self.background_color_down if self.state=='down' else self.background_color_normal
color: (1,1,1,1)
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
rectangle: self.x,self.y,self.width,self.height
<MenuBar>:
size_hint_y: None
height: 40
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
points: self.x, self.y, self.x + self.width, self.y
BoxLayout:
orientation: "vertical"
MenuBar:
MenuSubmenu:
text: "Submenu 1"
MenuDropDown:
Button:
text: "Button 11"
on_release: app.button(11)
Button:
text: "Button 12"
on_release: app.button(12)
Button:
text: "Button 13"
on_release: app.button(13)
MenuButton:
text: "Button 2"
on_release: app.button(2)
MenuButton:
text: "Button 3"
on_release: app.button(3)
Button:
text: "Nothing"
background_color: 0.4, 0.4, 0.4, 1
background_normal: ""
It would be great if you know what happen or if you could redirect me to some place more appropriate to find an answer.
As warned by #inclement I was not using dropdown properly. You should not add directly a dropdown widget in a kv file.
So instead I looked at how was working the actionbar in kivy (explanation here and source code here) and I updated in consequence my menu bar.
Here is then my python code :
#!/usr/bin/env python3
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.dropdown import DropDown
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty, ObjectProperty,\
StringProperty, BooleanProperty, NumericProperty
#from kivy.uix.actionbar import ActionBar, ActionView, ActionGroup, ActionButton
class MenuItem(Widget):
'''Background color, in the format (r, g, b, a).'''
background_color_normal = ListProperty([0.2, 0.2, 0.2, 1])
background_color_down = ListProperty([0.3, 0.3, 0.3, 1])
background_color = ListProperty([])
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
text_color = ListProperty([1,1,1,1])
inside_group = BooleanProperty(False)
pass
class MenuSubmenu(MenuItem, Spinner):
triangle = ListProperty()
def __init__(self, **kwargs):
self.list_menu_item = []
super().__init__(**kwargs)
self.dropdown_cls = MenuDropDown
def add_widget(self, item):
self.list_menu_item.append(item)
self.show_submenu()
def show_submenu(self):
self.clear_widgets()
for item in self.list_menu_item:
item.inside_group = True
self._dropdown.add_widget(item)
def _build_dropdown(self, *largs):
if self._dropdown:
self._dropdown.unbind(on_dismiss=self._toggle_dropdown)
self._dropdown.dismiss()
self._dropdown = None
self._dropdown = self.dropdown_cls()
self._dropdown.bind(on_dismiss=self._toggle_dropdown)
def _update_dropdown(self, *largs):
pass
def _toggle_dropdown(self, *largs):
self.is_open = not self.is_open
ddn = self._dropdown
ddn.size_hint_x = None
if not ddn.container:
return
children = ddn.container.children
if children:
ddn.width = max(self.width, max(c.width for c in children))
else:
ddn.width = self.width
for item in children:
item.size_hint_y = None
item.height = max([self.height, 48])
def clear_widgets(self):
self._dropdown.clear_widgets()
class MenuDropDown(DropDown):
pass
class MenuButton(MenuItem,Button):
icon = StringProperty(None, allownone=True)
pass
class MenuEmptySpace(MenuItem):
pass
class MenuBar(BoxLayout):
'''Background color, in the format (r, g, b, a).'''
background_color = ListProperty([0.2, 0.2, 0.2, 1])
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
def __init__(self, **kwargs):
self.itemsList = []
super().__init__(**kwargs)
def add_widget(self, item, index=0):
if not isinstance(item, MenuItem):
raise TypeError("MenuBar accepts only MenuItem widgets")
super().add_widget(item, index)
if index == 0:
index = len(self.itemsList)
self.itemsList.insert(index, item)
if __name__ == '__main__':
class MenuApp(App):
def button(self, nb):
print("Button", nb, "triggered")
MenuApp().run()
And here is the corresponding file menu.kv :
#:kivy 1.0.9
<MenuButton>:
size_hint_x: None if not root.inside_group else 1
width: self.texture_size[0] + 32
Image:
allow_stretch: True
opacity: 1 if root.icon else 0
source: root.icon
pos: root.x + 4, root.y + 4
size: root.width - 8, root.height - 8
<MenuSubmenu>:
size_hint_x: None
width: self.texture_size[0] + 32
triangle: (self.right-14, self.y+7, self.right-7, self.y+7, self.right-7, self.y+14)
canvas.after:
Color:
rgba: self.separator_color
Triangle:
points: self.triangle
<MenuButton,MenuSubmenu>:
background_normal: ""
background_down: ""
background_color: self.background_color_down if self.state=='down' else self.background_color_normal
color: self.text_color
<MenuEmptySpace>:
size_hint_x: 1
<MenuItem>:
background_color: self.background_color_normal
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
rectangle: self.x,self.y,self.width,self.height
<MenuBar>:
size_hint_y: None
height: 48
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
rectangle: self.x, self.y, self.width, self.height
BoxLayout:
orientation: "vertical"
MenuBar:
MenuButton:
icon: "/home/matthieu/internships/singapore/drawings/joytube_color_small.png"
width: 100
MenuButton:
text: "Button 1"
on_release: app.button(1)
MenuSubmenu:
text: "Submenu 2"
MenuButton:
text: "Button 21"
on_release: app.button(21)
MenuButton:
text: "Button 22"
on_release: app.button(22)
MenuButton:
text: "Button 23"
on_release: app.button(23)
MenuEmptySpace:
MenuSubmenu:
text: "Submenu 3"
MenuButton:
text: "Button 31"
on_release: app.button(31)
Button:
text: "Nothing"
background_color: 0.4, 0.4, 0.4, 1
background_normal: ""
MenuBar:
height: 30
Hope it will help other persons wanting to make menu bars.

Categories