How to remove a specific Rectangle in Kivy? - python

I want to know how to remove a specific Rectangle object in Kivy. I create the Rectangle in .py file by pressing a button and I want to the second button could be able to remove that specific Rectangle.
My .py code:
import kivy
kivy.require("1.10.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.app import App
from kivy.graphics import RoundedRectangle
class Screen1(Screen):
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg")
def remove_on_press(self):
pass #I don't know what to write there
class Test(App):
def build(self):
Builder.load_file("Test.kv")
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
return sm
Test().run()
And the .kv file:
#: kivy 1.10.1
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
pos: (root.width - 600) / 2, 800
size: 600, 200
text: "Create rectangle"
on_press: scr1.create_on_press()
pos_hint: {'width': 0.5, 'top': 0.8}
size_hint: None, None
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press
pos_hint: {'width': 0.5, 'top': 0.2}
size_hint: None, None
Thanks for any help.
I tried to use self.parent.remove_widget, but it removed the whole Screen1. However, I want to remove only this Rectangle.

One of the various ways can be adding a group attr. and remove that group later. Since RoundedRectangle is inherited from class Instruction, you can set its attr. group and later use method remove_group to remove that particular group as follows,
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg", group = u"rect")
def remove_on_press(self):
self.canvas.remove_group(u"rect")
Also an obvious change you need in .kv,
...
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press() # Call the function.
...

Thanks, it finally works. My .py:
import kivy
kivy.require("1.10.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.app import App
from kivy.graphics import RoundedRectangle
class Screen1(Screen):
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg", group = u"rect")
def remove_on_press(self):
self.canvas.remove_group(u"rect")
class Test(App):
def build(self):
Builder.load_file("Test.kv")
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
return sm
Test().run()
And the .kv:
#: kivy 1.10.1
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
pos: (root.width - 600) / 2, 800
size: 600, 200
text: "Create rectangle"
on_press: scr1.create_on_press()
pos_hint: {'width': 0.5, 'top': 0.8}
size_hint: None, None
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press()
size_hint: None, None
Thanks everyone for answer.

Related

Kivy - Calling a pulsing method from one class to another

I have the following classes in my kivy app, and i would like to call the blink method in my mainApp class. The start pulsing and blink methods enable the MainWindow background to pulse. However it's not working inside the MainWindow class and i need to call it in my mainApp class. Commented out (build method in mainApp) is what i tried, which results to the error Exception: Invalid instance in App.root. My python file:
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.properties import ColorProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from plyer import filechooser
data = ""
class MainWindow(Screen):
def analyze_data(self):
global data
data = self.ids.user_input.text
data = analyze(data)
animated_color = ColorProperty()
pulse_interval = 4
def blink(self):
x = Clock.schedule_once(self.start_pulsing, 5)
return x
def start_pulsing(self, *args):
d = self.pulse_interval / 2
anim = Animation(animated_color=(69/255, 114/255, 147/255, 1), duration=d) + Animation(animated_color=(1, 1, 1, 1), duration=d)
anim.repeat = True
anim.start(self)
class OutputScreen(Screen):
def on_enter(self, *args):
self.ids.output_label.text = data
class mainApp(MDApp):
def __init__(self):
super().__init__()
def choose_file(self):
try:
filechooser.open_file(on_selection = self.handle_selection)
except:
pass
def handle_selection(self,selection):
global path
selection_ls = selection[0]
path = selection_ls
#print(path)
def change_screen(self,screen):
screemanager = self.root.ids['screenmanager']
screemanager.current = screen
def change(self):
self.change_screen('output')
def back(self):
self.change_screen('main')
'''
def build(self):
x = MainWindow().blink()
return x'''
and my kv file:
#:import utils kivy.utils
GridLayout:
cols:1
ScreenManager:
id: screenmanager
MainWindow:
id: main
name: 'main'
OutputScreen:
id: output
name: 'output'
<MainWindow>:
BoxLayout:
orientation:'vertical'
MDBottomNavigation:
panel_color: utils.get_color_from_hex("#ffffff")
MDBottomNavigationItem:
name:'analytics'
text:'analytics'
icon:'account-circle'
FloatLayout:
size: root.width, root.height
canvas.before:
Color:
rgba: root.animated_color
Rectangle:
pos:self.pos
size:self.size
TextInput:
multiline:True
id: user_input1
pos_hint:{"x" : 0.05, "top" : 0.9}
size_hint: 0.9, 0.37
Label:
markup: True
id:input_label
pos_hint:{"x" : 0, "top":1}
size_hint: 1 ,0.08
font_size : 32
bold: True
canvas.before:
Color:
rgb: utils.get_color_from_hex("01121c")
Rectangle:
size: self.size
pos: self.pos
Button:
pos_hint:{"top" : 0.51, "x" : 0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Submit'
on_press: root.analyze_data()
on_release: app.change()
Button:
pos_hint:{"top":0.42, "x":0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Upload'
on_release:app.choose_file()
Button:
id:'info_button'
pos_hint:{"top":0.47, "x":0.8}
size_hint: (None,None)
width : 50
height : 22
font_size : 23
text:'i'
on_release:root.analytics_info()
<OutputScreen>:
ScrollView:
GridLayout:
cols:1
MDIconButton:
icon:'arrow-left'
pos_hint:{'top':1,'left':1}
size_hint: 0.1,0.1
user_font_size : '64sp'
on_release: app.back()
Label:
id: output_label
multiline:True
text_size: self.width, None
size_hint: 1, None
height: self.texture_size[1]
color: 0,0,0,1
padding_x: 15
Any help will be much appreciated.
The build() method of an App should return a Widget that will become the root of the App. But your build() method returns a ClockEvent (the return from Clock.schedule_once()). Try changing your build() method to:
def build(self):
x = MainWindow()
x.blink()
return x
Since you do not call Builder.load_file(), I assume that your kv file is named main.kv, and therefore will get loaded automatically. If that is true, then you do not need a build() method at all. Instead add an on_start() method to your mainApp class, like this:
def on_start(self):
self.root.ids.main.blink()

How to run a function several times but run the instruction list only once?

I made a program with several buttons and I wish that when I press the button several times, the list of instructions only launches once.
I tried to do it with a global variable but when I want to use the same variable for both screens, I can’t do it.
I know I can do it using two global variables but I would like to use the same one in both screens.
How, when we get to the second screen, u takes the value "True"? Or is there a simpler solution with a decorator?
Here is my code:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
import random
from kivy.properties import ObjectProperty
from kivy.properties import ListProperty, StringProperty
kivy.uix.screenmanager.FadeTransition
kv = """
#: import NoTransition kivy.uix.screenmanager.NoTransition
#:import Clock kivy.clock.Clock
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import RiseInTransition kivy.uix.screenmanager.RiseInTransition
#: import CardTransition kivy.uix.screenmanager.CardTransition
MyScreenManager:
transition: RiseInTransition()
Question1:
name: "question1"
Question2:
name: "question2"
<Question1>:
label_wid : ratio
FloatLayout:
Button:
text: "+1"
pos: 270, 300
size_hint: .30, .10
background_color: 0,1,0,0.75
on_press:
root.mauvais()
Button:
text: "following"
pos: 270, 240
size_hint: .30, .10
background_color: 0,1,0,0.75
on_press:
Clock.schedule_once(root.suite, 0.75)
Label:
id: ratio
text: root.manager.theText
pos: 280,270
font_size: 17
color: 0,0,1,0.65
<Question2>:
label_wid2: ratio
FloatLayout:
Button:
text: "+1"
pos: 270, 300
size_hint: .30, .10
background_color: 0,1,0,0.75
on_press:
root.mauvais()
Label:
id: ratio
text: root.manager.theText
pos: 280,270
font_size: 17
color: 0,0,1,0.65
"""
t=0
m=True
class MyScreenManager(ScreenManager):
theText = StringProperty('')
class Question1(Screen):
m= True
def mauvais(self):
global m
if m==True:
global t
t=t+1
self.manager.theText = str(t)
m=False
def suite(root,text):
root.manager.current = "question2"
pass
class Question2(Screen):
global m
m= True
def mauvais(self):
global m
if m==True:
global t
t=t+1
self.manager.theText = t
m=False
pass
class Quizz(App):
def build(self):
self.title = 'Quizz'
Window.clearcolor = (0, 1, 1, 0.25)
return Builder.load_string(kv)
if __name__ == '__main__':
Quizz().run()
So, since I didn't get an answer to my question, I have a proposed solution that does both possibilities:
import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty, BooleanProperty, NumericProperty
kivy.uix.screenmanager.FadeTransition
kv = """
#: import NoTransition kivy.uix.screenmanager.NoTransition
#:import Clock kivy.clock.Clock
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import RiseInTransition kivy.uix.screenmanager.RiseInTransition
#: import CardTransition kivy.uix.screenmanager.CardTransition
MyScreenManager:
transition: RiseInTransition()
Question1:
name: "question1"
Question2:
name: "question2"
<Question1>:
label_wid : ratio
FloatLayout:
Button:
text: "+1"
pos: 270, 300
size_hint: .30, .10
background_color: 0,1,0,0.75
on_press:
root.manager.mauvais() # method is now in MyScreenManager
Button:
text: "following"
pos: 270, 240
size_hint: .30, .10
background_color: 0,1,0,0.75
on_press:
Clock.schedule_once(root.suite, 0.75)
Label:
id: ratio
text: root.manager.theText
pos: 280,270
font_size: 17
color: 0,0,1,0.65
<Question2>:
label_wid2: ratio
FloatLayout:
Button:
text: "+1"
pos: 270, 300
size_hint: .30, .10
background_color: 0,1,0,0.75
on_press:
root.manager.mauvais() # method is now in MyScreenManager
Label:
id: ratio
text: root.manager.theText
pos: 280,270
font_size: 17
color: 0,0,1,0.65
"""
# time to ignore additional clicks
# if this is zero, ignore all additional clicks
CLICK_IGNORE_TIME = 3
class MyScreenManager(ScreenManager):
theText = StringProperty('')
m = BooleanProperty(True)
t = NumericProperty(0)
def mauvais(self):
if self.m==True:
self.t += 1
self.theText = str(self.t)
self.m=False
if CLICK_IGNORE_TIME > 0:
Clock.schedule_once(self.reset_m, CLICK_IGNORE_TIME)
else:
print('ignored click')
def reset_m(self, dt):
print('reset m')
self.m = True
class Question1(Screen):
def suite(root,text):
root.manager.current = "question2"
class Question2(Screen):
pass
class Quizz(App):
def build(self):
self.title = 'Quizz'
Window.clearcolor = (0, 1, 1, 0.25)
return Builder.load_string(kv)
if __name__ == '__main__':
Quizz().run()
This code moves the mauvais() method to the MyScreenManager class, since both versions were identical.
The CLICK_IGNORE_TIME is how long to ignore additional clicks. If it is zero all additional clicks will be ignored

Making part of the screen a scrollable image with kivy

I'm trying to get a part of the screen a scrollable image, so it will fit in the screen without ruining the image ratio (referring to notebook.jpg in the code). I saw some comments that suggested using ScrollView, but I couldn't really figure out how to add it to the existing class I already have (I mean as a second class in addition to NotebookScreen, so NotebookScreen will be able to use it).
Would really appreciate some help :)
Python code:
import kivy
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
GUI = Builder.load_file('style.kv')
# Window.size = (2224, 1668)
class NotebookScreen(GridLayout):
def __init__(self, **kwargs):
self.rows = 1
super(NotebookScreen, self).__init__(**kwargs)
class MainApp(App):
def build(self):
return NotebookScreen()
if __name__ == "__main__":
MainApp().run()
kivy code:
<NotebookScreen>
FloatLayout:
rows: 2
GridLayout:
size_hint: 1, .05
pos_hint: {"top": 1, "left": 1}
id: tool_bar
cols: 1
canvas:
Color:
rgba: 0, 0, 1, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
id: notebook_grid
size_hint: 1, .95
pos_hint: {"top": .95, "left": 0}
cols: 1
Image:
id: notebook_image
source: 'images/notebook.jpg'
allow_stretch: True
keep_ratio: True
pos: self.pos
size_hint: 1, 1
Here is a quick and dirty example of how you could do that. I simply used a Label's canvas to draw the image. I added the label to the scrollview and added scrollview along with another label to show you that you don't need to have the entire screen for your scrolling part. I only used PIL to get the size of the image, because I wanted to make sure that your window is smaller than the image you want to scroll. I hope it helps you with your approach.
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.graphics import Rectangle
from PIL import Image
class MyApp(App):
def build(self):
img = Image.open("path/to/your/image/dummy.jpg")
Window.size = (img.size[0]*0.8, img.size[1]*1.2)
layout = BoxLayout(orientation="vertical", size_hint=(None, None), size=Window.size)
lbl = Label(text="This is your picture!", size_hint=(None, None), size=(Window.width, Window.height*0.5))
layout.add_widget(lbl)
sv = ScrollView(size_hint=(None, None), size=(Window.width, Window.height*0.5))
img_box = Label(size_hint=(None, None), size=img.size)
with img_box.canvas:
Rectangle(source="path/to/your/image/dummy.jpg", size=img.size)
sv.add_widget(img_box)
layout.add_widget(sv)
return layout
MyApp().run()
[EDIT]
Here is basically the same thing I made at first, but with some outsourcing to a kv string. I hope this is now, what you are looking for.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.lang.builder import Builder
from PIL import Image
kv = """
#:import Window kivy.core.window.Window
<MyImageView>:
orientation: "vertical"
size_hint: None, None
size: Window.size
Label:
text: "This is your picture!"
size_hint: None, None
size: Window.width, Window.height * .5
ScrollView:
size_hint: None, None
size: Window.width, Window.height * .5
Label:
id: img_box
size_hint: None, None
size: root.img.size
canvas:
Rectangle:
source: root.img_path
size: root.img.size
"""
class MyImageView(BoxLayout):
def __init__(self, img_path, **kwargs):
self.img_path = img_path
self.img = Image.open(self.img_path)
Window.size = (self.img.size[0] * 0.8, self.img.size[1] * 1.2)
super(MyImageView, self).__init__(**kwargs)
class MyApp(App):
def build(self):
Builder.load_string(kv)
layout = MyImageView("Path/to/your/image.png")
return layout
MyApp().run()

How to Change BG Color of Dynamically Created Widget with On_Press and Save with Pickle? (Python with Kivy)

Goal:
Change background color of dynamically created widget with on-press.
Save this state with pickle such that when I open the program back up, the new color change is preserved
Note: You'll see in my code that I haven't made an attempt on saving the button bg color state to a file yet, as I'm still trying to get the on-press event to function.
I get the following error:
File "C:/Users/phili/scrollablelabelexample.py", line 45, in create_button
button_share.bind(on_press = self.update_buttons_departoverride(self))
TypeError: update_buttons_departoverride() takes 1 positional argument but 2 were given
Python code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.clock import Clock
import pandas as pd
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class AnotherScreen(Screen):
pass
class BackHomeWidget(Widget):
pass
class Sequence(Screen):
pass
class ScreenManagement(ScreenManager):
pass
class MainScreen(Screen):
pass
class CleanScreen(BoxLayout):
def __init__(self, **kwargs):
super(CleanScreen, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(lambda *args:self.create_button(self.ids.box_share))
def create_button(self, box_share):
top_button_share = 1.1
color = [.48,.72,.23,1]
for i in range(len(parts)):
top_button_share -= .4
button_share = Button(background_normal = '', background_color = color, id = "part"+str(i+1),pos_hint={"x": 0, "top": top_button_share}, size_hint_y=None, height=60, text=str(i))
button_share.bind(on_press = self.update_buttons_departoverride(self))
box_share.add_widget(button_share)
def update_buttons_departoverride(self):
self.background_color = 1.0, 0.0, 0.0, 1.0
presentation = Builder.load_file("garagemainexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
Kv Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
Sequence:
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "sequence"
text: "Sequence"
pos_hint: {"x":0.25, "top": 0.4}
<CleanScreen>:
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 90
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<Sequence>:
name: "sequence"
CleanScreen:
id: cleanscreen
BackHomeWidget:
With button_share.bind (on_press = self.update_buttons_departoverride (self)) you're calling the method, so you're trying to bind on_press with None (self.update_buttons_departoverride return None). If you want to pass arguments, use lambda or functools.partial:
from functools import partial
button_share.bind(on_press=partial(self.update_buttons_departoverride, arg1,...))
However, if you need to pass only the button's reference, it is already passed automatically. You just have to do:
button_share.bind(on_press=self.update_buttons_departoverride)
and:
def update_buttons_departoverride(self, button):
To store the configuration of your widgets you can use Storage. A simplified example using DictStore:
main.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.storage.dictstore import DictStore
class AnotherScreen(Screen):
pass
class BackHomeWidget(Widget):
pass
class Sequence(Screen):
pass
class ScreenManagement(ScreenManager):
pass
class MainScreen(Screen):
pass
class CleanScreen(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf.dat')
def __init__(self, **kwargs):
super(CleanScreen, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
top_button_share = 1.1
color = (.48, .72, .23, 1)
for i in range(5):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
text=str(i))
button_share.bind(on_press=self.update_buttons_departoverride)
self.box_share.add_widget(button_share)
def update_buttons_departoverride(self, button):
button.background_color = 1.0, 0.0, 0.0, 1.0
self.config_file.put(button.id, background_color=(1.0, 0.0, 0.0, 1.0))
presentation = Builder.load_file("garagemainexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
garagemainexample.kv:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
Sequence:
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "sequence"
text: "Sequence"
pos_hint: {"x":0.25, "top": 0.4}
<CleanScreen>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 90
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<Sequence>:
name: "sequence"
CleanScreen:
id: cleanscreen
BackHomeWidget:

Kivy - inheritance from parent?

I have a question concerning inheritance of sizes from parents within Kivy.
My program layout so far is something along the lines of:
GridLayout (3 cols)
|-------> widget
|-------> widget
|-------> boxlayout (with screenmanager)
|-----> relative layouts used within each screen
I'm having an issue whereby the screenmanager windows all default to size 100, 100 regardless of what I change, and I think it's something to do with the different layouts inheriting from each other, but I am unable to find the source.
Can anyone see what I'm doing wrong here, and suggest a fix?
main.py
import kivy
from kivy.app import App
from kivy.core.window import Window
from layout import MainLayout
Window.size = (581, 142)
class MyApp(App):
def build(self):
self.title = 'Test'
return MainLayout()
if __name__ == '__main__':
MyApp().run()
layout.py
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from screentest import ScreenTestWidget
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
layout = GridLayout(cols=3, col_default_width=200)
layout.add_widget(Label(text='test'))
layout.add_widget(Label(text='test'))
layout.add_widget(ScreenTestWidget())
self.add_widget(layout)
screentest.py
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<MenuScreen>:
RelativeLayout:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
on_press:
root.manager.current = 'settings'
root.manager.transition.direction = 'down'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Quit'
on_press: app.stop()
<SettingsScreen>:
RelativeLayout:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Menu'
on_press:
root.manager.current = 'menu'
root.manager.transition.direction = 'up'
""")
class ScreenTestWidget(BoxLayout):
def __init__(self, **kwargs):
super(ScreenTestWidget, self).__init__(**kwargs)
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
self.add_widget(sm)
# Declare both screens
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
First you create a MainLayout which inherits from GridLayout. This fills the complete screen. But because you never set columns or rows it never works and will not affect children at all.
Then (in MainLayout.__init__) you create a new gridlayout which you add to the MainLayout. MainLayout doesn't work, so size_hint does nothing so the GridLayout will default to size = 100,100. The position is pos = 0,0 again by default. This layout has 3 columns and a col_default_width = 200.
To this GridLayout you now add 2 Labels and the ScreenTestWidget. TheGridlayout has now a width of 600. Everything is display as it should
The ScreenTestWidget has size_hint = 1,1 by default. Thus its size is size = 200, 100. Each screen inside also has size_hint = 1,1 by default, because you never change it. Then each screen has a relativelayout which also has size_hint = 1,1 by default.
Then the buttons in each screen have custom position and resize with the parent. The parent is the Relativelayout. The Relativelayout resizes with the Screen. The Screen resizes with the ScreenTestWidget. The ScreenTestWidget gets resized by the ColumnLayout. The Columnlayout does not resize, because its parent has no cols or rows set and thus doesn't work. Thus it defaults to 100, 100 (but then each child is resized to 200 width).
To fix this you can change the code as following:
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
# instead of creating a new Layout set the attributes of this one
self.cols = 3
self.col_default_width = 200
self.add_widget(Label(text='test'))
self.add_widget(Label(text='test'))
self.add_widget(ScreenTestWidget())
Also you don't need the RelativeLayouts in the screens, because Screens are already RelativeLayouts:
<SettingsScreen>:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Menu'
on_press:
root.manager.current = 'menu'
root.manager.transition.direction = 'up'
Alternatively you could also set the either rows or cols for the MainLayout.

Categories