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.
Related
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.
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.
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.
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.
I am trying to center a circle inside a layout. I'm currently doing some padding calculations, but I'm also looking for a better way. I imagine one of the predefined layouts may be a better choice. Here's what my code is producing...
For square layouts:
For wide layouts:
This is the right behavior, which is great, but is there a better way? I can imagine this getting messy with non-circle shapes, for example.
Here's my code:
#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Color, Ellipse, Rectangle
class MinimalApp(App):
title = 'My App'
def build(self):
root = RootLayout()
return(root)
class RootLayout(AnchorLayout):
pass
class Circley(RelativeLayout):
pass
if __name__ == '__main__':
MinimalApp().run()
And the KV:
#:kivy 1.7.2
#:import kivy kivy
<RootLayout>:
anchor_x: 'center' # I think this /is/ centered
anchor_y: 'center'
canvas.before:
Color:
rgba: 0.4, 0.4, 0.4, 1
Rectangle:
pos: self.pos
size: self.size
Circley:
anchor_x: 'center' # this is /not/ centered.
anchor_y: 'center'
canvas.before:
Color:
rgba: 0.94, 0.94, 0.94, 1
Ellipse:
size: min(self.size), min(self.size)
pos: 0.5*self.size[0] - 0.5*min(self.size), 0.5*self.size[1] - 0.5*min(self.size)
Label:
text: unicode(self.size) # this is /not/ appearing
color: 1,0,0,1
Snippet using FloatLayout, size_hint and pos_hint:
from kivy.app import App
from kivy.lang import Builder
kv = '''
FloatLayout:
Widget:
size: min(root.size), min(root.size)
size_hint: None, None
pos_hint: {'center_x': .5, 'center_y': .5}
canvas:
Color:
rgb: 1, 0, 0
Ellipse:
size: self.size
pos: self.pos
'''
Builder.load_string(kv)
class MyApp(App):
def build(self):
return Builder.load_string(kv)
MyApp().run()
Flag of Japan:
from kivy.app import App
from kivy.lang import Builder
kv = '''
FloatLayout:
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
size: self.size
pos: self.pos
Widget:
size: min(root.size)/2, min(root.size)/2
size_hint: None, None
pos_hint: {'center_x': .5, 'center_y': .5}
canvas:
Color:
rgb: 1, 0, 0
Ellipse:
size: self.size
pos: self.pos
'''
Builder.load_string(kv)
class MyApp(App):
def build(self):
return Builder.load_string(kv)
MyApp().run()