from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.effects.scroll import ScrollEffect
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class BSGameMain(BoxLayout):
blmain = BoxLayout(orientation = 'vertical') # MainBoxLayout init
scrlFBtns = ScrollView(effect_cls = 'ScrollEffect')
blbtns = BoxLayout(
orientation = 'vertical',
size_hint_y = None
) # BoxLayout for buttons
blbtns.bind(minimum_height = blbtns.setter('height'))
scrlFBtns.add_widget(blbtns)
for i in range (2):
blbtns.add_widget(Button(
text='asd',
size_hint_y = None,
height = 40
))
lblmain = Label(text = 'asd')
blmain.add_widget(lblmain)
blmain.add_widget(scrlFBtns)
class BSApp(App):
def build(self):
game = BSGameMain()
return game.blmain
if __name__ == "__main__":
BSApp().run()
It is necessary to make the buttons appear from below, and not from the middle, taking into account the scrollview. I just can't make him do it.
Please keep in mind that BoxLayouts override modifications to positioning, as they automatically position their children. This makes them pretty self-sufficient but a little stubborn to work with.
Like you said, if there's only a couple buttons, it sticks to the middle. The scrollview works, but since it's a child of a boxlayout, it can only stretch as far as what the boxlayout says it can, which is the bottom half of the window. Also, since the boxlayout for the buttons adds children top-down, this is why your buttons appear in the middle. In reality, it's adding buttons to the top of the bottom half of the window.
You have specified boxlayouts where boxlayouts are not necessary. One thing you might want is to position your scrollview at will, depending on the number of buttons in the scrollview. This will require an if statement and an integer.
My changes:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.effects.scroll import ScrollEffect
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class BSGameMain(): #No longer a boxlayout.
scroll_height = 0;
blmain = Widget() #No longer a boxlayout. Fills the window.
blbtns = BoxLayout(
orientation = 'vertical',
size_hint = (1, None),
) # BoxLayout for buttons
blbtns.bind(minimum_height = blbtns.setter('height'))
for i in range (20): #number of buttons printed
blbtns.add_widget(Button(
text='asd',
size_hint_y = None,
height = 40
))
if (scroll_height < (Window.height / 2)):
scroll_height = scroll_height + 40 #This measures how much space we need for the scrollview. Make sure it matches button height!
scrlFBtns = ScrollView(effect_cls = 'ScrollEffect', pos = (0, 0), size = (Window.width, scroll_height))
#The pos of 0,0 ensures scrlFBtns stays at the bottom. Place at negative coordinates if you want it to extend off-screen.
scrlFBtns.add_widget(blbtns)
lblmain = Label(text = 'asd', halign = 'center', y = (Window.height * 0.75), width = Window.width, color = (1,1,1,1))
blmain.add_widget(lblmain)
blmain.add_widget(scrlFBtns)
class BSApp(App):
def build(self):
game = BSGameMain()
return game.blmain
if __name__ == "__main__":
BSApp().run()
Results:
One Button
,
Three Buttons
,
Twenty Buttons
Hopefully this is along the lines of what you're looking for!
Related
I am new to Kivy and I am sort of stuck. I am using GridLayout to make my app and I am having some trouble putting a video in my background. The code I will post makes an app with a black background. How do I replace the black background with a video, particularly mp4. I would also like to make the video darker if possible. I wanted to use AnchorPoint but I am not quite sure how to put both in there. Any help will be appreciated.
from kivy.app import App
from kivy.uix.video import Video
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class MZ_Invest(App):
def build(self):
self.root_layout = FloatLayout()
self.window = GridLayout()
self.window.cols = 1
self.window.size_hint = (0.6,0.7)
self.window.pos_hint = {"center_x":0.5, "center_y":0.5}
#add widgets
#Video
video = Video(source='birds.mp4', state='play')
video.opacity = 0.5
#Image
self.window.add_widget(Image(
source="mzi.png",
size_hint = (1.5,1.5)
))
#Label
self.greeting = Label(
text="How much would you like to invest?",
font_size = 18,
color='90EE90'
)
self.window.add_widget(self.greeting)
#User Input
self.user = TextInput(
multiline=False,
padding_y= (20,20),
size_hint = (1, 0.5)
)
self.window.add_widget(self.user)
#Button
self.button = Button(
text="Submit",
size_hint = (1,0.5),
bold = True,
background_color = '90EE90',
)
self.button.bind(on_press=self.callback)
self.window.add_widget(self.button)
#self.root_layout.add_widget(video)
self.root_layout.add_widget(self.window)
return self.root_layout
def callback(self, instance):
if self.user.text.isnumeric() and int(self.user.text) >= 10000:
self.greeting.text = "Calculating: $" + self.user.text
self.greeting.color = '90EE90'
else:
self.greeting.text = "Invalid"
self.greeting.color = "#FF0000"
if __name__ == "__main__":
MZ_Invest().run()
Most Kivy widgets have a transparent background, so you can just display a video and then display your GUI over that. Try adding something like this at the end of your build() method:
self.root_layout = FloatLayout()
video = Video(source='BigBuckBunny.mp4', state='play')
video.opacity = 0.5 # adjust to make the video lighter/darker
self.root_layout.add_widget(video) # add the video first
self.root_layout.add_widget(self.window) # now add the GUI window so it will be drawn over the video
return self.root_layout # return the root_layout instead of the window
BTW, thanks to everyone at stackoverflow for being so supportive.
Thanks to crystal clear help from 'John Anderson' I've learned a megaton about using splitters, layouts & widgets in Kivy.
I've achieved the desired 'look' of my GUI thus far, but ran into an odd quirk that eludes me. My buttons stopped accepting clicks.
If one looks closely at the depiction of my GUI below, the buttons named 'White' & 'Black' have a line inside them through the text. When I pull the horizontal splitter bar down enough, the lines in the button texts move as well until they disappear causing my buttons to accept clicks again. I know it must have something to do with the layouts, but don't see how to fix it. Can someone explain what's going on?
Side note:--The positioning of widgets inside layouts within splitters is more convoluted than expected since any adjustments of size_hints & pos_hints, spacing and padding affect each other.
Here's my GUI:
Here's the code:
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.splitter import Splitter
from kivy.uix.image import Image
kivy.require('2.0.0')
class ChessBoardWidget(RelativeLayout):
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
# adjust where the left side of vertical layout starts by changing 'center_x'
repertoire_boxlayout_vert = BoxLayout(orientation='vertical', size_hint_y=.05,
pos_hint={'center_x': .774}) # >center_x moves right
# Padding between layout box and children: [padding_left, padding_top, padding_right, padding_bottom]
# Padding puts space between widgets and the edge of layout holding the widgets
# Spacing puts space between the widgets inside a layout
repertoire_boxlayout_horz = BoxLayout(orientation='horizontal', size_hint=(.45, .05),
spacing=10, padding=[0, 0, 0, 30])
repertoire_boxlayout_horz.add_widget(Label(text='Repertoire for:', size_hint=(.08, 1)))
repertoire_boxlayout_horz.add_widget(Button(text='White', size_hint=(.04, 1)))
repertoire_boxlayout_horz.add_widget(Button(text='Black', size_hint=(.04, 1)))
repertoire_boxlayout_vert.add_widget(repertoire_boxlayout_horz)
chessboard_gui_boxlayout = BoxLayout(orientation='vertical', spacing=40)
chessboard_gui_boxlayout.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", pos=self.pos, keep_ratio=True,
allow_stretch=True)) # default size_hint of (1,1) claims all of remaining height
chessboard_gui_boxlayout.add_widget(repertoire_boxlayout_vert)
self.add_widget(chessboard_gui_boxlayout)
class SplitterGui(BoxLayout):
def __init__(self, **kwargs):
super(SplitterGui, self).__init__(**kwargs)
self.orientation = 'horizontal'
# Splitter 1
split1_boxlayout = BoxLayout(orientation='vertical')
split1 = Splitter(sizable_from='bottom', min_size=74,
max_size=1100, size_hint=(1, .8)) # size_hint=(..,y is smaller, bar moves up
chessboard_widget = ChessBoardWidget()
split1.add_widget(chessboard_widget)
split1_boxlayout.add_widget(split1)
s3_button = Button(text='s3', size_hint=(1, 1))
split1_boxlayout.add_widget(s3_button)
self.add_widget(split1_boxlayout)
# Splitter 2
split2 = Splitter(sizable_from='left', min_size=74,
max_size=1800, size_hint=(3.33, 1)) # size_hint=(x is larger, bar moves left
s2_button = Button(text='s2', size_hint=(.1, 1))
split2.add_widget(s2_button)
self.add_widget(split2)
class ChessBoxApp(App):
def build(self):
return SplitterGui() # root
if __name__ == '__main__':
ChessBoxApp().run()
The problem is that your repertoire_boxlayout_horz with its size_hint, spacing and padding settings leaves no space for the Buttons and Label. So those three widgets end up with a height of 0 and that results in mouse click calculations saying that the clicks do not occur on those widgets. Even though those three widgets have a height of 0, The text and background is still drawn for each. Possible fixes are to eliminate the spacing and padding settings, or set size_hint_y to None for the repertoire_boxlayout_horz with a specified height that allows for those settings, or switch to kv to define your GUI where you can use the minimum_height property of BoxLayout.
I'm trying to output text from button presses and add it to a textbox each time it's pressed as well as delete the text when the delete button is pressed - in normal python.
I've been trying to use kivy.uix.textinput, but I'm not sure on how to output the value from the buttons as well as delete it.
This is what I've done so far.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class CodeScreen(Screen):
def __init__(self):
super(CodeScreen, self).__init__(name='code_screen')
main_grid_lay = GridLayout(cols=2,cols_minimum={0:640, 1:175})
self.add_widget(main_grid_lay)
#Code ouput display
display_code = TextInput(text='Code!', readonly = True, id=output)
main_grid_lay.add_widget(display_code)
#Options Buttons
box_lay = BoxLayout(orientation='vertical')
main_grid_lay.add_widget(box_lay)
delete_button = Button(
text='Delete',
size_hint_x= None,
width=160,
id='' #unsure on how to delete
)
box_lay.add_widget(delete_button)
base_left = Button(
text='Base Left',
#on_release= b_left(),
#id='left',
)
base_right = Button(
text='Base Right',
on_release=self.degree_popup,
#on_release= b_right(),
)
#b_left = self.ids.output.text='left'
#b_left = self.ids.output.text='right'
box_lay.add_widget(base_left)
box_lay.add_widget(base_right)
# The app class
class MyMain(App):
def build(self):
return CodeScreen()
# Runs the App
if __name__ == '__main__':
MyMain().run()
It currently sends an error and is probably because of the ids. Not fully sure how ids work without using kv language. Any help is appreciated.
The id only makes sense in .kv, in python they are useless. In this case the solution is to access the objects in the methods connected to on_release but for this they must be class attributes.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class CodeScreen(Screen):
def __init__(self):
super(CodeScreen, self).__init__(name="code_screen")
self.display_code = TextInput(text="Code!", readonly=True)
self.delete_button = Button(
text="Delete", size_hint_x=None, width=160, on_release=self.delete_clicked
)
self.base_left = Button(text="Base Left", on_release=self.left_clicked)
self.base_right = Button(text="Base Right", on_release=self.right_clicked)
main_grid_lay = GridLayout(cols=2, cols_minimum={0: 640, 1: 175})
main_grid_lay.add_widget(self.display_code)
self.add_widget(main_grid_lay)
box_lay = BoxLayout(orientation="vertical")
box_lay.add_widget(self.delete_button)
main_grid_lay.add_widget(box_lay)
box_lay.add_widget(self.base_left)
box_lay.add_widget(self.base_right)
def delete_clicked(self, instance):
self.display_code.text = ""
def left_clicked(self, instance):
self.display_code.text += "left"
def right_clicked(self, instance):
self.display_code.text += "right"
# The app class
class MyMain(App):
def build(self):
return CodeScreen()
# Runs the App
if __name__ == "__main__":
MyMain().run()
I want to create an Accordion containing content consisting of an RstDocument and a button. The Accordion shall be scrollable as well as the content of the RstDocument when this content is larger than the given space. So I came up with the following code but after some clicks on AccordionItems all further interaction is blocking. What am I doing wrong here?
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.rst import RstDocument
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.button import Button
class ShowrstApp (App):
def update_size(self, instance, *args):
instance.size = 60 * len(instance.children)
def build (self):
numitems = 10
root = BoxLayout()
accheight = numitems * 60
accitems = Accordion(id='acc_panel', orientation='vertical', pos_hint={'top': 1}, size_hint_y=None,
height=accheight, md_bg_color=(1, 1, 1, 1))
for i in xrange(numitems):
item = AccordionItem(title='This is item: %d' % i)
somecontent = BoxLayout(orientation='vertical')
somecontent.add_widget(RstDocument(text='Some nicely formatted text here'))
somecontent.add_widget(Button(text='click here', height=(42), size_hint=(1,None)))
item.add_widget(somecontent)
item.bind(children=self.update_size)
accitems.add_widget(item)
sv = ScrollView(do_scroll_x = False)
sv.add_widget(accitems)
root.add_widget(sv)
return root
Window.size = (350,650)
showrst = ShowrstApp()
showrst.run()
Below the scrolling effect works when the RstDocument Boxlayout is either horizontal of vertical, but an issue I saw was that when the BoxLayout was set to vertical, the toggle of each item was mute, you had to go one by one from the bottom-up. This was weird. You can click on each AccordionItem, where the RstDocument is not. This should be a great starting point. Noticed this effect doesn't occur when using a Label, so that could be a another option.
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.rst import RstDocument
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.button import Button
class ShowrstApp (App):
def update_size(self, instance, *args):
instance.height = 100 * len(instance.children) # set Accordion height to the number of accordionItem times the height accordionItem height
def build (self):
numitems = 10
root = BoxLayout()
accheight = numitems * 60
accitems = Accordion(id='acc_panel', orientation='vertical', size_hint_y=None, pos_hint={'top':1}
height=accheight, md_bg_color=(1, 1, 1, 1))
for i in xrange(numitems * 2): # *2 to show it works
item = AccordionItem(title='This is item: %d' % i)
somecontent = BoxLayout(orientation = 'horizontal') # couldn't solve an issue I notice so I used horizontal
somecontent.add_widget(RstDocument(text='Some nicely formatted text here' * 10))
somecontent.add_widget(Button(text='click here', height=(42), size_hint=(1,None)))
item.add_widget(somecontent)
accitems.bind(size=self.update_size)
accitems.add_widget(item)
sv = ScrollView(do_scroll_x = False)
sv.add_widget(accitems)
root.add_widget(sv)
return root
Window.size = (350,650)
showrst = ShowrstApp()
showrst.run()
I was having the same issue with RstDocuments in a ScrollView. The problem arises because RstDocuments have their own scrolling and 'intercept' the scroll command because it thinks you're trying to scroll inside the RstDocument. If you're fine with non-scrolling RstDocuments, you can set do_scroll_y: False for RstDocument, and scrolling will then work fine in the ScrollView regardless of mousing over an RstDocument.
I'm something of a newbie to Python and Kivy. After making some progress, I've hit a brick wall and no amount of internet searching can find an answer.
I have a python/kivy script which starts with a GridLayout selection menu. Then I would like to "Click Next" and replace the GridLayout with a BoxLayout to display the output. The Python script is:
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.gridlayout import GridLayout
from kivy.uix.pagelayout import PageLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import StringProperty, ListProperty, DictProperty
from kivy.uix.widget import Widget
from brp_stats import *
from dice_roller import *
#from display import *
race = ''
statblock = ''
class Test(GridLayout):
def printcharacter(self,my_sb,my_cr,my_scm,my_scb):
printable_stats = print_stats(my_sb)
printable_rolls = print_rolls(my_cr)
printable_scm = print_scm(my_scm)
printable_scb = print_scb(my_scb)
self.clear_widgets()
self.add_widget(Label(text_size=(300, None),
text='Stats\n' + str(printable_stats)))
self.add_widget(Label(text_size=(300, None),
text='Rolls\n' + str(printable_rolls)))
self.add_widget(Label(text_size=(300, None),
text='SCM\n' + str(printable_scm)))
self.add_widget(Label(text_size=(300, None),
text='SCB\n' + str(printable_scb)))
wayout = Button(text='Way Out')
self.add_widget(wayout)
wayout.bind(on_press=self.canvas.clear)
# def bar():
# print ("BAR")
def human(self,a,b):
if b==True:
self.Status="human"
race=self.Status
statblock = human()
characteristic_rolls = rolls(statblock)
skill_category_modifiers = scm(statblock)
skill_category_bonuses = scb(statblock)
# TestApp.foo()
# Test.bar()
Test.printcharacter( \ self,statblock,characteristic_rolls,skill_category_modifiers,skill_category_bonuses)
class TestApp(App):
# def foo():
# print ("FOO")
def build(self):
self.title="BRP Character Generator"
return Test()
#### MAIN CODE ####
if __name__ == '__main__':
TestApp().run()
And the KV script is
<Test>:
cols: 2
canvas:
Color:
rgb: .2,.2,.2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'Human'
CheckBox:
group: 'race_group'
on_active: root.human(*args)
Button:
text: 'Next'
on_press: printcharacter()
What's happening is I select an option (Human in this example). It should wait until I click Next before displaying the results. However, as soon as I select Human, it immediately prints the result, in a GridLayout. Two questions spring to mind, which I'm hoping the experts here can help with:
1) Why does the Select screen not wait until I have clicked Next before displaying the results?
2) How do I swap the layout from Grid to Box when I display the second screen?
Any pointers or suggestions would be gratefully received.
Regards,
Colin
Your Checkbox calls Test.human(*args) when it is active (pressed/selected), and the last line of Test.human() calls Test.printcharacter() which explains why it is printing before you hit the 'Next' button.
Since your Test class inherits from GridLayout the widgets you add to a root Test layout will be added as if to a GridLayout. There are several ways to handle switching the layout to a BoxLayout, one of which would be to not inherit directly from a particular Layout class and instead add/remove different Layout widgets to Test as needed.
For example, Test could start off with a GridLayout child widget that is the parent of the Label, CheckBox, and Button widgets. Then when you want to switch layouts for printing your results, you clear those widgets and start by adding a BoxLayout to Test.