I have a music player app that reads the cover of songs and displays them on the screen. For this I created my own class ByteFitImage for the image to read bytes and for it to adapt to different sizes.
The problem I am encountering is that if I decide to change the data, I get a key error:
KeyError: 'source'
And obviously this line is causing the problem:
self.kv.ids.img.data = BytesIO(second_img.read())
Why is it asking for source? My Code:
from io import BytesIO
from kivy.clock import Clock
from kivy.core.image import Image
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.properties import BooleanProperty, ObjectProperty, VariableListProperty, Property
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.app import App
class ByteFitImage(BoxLayout):
data = Property(None)
radius = VariableListProperty([0], length=4)
mipmap = BooleanProperty(False)
_container = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self._late_init)
def _late_init(self, *args):
self._container = Container(self.data)
self.bind(data=self._container.setter("source"))
self.add_widget(self._container)
def reload(self):
self._container.image.reload()
class Container(Widget):
# source = ObjectProperty()
image = ObjectProperty()
def __init__(self, source, **kwargs):
super().__init__(**kwargs)
# self.image = AsyncImage(mipmap=mipmap)
self.bind(size=self.adjust_size, pos=self.adjust_size)
self.image = Image(source, ext="jpg")
self.image.bind(on_load=self.adjust_size)
# self.source = source
def on_source(self, instance, value):
if isinstance(value, str):
self.image.source = value
else:
self.image.texture = value
self.adjust_size()
def adjust_size(self, *args):
if not self.parent or not self.image.texture:
return
(par_x, par_y) = self.parent.size
if par_x == 0 or par_y == 0:
with self.canvas:
self.canvas.clear()
return
par_scale = par_x / par_y
(img_x, img_y) = self.image.texture.size
img_scale = img_x / img_y
if par_scale > img_scale:
(img_x_new, img_y_new) = (img_x, img_x / par_scale)
else:
(img_x_new, img_y_new) = (img_y * par_scale, img_y)
crop_pos_x = (img_x - img_x_new) / 2
crop_pos_y = (img_y - img_y_new) / 2
subtexture = self.image.texture.get_region(
crop_pos_x, crop_pos_y, img_x_new, img_y_new
)
with self.canvas:
self.canvas.clear()
Color(1, 1, 1)
Rectangle(texture=subtexture, pos=self.pos, size=(par_x, par_y))
class Example(App):
def __init__(self, **kwargs):
with open("images/song_img.jpg", "rb") as song_img:
self.song_img_bytes = BytesIO(song_img.read())
super(Example, self).__init__(**kwargs)
self.kv = Builder.load_string('''
#:kivy 2.0.0
BoxLayout:
orientation: "vertical"
padding: 10
ByteFitImage:
id: img
data: app.song_img_bytes
Button:
text: "Change cover!"
font_size: 32
on_release: app.change_cover()''')
def build(self):
return self.kv
def change_cover(self):
with open("images/second_img.jpg", "rb") as second_img:
self.kv.ids.img.data = BytesIO(second_img.read())
if __name__ == '__main__':
Example().run()
Related
I tried to make Galaxy project. I am having A Mistake in between!
This I am try through freecodecamp.org kivy full course.
I am beginner to kivy.
See if there is problem in canvas.
I wasn't able to see my drawn lines on kivy window.
Here are My files:
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.lang import Builder
from kivy.properties import NumericProperty
Builder.load_file('galaxy.kv')
class MainWidget(Widget):
perspective_point_x = NumericProperty(0)
perspective_point_y = NumericProperty(0)
V_NB_LINES = 7
V_NB_SPACING = .1
vertical_lines = []
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.init_vertical_lines()
def on_parent(self, widget, parent):
pass
def on_size(self, *args):
self.update_vertical_lines()
def on_perspective_point_x(self, widget, value):
pass
def on_perspective_point_y(self, widget, value):
pass
def init_vertical_lines(self):
with self.canvas:
Color(1, 1, 1)
for i in range(0, self.V_NB_LINES):
self.vertical_lines.append(Line())
def update_vertical_lines(self):
central_line_x = int(self.width/2)
spacing = int(self.V_NB_SPACING * self.width)
offset = -int(self.V_NB_LINES/2)
for i in range(0, self.V_NB_LINES):
line_x = central_line_x + offset*spacing
self.vertical_lines[i].points = [line_x, 0, line_x, self.height]
offset += 1
class GalaxyApp(App):
pass
GalaxyApp().run()
galaxy.kv
<MainWidget>
perspective_point_x: self.width / 2
perspective_point_y: self.height * 0.75
You where missing the build function in GalaxyApp
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.lang import Builder
from kivy.properties import NumericProperty
Builder.load_file('galaxy.kv')
class MainWidget(Widget):
perspective_point_x = NumericProperty(0)
perspective_point_y = NumericProperty(0)
V_NB_LINES = 7
V_NB_SPACING = .1
vertical_lines = []
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.init_vertical_lines()
def on_parent(self, widget, parent):
pass
def on_size(self, *args):
self.update_vertical_lines()
def on_perspective_point_x(self, widget, value):
pass
def on_perspective_point_y(self, widget, value):
pass
def init_vertical_lines(self):
with self.canvas:
Color(1, 1, 1)
for i in range(0, self.V_NB_LINES):
self.vertical_lines.append(Line())
def update_vertical_lines(self):
central_line_x = int(self.width/2)
spacing = int(self.V_NB_SPACING * self.width)
offset = -int(self.V_NB_LINES/2)
for i in range(0, self.V_NB_LINES):
line_x = central_line_x + offset*spacing
with self.canvas:
self.vertical_lines[i].points = [line_x, 0, line_x, self.height]
offset += 1
class GalaxyApp(App):
def build(self):
return MainWidget()
GalaxyApp().run()
I am trying to create a stack of multiline Labels on Kivy inside a BoxLayout. How do I get the BoxLayout to expand according to its contents? Now the BoxLayout is squeezing the Labels instead.
I do not wish to hardcode the multiline Label size as I want each to be flexible to accommodate varying lines of text.
My demo code is as follows:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.graphics import Color, Rectangle
class MyApp(App):
def build(self):
self.root = GridLayout(rows=1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self):
ScrollView.__init__(self)
self.view = GridLayout(cols=1, size_hint=(1, None))
self.add_widget(self.view)
self.view.bind(minimum_height=self.view.setter("height"))
for i in range(20):
self.view.add_widget(MyWidget(i % 2 is 1))
class MyWidget(BoxLayout):
def __init__(self, odd, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.size_hint = (1, None)
self.odd = odd
for i in range(3):
ll = Label(text=f"I am line\nNumber {i + 1}")
self.add_widget(ll)
self.bind(pos=self.format_background_color)
self.bind(size=self.format_background_color)
def format_background_color(self, *args):
with self.canvas.before:
if self.odd:
Color(0.0, 0.0, 0.2, mode="rgb")
else:
Color(0.0, 0.0, 0.8, mode="rgb")
Rectangle(pos=self.pos, size=self.size)
MyApp().run()
If the Labels you added to MyWidget have fixed amount of text you can just set a specific value for MyWidget's height as self.height = "150dp" (Adjust to your need).
Otherwise if you want the MyWidget's height enough to contain its children (here, Labels) then set it to its minimum_height. Also you have to set each Label's height to its texture height.
Thus your modified MyWidget class should now look like,
class MyWidget(BoxLayout):
def __init__(self, odd, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.size_hint_y = None
self.odd = odd
self.bind(
pos = self.format_background_color,
size = self.format_background_color,
minimum_height = self.setter("height")
)
for i in range(3):
ll = Label(text=f"I am line\nNumber {i + 1}")
ll.bind(texture_size=self.resize_label)
self.add_widget(ll)
def resize_label(self, instance, value):
instance.size_hint_y = None
instance.height = value[1]
...
You can also utilize padding and spacing as,
class MyWidget(BoxLayout):
def __init__(self, odd, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.size_hint_y = None
self.padding = "10dp"
self.spacing = "10dp"
...
I am am doing python kivy scroll view program there is no error but it gives me blank output
in main python file:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.stacklayout import StackLayout
from kivy.metrics import dp
class stacklayoutexample(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for i in range(0,101):
size = dp(100)
b=Button(text=str(i), size_hint=(None,None), size=(size,size))
# self.orientation = "rl-bt"
# self.spacing = ("20dp","20dp")
self.add_widget(b)
class main(App):
pass
main().run()
in main.kv file:
scrollviewexample:
<scrollviewexample#ScrollView>:
stacklayoutex:
size_hint:1,None
<stacklayoutexample>:
When you use kv, you must capitalize all your classes. Also, is stacklayoutex intended to be stacklayoutexample? If so, here is a modified version of your code that I think will work:
main.kv:
Scrollviewexample:
<Scrollviewexample#ScrollView>:
Stacklayoutexample:
size_hint:1,None
<Stacklayoutexample>:
And the python code:
class Stacklayoutexample(StackLayout): # just capitalized
def __init__(self, **kwargs):
super().__init__(**kwargs)
for i in range(0, 101):
size = dp(100)
b = Button(text=str(i), size_hint=(None, None), size=(size, size))
# self.orientation = "rl-bt"
# self.spacing = ("20dp","20dp")
self.add_widget(b)
How to resolve widget overlapping in kivy framework, I am adding widgets in for cycle so I have more than one widget but all of them comes to be in one place, how can I prevent this?
My python code:
from kivy.app import App
from kivymd.app import MDApp
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.graphics import Color
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivymd.uix.card import MDCard
from kivy.properties import StringProperty
from kivy.lang import Builder
import requests, time, collections
class request_sender():
return 'DATA'
class CustomLabel(Label):
pass
class CustomBox(BoxLayout):
pass
class AuctionCard(Widget):
auc_timer = ''
auc_img = ''
auc_name = ''
def __init__(self, **kwargs):
super(AuctionCard, self).__init__(**kwargs)
with self.canvas.before: Color(1, 0, .4, mode='rgb')
Clock.schedule_once(self.load_info)
def load_info(self, dt):
print(self.auc_name)
self.size_hint = None, None
box = BoxLayout(orientation='vertical', size = (800, 600))
box.size_hint_x = 50;
box.height = 100
AuctionName = CustomLabel(text=self.auc_name, pos_hint={'top': 300.9})
AuctionImage = CustomLabel(text=self.auc_img)
AuctionTimer = CustomLabel(text=self.auc_name)
box.add_widget(AuctionName)
box.add_widget(AuctionTimer)
box.add_widget(AuctionImage)
self.add_widget(box)
class MyWidget(Widget):
prop = StringProperty('')
array_of_labels = []
prop1 = StringProperty('Second card')
n = 0
all_cards = collections.defaultdict()
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.screen_load()
def timer_le(self, dt):
returned_data = request_sender().sender_update('ajax.php')
for key in self.all_cards:
for data in returned_data:
if data['pid'] == key:
self.all_cards[key][0].text = str(data['remaining_time'])
def screen_load(self):
returned_data = request_sender().sender_update('ajax.php')
box = GridLayout(cols=2)
self.size_hint = None, None
for data in returned_data:
AucCard = AuctionCard()
AucCard.auc_name = str(data['auc_name'])+'\n\n'
AucCard.auc_timer = str(data['remaining_time'])+'\n\n'
AucCard.auc_img = str(data['auc_img'])+'\n\n'
box.add_widget(AucCard)
print('Widget added')
self.add_widget(box)
#self.all_cards[str(data['pid'])] = [AucCard]
#Clock.schedule_interval(self.timer_le, 1/30)
class TestApp(App):
def build(self):
box = GridLayout(cols=2)
return MyWidget()
TestApp().run()
My kv code:
<CustomLabel>:
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
Result:
Kind of result that I want to create:
Actually I found easy way to solve this issue. The problem was in parent class of my AuctionCard and MyWidget classes, I set parent class to Widget but for the AuctionCard it should be BoxLayout and for MyWidget GridLayout. So from there I managed to set cols = 2 and size to window.size. From here it works exactly how it should work.
from kivy.lang import Builder
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.uix.gridlayout import GridLayout
class AuctionCard(BoxLayout):
auc_timer = ''
auc_img = ''
auc_name = ''
def __init__(self, **kwargs):
super(AuctionCard, self).__init__(**kwargs)
Clock.schedule_once(self.load_info)
def load_info(self, dt):
self.orientation = 'vertical'
AuctionName = Label(text=self.auc_name)
AuctionImage = Label(text=self.auc_img)
AuctionTimer = Label(text=self.auc_timer)
self.add_widget(AuctionName)
self.add_widget(AuctionTimer)
self.add_widget(AuctionImage)
class MyWidget(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.size = (Window.size[0], self.size[1])
self.load_app()
print('MyWidget size: '+str(self.size))
def load_app(self):
self.size_hint = None, None
returned_data = [{'auc_name':'name 1', 'remaining_time':'100', 'auc_img':'https://img.src'},
{'auc_name':'name 2', 'remaining_time':'200', 'auc_img':'https://img.src'},
{'auc_name':'name 3', 'remaining_time':'300', 'auc_img':'https://img.src'}]
for data in returned_data:
AucCard = AuctionCard()
AucCard.auc_name = str(data['auc_name'])+'\n\n'
AucCard.auc_timer = str(data['remaining_time'])+'\n\n'
AucCard.auc_img = str(data['auc_img'])+'\n\n'
self.add_widget(AucCard)
print('Widget added')
class MyTestApp(App):
def __init__(self, **kwargs):
self.title = "My Material Application"
super().__init__(**kwargs)
def build(self):
return MyWidget()
if __name__ == "__main__":
MyTestApp().run()
Result:
Result
I need to create an app that works on a VR glasses, as in this example below:
But I have two problems:
I can not make two simultaneous accesses to the camera and create two widgets.
I can not make the widgets created in kv file (in this case a label) stay on the camera, to write information about the view.
Any way to create this dual camera widget and write it over?
my code:
main:
__version__ = '1.0'
from kivy.lang import Builder
from kivy.app import App
from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, \
NumericProperty
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from jnius import autoclass, PythonJavaClass, java_method, cast
from android.runnable import run_on_ui_thread
PythonActivity = autoclass('org.renpy.android.PythonActivity')
Camera = autoclass('android.hardware.Camera')
SurfaceView = autoclass('android.view.SurfaceView')
LayoutParams = autoclass('android.view.ViewGroup$LayoutParams')
class PreviewCallback(PythonJavaClass):
__javainterfaces__ = ('android.hardware.Camera$PreviewCallback', )
def __init__(self, callback):
super(PreviewCallback, self).__init__()
self.callback = callback
class SurfaceHolderCallback(PythonJavaClass):
__javainterfaces__ = ('android.view.SurfaceHolder$Callback', )
def __init__(self, callback):
super(SurfaceHolderCallback, self).__init__()
self.callback = callback
#java_method('(Landroid/view/SurfaceHolder;III)V')
def surfaceChanged(self, surface, fmt, width, height):
self.callback(fmt, width, height)
class AndroidWidgetHolder(Widget):
view = ObjectProperty(allownone=True)
def __init__(self, **kwargs):
self._old_view = None
from kivy.core.window import Window
self._window = Window
kwargs['size_hint'] = (None, None)
super(AndroidWidgetHolder, self).__init__(**kwargs)
def on_view(self, instance, view):
activity = PythonActivity.mActivity
activity.addContentView(view, LayoutParams(*self.size))
view.setZOrderOnTop(True)
view.setX(self.x)
view.setY(self._window.height - self.y - self.height)
class AndroidCamera(Widget):
index = NumericProperty(0)
__events__ = ('on_preview_frame', )
def __init__(self, **kwargs):
self._holder = None
self._android_camera = None
super(AndroidCamera, self).__init__(**kwargs)
self._holder = AndroidWidgetHolder(size=self.size, pos=self.pos)
self.add_widget(self._holder)
self.start()
#run_on_ui_thread
def stop(self):
if self._android_camera is None:
return
self._android_camera.setPreviewCallback(None)
self._android_camera.release()
self._android_camera = None
self._holder.view = None
#run_on_ui_thread
def start(self):
if self._android_camera is not None:
return
self._android_camera = Camera.open(self.index)
self._android_surface = SurfaceView(PythonActivity.mActivity)
surface_holder = self._android_surface.getHolder()
self._android_surface_cb = SurfaceHolderCallback(self._on_surface_changed)
surface_holder.addCallback(self._android_surface_cb)
self._holder.view = self._android_surface
self._holder2.view = self._android_surface
def _on_surface_changed(self, fmt, width, height):
params = self._android_camera.getParameters()
params.setPreviewSize(width, height)
self._android_camera.setParameters(params)
self._previewCallback = PreviewCallback(self._on_preview_frame)
self._android_camera.setPreviewCallbackWithBuffer(self._previewCallback);
self._android_camera.setPreviewDisplay(self._android_surface.getHolder())
self._android_camera.startPreview();
def _on_preview_frame(self, camera, data):
self.dispatch('on_preview_frame', camera, data)
self._android_camera.addCallbackBuffer(data)
def on_preview_frame(self, camera, data):
pass
def on_size(self, instance, size):
if self._holder:
self._holder.size = size
def on_pos(self, instance, pos):
if self._holder:
self._holder.pos = pos
class vrtest(App, BoxLayout):
def build(self):
return Builder.load_file('qrcode.kv')
camera_size = ListProperty([640, 480])
xpos = NumericProperty(-190)
ypos = 25
def __init__(self, **kwargs):
super(vrtest, self).__init__(**kwargs)
self._camera = AndroidCamera(
size=self.camera_size,
size_hint=(None, None),
pos=(self.xpos, self.ypos))
self.add_widget(self._camera)
def start(self):
self._camera.start()
if __name__ == '__main__':
vrtest().run()
kv:
FloatLayout:
Label:
text: 'Label Test12345678987654321'
pos_hint:{'x':0, 'y':0}