Related
I am new to Kivy. I am trying to create android app with two screens, whitch both display list of dynamicaly created Buttons. Generaly I add or remove Buttonor edit the its content and want to see changes made in it as they are happening. Here is simplified example where Button changes from column to column:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
KV = '''
BoxLayout:
MyListA:
MyListB:
'''
class Content:
def __init__(self):
self.contentA =[]
self.contentB = []
for i in range(10):
self.contentA.append(f"X{i}")
self.change_made = 0
def switch_to_content_A(self, content_id):
content = self.contentB.pop(content_id)
self.contentA.append(content)
self.change_made = 2
def switch_to_content_B(self, content_id):
content = self.contentA.pop(content_id)
self.contentB.append(content)
self.change_made = 2
class MyListA(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
content_listA = []
for i in c.contentA:
content_listA.append(Button(text=str(i), on_release=lambda x, id=len(content_listA): self.switsch_to_B(id)))
self.add_widget(content_listA[-1])
Clock.schedule_interval(self.update, 0.5)
def update(self, *args):
if c.change_made > 0:
self.clear_widgets()
self.__init__()
def switsch_to_B(self, content_id):
c.switch_to_content_B(content_id)
self.update()
c.change_made -= 1
class MyListB(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
content_listB = []
for i in c.contentB:
content_listB.append(Button(text=str(i), on_release=lambda x, id=len(content_listB): self.switsch_to_A(id)))
self.add_widget(content_listB[-1])
Clock.schedule_interval(self.update, 0.5)
def update(self, *args):
if c.change_made > 0:
self.clear_widgets()
self.__init__()
def switsch_to_A(self, content_id):
c.switch_to_content_A(content_id)
self.update()
c.change_made -= 1
class MyApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == "__main__":
c = Content()
MyApp().run()
The way I understand it script Clock.schedule_interval(self.update, 0.5) is nessesary to update the other column. However I suspect that there are multiple schedule_intervals created (but never killed) because my program begins to slow significaly and then freeze after a while. I am looking to see if I can detect and kill older schedule_intervals. Though if that is not to reason of freezing I would like to know what is.
Try changing MyListA to:
class MyListA(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.update, 0.5)
def update(self, *args):
if c.change_made > 0:
self.clear_widgets()
content_listA = []
for i in c.contentA:
content_listA.append(Button(text=str(i), on_release=lambda x, id=len(content_listA): self.switsch_to_B(id)))
self.add_widget(content_listA[-1])
def switsch_to_B(self, content_id):
c.switch_to_content_B(content_id)
self.update()
c.change_made -= 1
and similar for MyListB. Also Content.change_made has to be 1 or 2 for the first update to happen. If you need the first update before 0.5 seconds, yu can just add one call to self.update() in __init__()
I am trying to change the text of an MDLabel inside one screen from another screen. I can reference the screen itself but since I'm not using kv language I can't set Id of the MDLabel I'm trying to reference.
Is there a way to set Id of an MDLabel from within Python and add it to self.ids of the screen it's part of?
-Or. Is there another way to reference widgets of another screen?
My code:
def main():
class HomeScreen(Screen, GridLayout, MDApp):
def __init__(self, **kwargs):
super(HomeScreen, self).__init__(**kwargs)
self.add_widget(MDRaisedButton(text='Read', size_hint=(.3, .2), font_size='30sp', on_press=lambda x:self.changerReadMail()))
def changerReadMail(self, *args):
self.manager.transition.direction = 'right'
# It's here I want to change the text of label inside the ReadMail class.
self.manager.current = 'read'
class ReadMail(Screen, FloatLayout, MDApp):
def __init__(self, **kwargs):
super(ReadMail, self).__init__(**kwargs)
label = (MDLabel(text='hej'))
self.add_widget(label)
self.add_widget(MDFillRoundFlatButton(text='Back', font_size='20sp', size_hint=(.1,.1), pos_hint={'x':.01, 'y':.02}, on_press=lambda x:self.changerInbox()))
def changerInbox(self, *args):
self.manager.transition.direction = 'left'
self.manager.current = 'home'
class KivyApp(MDApp):
def build(self):
Window.size = (1000, 600)
self.sm = ScreenManager()
self.sm.add_widget(HomeScreen(name='home'))
self.sm.add_widget(ReadMail(name='read'))
self.sm.current = 'home'
return self.sm
KivyApp().run()
if __name__ == '__main__':
main()
I solved this one!
Here's my solution (See the line commented with "This is a new line"):
import weakref #This is a new line
def main():
class HomeScreen(Screen, GridLayout, MDApp):
def __init__(self, **kwargs):
super(HomeScreen, self).__init__(**kwargs)
self.add_widget(MDRaisedButton(text='Read', size_hint=(.3, .2), font_size='30sp', on_press=lambda x:self.changerReadMail()))
def changerReadMail(self, *args):
self.manager.transition.direction = 'right'
self.manager.get_screen('read').ids.test.text = 'test' #This is a new line
self.manager.current = 'read'
class ReadMail(Screen, FloatLayout, MDApp):
def __init__(self, **kwargs):
super(ReadMail, self).__init__(**kwargs)
label = (MDLabel())
self.ids['test'] = weakref.ref(label) #This is a new line
self.add_widget(label)
self.add_widget(MDFillRoundFlatButton(text='Back', font_size='20sp', size_hint=(.1,.1), pos_hint={'x':.01, 'y':.02}, on_press=lambda x:self.changerInbox()))
def changerInbox(self, *args):
self.manager.transition.direction = 'left'
self.manager.current = 'home'
class KivyApp(MDApp):
def build(self):
Window.size = (1000, 600)
self.sm = ScreenManager()
self.sm.add_widget(HomeScreen(name='home'))
self.sm.add_widget(ReadMail(name='read'))
self.sm.current = 'home'
return self.sm
KivyApp().run()
if __name__ == '__main__':
main()
I am using Kivy library to create an app using mainly Python code. I am newbie in Python and Kivy. Here some snippets of my code.
I was trying to pass an image defined "on_start" to my NavButton. #ikolim helped me out to identify the issue. You can see my comments below.
Widget
class NavButton(GridLayout):
def __init__(self, **kwargs):
super(NavButton, self).__init__(**kwargs)
self.rows = 1
frame = FloatLayout(id = "frame")
grid1 = MyGrid(
rows = 1,
cols = 5,
pos_hint = {"top":1, "left": 1},
size_hint = [1, .2] )
grid1.set_background(0.95,0.95,0.95,1)
grid1.add_widget(MyButton(
source = "img/settings.png",
size_hint = [0.2,0.2] ))
grid1.add_widget(MyButton(
source = "img/arrow-left.png" ))
city_icon = Image(
source="img/image1.png",
id="city_icon",
size_hint=[0.8, 0.8])
self.ids.cityicon = city_icon
grid1.add_widget(city_icon)
grid1.add_widget(MyButton(
source = "img/arrow-right.png" ))
grid1.add_widget(MyButton(
source = "img/globe.png",
size_hint = [0.2,0.2] ))
frame.add_widget(grid1)
self.add_widget(frame)
Screens
class Homescreen(Screen):
def __init__(self, **kwargs):
super(Homescreen, self).__init__(**kwargs)
frame = FloatLayout(id = "frame")
grid1 = NavButton()
frame.add_widget(grid1)
...
Screen Manager
Here is where I was messing up! Apparently you have to define the name of your homescreen (i.e.homescreen = Homescreen(name="home_screen")) otherwise it does not update the image of the NavButton when you start the application. I am not sure why but I just what to highlight this for future coders.
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
homescreen = Homescreen()
self.add_widget(homescreen)
self.ids.screenmanager = self
def change_screen(self, name):
self.current = name
Builder
GUI = Builder.load_file("main.kv")
class Main(App):
def build(self):
return GUI
def on_start(self):
self.mynewimage = "image2"
homescreen = self.root.get_screen("home_screen")
homescreen.ids.navbutton.ids.cityicon.source = f"img/{self.mynewimage}.png"
if __name__ == "__main__":
Main().run()
Again, #ikolim thanks for your support.
Solution 2
The following enhancements are required to solve the problem.
class NavButton()
Replace self.ids.cityicon = "cityicon"
with self.ids.cityicon = city_icon
Delete self.bind(on_start=self.update_image) and method update_image()
Method on_start()
Replace homescreen =
Main.get_running_app().root.get_screen("home_screen") with
homescreen = self.root.get_screen("home_screen")
Replace
frame.children[3].children[0].children[0].children[2].source with
homescreen.ids.navbutton.ids.cityicon.source
Remove lines (frame = homescreen.children[0], navbutton =
frame.children[3], and print(...))
Snippets - py file
class NavButton(GridLayout):
def __init__(self, **kwargs):
super(NavButton, self).__init__(**kwargs)
...
city_icon = Image(
source="img/settings.png",
id="city_icon",
size_hint=[0.8, 0.8])
self.ids.cityicon = city_icon
...
class Main(App):
...
def on_start(self):
self.my_city = "it-Rome"
homescreen = self.root.get_screen("home_screen")
homescreen.ids.navbutton.ids.cityicon.source = f"img/{self.my_city}.png"
...
Example - main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import ButtonBehavior
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.graphics import Color, Rectangle
class MyButton(ButtonBehavior, Image):
pass
class ImageButton(ButtonBehavior, Image):
pass
### Components
class NavButton(GridLayout):
def __init__(self, **kwargs):
super(NavButton, self).__init__(**kwargs)
self.rows = 1
frame = FloatLayout(id="frame")
grid1 = MyGrid(
rows=1,
cols=5,
pos_hint={"top": 1, "left": 1},
size_hint=[1, .2])
grid1.set_background(0.95, 0.95, 0.95, 1)
grid1.add_widget(MyButton(
source="img/settings.png",
size_hint=[0.2, 0.2]))
grid1.add_widget(MyButton(
source="img/arrow-left.png"))
city_icon = Image(
source="img/settings.png",
id="city_icon",
size_hint=[0.8, 0.8])
self.ids.cityicon = city_icon
grid1.add_widget(city_icon)
grid1.add_widget(MyButton(
source="img/arrow-right.png"))
grid1.add_widget(MyButton(
source="img/globe.png",
size_hint=[0.2, 0.2]))
frame.add_widget(grid1)
self.add_widget(frame)
### Grids
class MyGrid(GridLayout):
def set_background(self, r, b, g, o):
self.canvas.before.clear()
with self.canvas.before:
Color(r, g, b, o)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect,
size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
### Screens
class Homescreen(Screen):
def __init__(self, **kwargs):
super(Homescreen, self).__init__(**kwargs)
frame = FloatLayout(id="frame")
grid1 = NavButton()
frame.add_widget(grid1)
# This grid contains the number of zpots collected
grid2 = MyGrid(
cols=1,
pos_hint={"top": 0.8, "left": 1},
size_hint=[1, .2], )
grid2.add_widget(Image(
source="img/spot.png"))
grid2.add_widget(Label(
text="[color=3333ff]20/30[/color]",
markup=True))
grid2.set_background(0.95, 0.95, 0.95, 1)
frame.add_widget(grid2)
# This grid contains a scrollable list of nearby zpots
grid3 = MyGrid(
cols=1,
pos_hint={"top": 0.6, "left": 1},
size_hint=[1, .5])
grid3.set_background(0.95, 0.95, 0.95, 1)
frame.add_widget(grid3)
# This grid contains a the map of nearby zpots
grid4 = MyGrid(
cols=1,
pos_hint={"top": 0.1, "left": 1},
size_hint=[1, .1])
grid4.set_background(0.95, 0.95, 0.95, 1)
frame.add_widget(grid4)
self.ids.navbutton = grid1
self.add_widget(frame)
class Newscreen(Screen):
pass
class Settingscreen(Screen):
pass
### ScreenManager
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
homescreen = Homescreen(name='home_screen')
self.add_widget(homescreen)
self.ids.screenmanager = self
def change_screen(self, name):
self.current = name
class TestApp(App):
def build(self):
return MyScreenManager()
def on_start(self):
self.my_city = "it-Rome"
homescreen = self.root.get_screen("home_screen")
print(f"\non_start-Before change: img={homescreen.ids.navbutton.ids.cityicon.source}")
homescreen.ids.navbutton.ids.cityicon.source = f"img/{self.my_city}.png"
print(f"\non_start-After change: img={homescreen.ids.navbutton.ids.cityicon.source}")
if __name__ == "__main__":
TestApp().run()
Output
Solution 1
Use App.get_running_app() function to get an instance of your application
Add self in-front of my_city
Snippets - py file
class NavButton(GridLayout):
def get(self):
...
city_icon = Image(
source = "img/" + App.get_running_app().my_city,
size_hint = [0.8,0.8] )
...
class Main(App):
def build(self):
return GUI
def on_start(self):
# get data from DB
self.my_city = "it-Rome"
Immediately after the super() function is called, it creates a duplicate WidgetClass instance.
My understanding of the super() I've used is that it refers to the EditImageLayout class to inherit from. To this end I've tried to implement different variations of the super() but admittedly I'm only guessing at this stage.
Updated to full working, I've cut out a few hundred lines
Run as > OK > Select an Image > Open > Crop (dual instances start running here)
App_R3App.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.graphics import Line, Color
from kivy.properties import NumericProperty, ObjectProperty, StringProperty, ListProperty
from kivy.uix.image import Image
import io
from kivy.core.image import Image as CoreImageKivy
from kivy.uix.bubble import Bubble
from kivy.core.window import Window
__version__ = '0.1'
class FirstScreen(Screen):
pass
class SecondScreen(Screen):
def hl(self, image_address):
self.new_image_address = image_address # for the sake of this
self.callback_image(self.new_image_address, image_address, "Auto-cropped image")
def callback_image(self, new_image_address_tmp, image_address_tmp, title):
if new_image_address_tmp:
third_screen = self.manager.get_screen("_third_screen_")
new_image_address_tmp = [k.replace("\\", "/") for k in new_image_address_tmp]
third_screen.callback_image(new_image_address_tmp[0], image_address_tmp[0], title)
class ThirdScreen(Screen, BoxLayout):
# class attribute references
image_size = (0, 0)
image_pos = (0, 0)
image_address = ""
new_image_address = ""
title = "Upload"
rect_box = ObjectProperty(None)
t_x = NumericProperty(0.0)
t_y = NumericProperty(0.0)
x1 = y1 = x2 = y2 = NumericProperty(0.0)
def __init__(self, **kwargs):
super(ThirdScreen, self).__init__(**kwargs)
pass
def callback_image(self, new_image_address, image_address, title):
sm.current = "_third_screen_"
self.new_image_address = new_image_address
self.image_address = image_address
self.title = title
self.ids.main_image.source = self.new_image_address
self.ids.main_title.text = self.title
def enable_cropping(self):
# overwrite class attributes
ThirdScreen.image_address = self.image_address
ThirdScreen.new_image_address = self.new_image_address
print("enable_cropping")
sm.current = "_edit_image_screen_"
return True
class EditImageScreen(Screen):
def __init__(self, **kwargs):
print("EditImageScreen")
super(EditImageScreen, self).__init__(**kwargs)
self.layout = None
def on_pre_enter(self):
print("on_pre_enter")
self.layout = EditImageLayout()
self.add_widget(self.layout)
class EditImageLayout(FloatLayout):
color_button = ListProperty([1, .3, .4, 1])
button_color = ListProperty([0, 0, 0, 1])
rectangle_selector = ObjectProperty()
text_size_rectangle = ObjectProperty()
image_layout = ObjectProperty()
bubble_buttons = ObjectProperty()
bubble_buttons_undo_confirm = ObjectProperty()
def __init__(self, **kwargs):
print("EditImageLayout")
self.sm = kwargs.pop('sm', None)
self.crop_image_screen = kwargs.pop('crop_image_screen', None)
# This is where the problem occurs
super(EditImageLayout, self).__init__(**kwargs)
self.rectangle_selector.bind(size_selected=self.on_change_size_rectangle_selector)
self.rectangle_selector.bind(size_selected_temp=self.update_text_size_rectangle)
self.bind(on_touch_down=self.bubble_buttons.hide)
self.bubble_buttons.resize_button.bind(on_press=self.on_press_resize_button)
self.bubble_buttons_undo_confirm.undo_button.bind(on_press=self.on_press_undo_button)
self.bubble_buttons_undo_confirm.confirm_button.bind(on_press=self.on_press_confirm_button)
def on_change_size_rectangle_selector(self, instance, size_selected):
print("on_change_size_rectangle_selector")
if not self.rectangle_selector.tap_not_draw_a_line():
self.bubble_buttons.show()
else:
self.text_size_rectangle.text = ''
def on_press_resize_button(self, instance):
print("on_press_resize_button")
self.image_layout.resize_image(width=self.rectangle_selector.size_selected[0],
height=self.rectangle_selector.size_selected[1])
self.rectangle_selector.delete_line()
self.text_size_rectangle.text = ''
self.bubble_buttons_undo_confirm.show()
def on_press_undo_button(self, instance):
print("on_press_undo_button")
size = self.image_layout.old_size
self.image_layout.resize_image(width=size[0], height=size[1])
self.bubble_buttons_undo_confirm.hide()
def on_press_confirm_button(self, instance):
print("on_press_confirm_button")
self.bubble_buttons_undo_confirm.hide()
def update_text_size_rectangle(self, instance, size):
print("update_text_size_rectangle")
self.text_size_rectangle.text = str('({0}, {1})'.format(int(size[0]), int(size[1])))
class ImageLayout(Image):
image = ObjectProperty()
path_image = StringProperty('image_tmp.jpg')
path_image_tmp = StringProperty('image_tmp.jpg')
old_size = ListProperty([0, 0])
def __init__(self, **kwargs):
print("ImageLayout")
super(ImageLayout, self).__init__(**kwargs)
self.path_image = ThirdScreen.image_address
self.image = CoreImage(self.path_image,
data=io.BytesIO(open(self.path_image, "rb").read()),
ext=self.path_image[self.path_image.rfind('.') + 1::])
self.source = self.path_image
def resize_image(self, width, height, pos_x=None, pos_y=None):
pos_x, pos_y = abs(Window.width - width)/2 , abs(Window.height - height)/2
self.image.resize(self.path_image,
self.path_image_tmp,
int(width),
int(height))
self.source = self.path_image_tmp
self.pos = pos_x, pos_y
self.old_size = self.size
self.size = width, height
self.reload()
class CoreImage(CoreImageKivy):
def __init__(self, arg, **kwargs):
print("CoreImage")
super(CoreImage, self).__init__(arg, **kwargs)
def resize(self, fname, fname_scaled, width, height):
try:
img = Image.open(fname)
except Exception as e:
print('Exception: ', e)
return
img = img.resize((width, height), Image.ANTIALIAS)
try:
img.save(fname_scaled)
except Exception as e:
print('Exception: ', e)
return
class TouchSelector(Widget):
# Points of Line object
Ax = NumericProperty(0)
Ay = NumericProperty(0)
Bx = NumericProperty(0)
By = NumericProperty(0)
Cx = NumericProperty(0)
Cy = NumericProperty(0)
Dx = NumericProperty(0)
Dy = NumericProperty(0)
# Object line
line = ObjectProperty()
# List of line objects drawn
list_lines_in_image = ListProperty([])
# Size of the selected rectangle
size_selected = ListProperty([0, 0])
# Size previous of the selected rectangle
size_selected_previous = ListProperty([0, 0])
# Size temporary of the selected rectangle
size_selected_temp = ListProperty([0, 0])
# Line Color and width
line_color = ListProperty([0.2, 1, 1, 1])
line_width = NumericProperty(1)
# First tap in TouchSelector
first_tap = True
def __init__(self, *args, **kwargs):
super(TouchSelector, self).__init__(*args, **kwargs)
self.bind(list_lines_in_image=self.remove_old_line)
def on_touch_up(self, touch): # on button up
self.size_selected = abs(self.Cx - self.Dx), abs(self.Cy - self.By)
self.size_selected_previous = self.size_selected
print(self.Dx, self.Dy, self.Cx, self.Cy)
def on_touch_down(self, touch):
with self.canvas:
Color(self.line_color)
# Save initial tap position
self.Ax, self.Ay = self.first_touch_x, self.first_touch_y = touch.x, touch.y
# Initilize positions to save
self.Bx, self.By = 0, 0
self.Cx, self.Cy = 0, 0
self.Dx, self.Dy = 0, 0
# Create initial point with touch x and y postions.
self.line = Line(points=([self.Ax, self.Ay]), width=self.line_width, joint='miter', joint_precision=30)
# Save the created line
self.list_lines_in_image.append(self.line)
print("on_touch_down")
def remove_old_line(self, instance=None, list_lines=None):
if len(self.list_lines_in_image) > 1:
self.delete_line()
def delete_line(self, pos=0):
try:
self.list_lines_in_image.pop(pos).points = []
except:
pass
def on_touch_move(self, touch):
# Assign the position of the touch at the point C
self.Cx, self.Cy = touch.x, touch.y
# There are two known points A (starting point) and C (endpoint)
# Assign the positions x and y known of the points
self.Bx, self.By = self.Cx, self.Ay
self.Dx, self.Dy = self.Ax, self.Cy
# Assign points positions to the last line created
self.line.points = [self.Ax, self.Ay,
self.Bx, self.By,
self.Cx, self.Cy,
self.Dx, self.Dy,
self.Ax, self.Ay]
self.size_selected_temp = abs(self.Cx - self.Dx), abs(self.Cy - self.By)
def tap_not_draw_a_line(self):
return (self.size_selected[0] == 0 and self.size_selected[1] == 0)
class BaseBubbleButtons(Bubble):
def __init__(self, **kwargs):
super(BaseBubbleButtons, self).__init__(**kwargs)
def hide(self, instance=None, value=None):
self.opacity = 0
def show(self, instance=None, value=None):
self.opacity = 1
class BubbleButtons(BaseBubbleButtons):
resize_button = ObjectProperty()
cut_button = ObjectProperty()
rotate_button = ObjectProperty()
class BubbleButtonsUndoConfirm(BaseBubbleButtons):
undo_button = ObjectProperty()
confirm_button = ObjectProperty()
class App_R3App(App):
Builder.load_file('App_R3.kv')
def build(self):
return sm
def on_start(self):
return True
def on_pause(self):
return True
def on_resume(self):
return True
def on_stop(self):
return True
if __name__ == '__main__':
# Create the screen manager
sm = ScreenManager()
sm.add_widget(FirstScreen(name='_first_screen_'))
sm.add_widget(SecondScreen(name='_second_screen_'))
sm.add_widget(ThirdScreen(name='_third_screen_'))
sm.add_widget(EditImageScreen(name='_edit_image_screen_'))
App_R3App().run()
App_R3.kv
#:import Window kivy.core.window.Window
<MyScreenManager>:
FirstScreen:
id: first_screen
SecondScreen:
id: second_screen
ThirdScreen:
id: third_screen
EditImageScreen:
id: edit_image_screen
<FirstScreen>:
name: '_first_screen_'
BoxLayout:
orientation: "horizontal"
Label:
id: first_screen_label
text: "Hi, I'm the home page"
BoxLayout:
orientation: "vertical"
Button:
text: "Okay!"
on_press: root.manager.current = '_second_screen_'
Button:
text: "Cancel!"
on_press: app.stop()
<SecondScreen>:
name: '_second_screen_'
id: file_chooser
BoxLayout:
id: file_chooser_box_layout
orientation: "horizontal"
Button
text: "Open"
on_press: root.hl(file_chooser_list_view.selection)
FileChooserListView:
id: file_chooser_list_view
<ThirdScreen>:
name: '_third_screen_'
id: third_screen
xx1: root.x1
yy1: root.y1
tt_x: root.t_x
tt_y: root.t_y
BoxLayout:
orientation: "vertical"
id: third_screen_boxlayout
Label:
id: main_title
text: root.title
size_hint: (1, 0.1)
BoxLayout:
id: image_box_layout
# limits the box layout to the position of the image
Image:
id: main_image
source: root.image_address
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
BoxLayout:
id: button_boxlayout
orientation: "horizontal"
padding: 10
size_hint: (1, 0.15)
Button:
id: accept_button
text: "Okay"
size_hint: (0.33, 1)
on_press: root.image_accepted_by_user(root.image_address)
Button:
id: crop_button
text: "Crop"
size_hint: (0.33, 1)
on_press: root.enable_cropping()
Button:
id: cancel_button
text: "Cancel"
size_hint: (0.33, 1)
on_press: root.manager.current = '_first_screen_'
<EditImageLayout>:
rectangle_selector: rectangle_selector
text_size_rectangle: text_size_rectangle
image_layout: image_layout
bubble_buttons: bubble_buttons
bubble_buttons_undo_confirm: bubble_buttons_undo_confirm
canvas.before:
Color:
rgba: (1, 1, 1, 0.2)
Rectangle:
pos: self.pos
size: Window.width, Window.height
Label:
id: text_size_rectangle
pos_hint_x: None
pos_hint_y: None
pos: Window.width*.45, Window.height*.45
color: (1, 1, 1, 1)
ImageLayout:
id: image_layout
size: Window.width*.8, Window.height*.8
pos: Window.width*.1, Window.height*.1
size_hint: None, None
pos_hint_x: None
pos_hint_y: None
TouchSelector:
id: rectangle_selector
BubbleButtons:
id: bubble_buttons
size_hint: (None, None)
size: (200, 40)
pos_hint_x: None
pos_hint_y: None
pos: Window.width*.4, Window.height*.1
opacity: 0
arrow_pos: 'top_mid'
BubbleButtonsUndoConfirm:
id: bubble_buttons_undo_confirm
size_hint: (None, None)
size: (200, 40)
pos_hint_x: None
pos_hint_y: None
pos: Window.width*.4, Window.height*.9
opacity: 0
arrow_pos: 'top_mid'
<BubbleButtons>:
resize_button: resize_button
cut_button: cut_button
rotate_button: rotate_button
BubbleButton:
id: resize_button
text: 'Resize'
BubbleButton:
id: cut_button
text: 'Cut'
BubbleButton:
id: rotate_button
text: 'Rotate'
<BubbleButtonsUndoConfirm>:
undo_button: undo_button
confirm_button: confirm_button
BubbleButton:
id: undo_button
text: 'Undo'
BubbleButton:
id: confirm_button
text: 'Confirm'
Console output, aka what the code prints (you can see that ImageLayout and CoreImage run twice)
EditImageScreen
enable_cropping
on_pre_enter
EditImageLayout
ImageLayout
CoreImage
ImageLayout
CoreImage
What I suspect is happening is that the super() is calling the base class EditImageLayout, the static elements of that base class are calling the .kv file and initating the ImageLayout and CoreImage classes from there. At the same time, "self" goes into action and does the exact same thing. This causes trouble later on when I implement on_touch_down over it (on_touch_down then appears twice, etc. )
Problem
ImageLayout and CoreImage were called twice.
[INFO ] [GL ] Unpack subimage support is available
enable_cropping
on_pre_enter
EditImageLayout
ImageLayout
CoreImage
ImageLayout
CoreImage
[INFO ] [Base ] Leaving application in progress...
Process finished with exit code 0
Root Cause
The double calls to ImageLayout and CoreImage were due to two kv files, App_R3.kv and app_r3.kv present as shown in the screen shots below. In the application, the app class is App_R3App(App): and it is using Builder.load_file('App_R3.kv') to load kv code into application.
Solution
Remove kv file, app_r3.kv. In the example, we renamed it to app_r3-off.kv
Kv language » How to load KV
There are two ways to load Kv code into your application:
By name convention:
Kivy looks for a Kv file with the same name as your App class in
lowercase, minus “App” if it ends with ‘App’ e.g:
MyApp -> my.kv
If this file defines a Root Widget it will be attached to the App’s
root attribute and used as the base of the application widget tree.
By Builder:
You can tell Kivy to directly load a string or a file. If this string
or file defines a root widget, it will be returned by the method:
Builder.load_file('path/to/file.kv')
or:
Builder.load_string(kv_string)
Recommendations
Since class rule, <MyScreenManager>: is defined in your kv file, you should use it rather than define sm = ScreenManager() in your Python code. On top of that, keep the view/design separate.
kv file
Add missing class rule for EditImageScreen.
<EditImageScreen>:
name: '_edit_image_screen_'
<EditImageLayout>:
Python code
Add class definition for MyScreenManager
In build() method, replace return sm with return MyScreenManager()
Before App_R3().run(), remove all references to sm
Since each screen has by default a property manager that gives you the instance of the ScreenManager used. In callback_image() and enable_cropping() methods, replace sm.current with self.manager.current
In __init__() method of EditImageLayout() class, remove self.sm = kwargs.pop('sm', None). If you need to access the root/ScreenManager, use sm = App.get_running_app().root
Snippet
class MyScreenManager(ScreenManager):
pass
...
def callback_image(self, new_image_address, image_address, title):
self.manager.current = "_third_screen_"
self.new_image_address = new_image_address
...
def enable_cropping(self):
...
print("enable_cropping")
self.manager.current = "_edit_image_screen_"
...
def __init__(self, **kwargs):
print("EditImageLayout")
self.crop_image_screen = kwargs.pop('crop_image_screen', None)
...
class App_R3App(App):
def build(self):
return MyScreenManager()
if __name__ == '__main__':
App_R3App().run()
ScreenManager » Basic Usage
Each screen has by default a property manager that gives you the
instance of the ScreenManager used.
Related: Kivy: understanding widget instances in apps
I am initializing my app with 2 widget instances, the (.kv) file is as follows:
#:kivy 1.0.9
<GraphInterface>:
node: graph_node
edge: graph_edge
GraphNode:
id: graph_node
center: self.parent.center
GraphEdge:
id: graph_edge
center: 150,200
<GraphNode>:
size: 50, 50
canvas:
Color:
rgba: (root.r,1,1,1)
Ellipse:
pos: self.pos
size: self.size
<GraphEdge>:
size: self.size
canvas:
Color:
rgba: (root.r,1,1,1)
Line:
width: 2.0
close: True
The node and edge objects are defined as follows:
class GraphNode(Widget):
r = NumericProperty(1.0)
def __init__(self, **kwargs):
self.size= [50,50]
self.pos = [175,125]
self.r = 1.0
super(GraphNode, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.grab_current == None:
self.r = 0.6
touch.grab(self)
return True
return super(GraphNode, self).on_touch_down(touch)
def on_touch_move(self, touch):
if touch.grab_current is self:
self.pos=[touch.x-25,touch.y-25]
for widget in self.parent.children:
if isinstance(widget, GraphEdge) and widget.collide_widget(self):
print "collision detected"
widget.snap_to_node(self)
return True
return super(GraphNode, self).on_touch_move(touch)
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self)
self.r = 1.0
# and finish up here
pass
class GraphEdge(Widget):
r = NumericProperty(1.0)
def __init__(self, **kwargs):
super(GraphEdge, self).__init__(**kwargs)
with self.canvas:
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)
def snap_to_node(self, node):
if self.collide_widget(node):
print "collision detected"
del self.line.points[-2:]
self.line.points+=node.center
self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
return True
pass
Sorry that there is so much code here, I don't know which part is causing the issue though I suspect it is simply the way I am initializing in the (.kv) file.
Key Problem:
When I click on the node the edge changes colour as well, despite the fact they do not share the same colour property. (I also tried renaming the GraphEdge colour property, but the issue still arises).
Below I show the issue, and the expected result (respectively).
You were actually trying to create two edges - one in with self.canvas: in your GraphEdge class (where you did not specified color) and second in your *.kv file (where you did not specified points). So you can remove whole <GraphEdge> in your *.kv file and then expand with self.canvas: like this:
with self.canvas:
Color(self.r, 1, 1, 1)
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)