I have the following code for a custom knob widget. I am trying to have 6 of these knobs in a layout. But it doesn't seem to work when I wrap the kv code into a CustomKnob#BoxLayout, I followed this answer. Is there a way to replicate the kv code to have 6 knobs, each with their own touch area ?
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate
from kivy.lang import Builder
from kivy.properties import NumericProperty
import math
kv = '''
<Dial>
canvas:
PushMatrix
Rotate:
angle: root.angle
origin: self.center
# red circle
Color:
rgb: 1,0,0
Line:
circle: (self.center_x, self.center_y, 112, 0, self.angle)
width: 5
#green circle
Color:
rgb: .1, 1, .1
Line:
width: 2
circle: (self.center_x, self.center_y, min(self.width, self.height)/ 8.5)
PopMatrix
Label:
id: lbl
text: str(round(root.angle/360,2))
center: self.parent.center
'''
Builder.load_string(kv)
class Dial(Widget):
angle = NumericProperty(90)
def __init__(self, **kwargs):
super(Dial, self).__init__(**kwargs)
def on_touch_move(self, touch):
y = (touch.y - self.center[1])
x = (touch.x - self.center[0])
temp_calc = math.degrees(math.atan2(y, x))
if temp_calc >= 0:
calc = temp_calc
else:
calc = 360 + temp_calc
self.angle = calc
print(round(self.angle / 360, 2))
class DialApp(App):
def build(self):
return Dial()
if __name__ == "__main__":
DialApp().run()
You can add a rule for CustomKnob in your kv:
<CustomKnob#BoxLayout>:
orientation: 'vertical'
Dial:
id: d1
Dial:
id: d2
Dial:
id: d3
Dial:
id: d4
Dial:
id: d5
Dial:
id: d6
Then, in your build() method:
class DialApp(App):
def build(self):
return Factory.CustomKnob()
The use of Factory is required since CustomKnob is defined in kv.
Note that you still must work out size and positioning of the Dials.
Related
I would like to draw something in canvas from .py by using class 'BotRightCanvas'. The initial widgets are all defined in .kv
Problem is when I finally declare BotRightCanvas in .kv, the size of the canvas widget is not coming proper (still 100x100). I would like the green rectangle to coincide with the cyan one.
Here's .py
from kivy.graphics import Color
from kivy.graphics.vertex_instructions import Line, Rectangle, Ellipse
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def getSize(self, id):
cSize = id.size
print('win ' + str(Window.size))
print('canvas ' + str(id.size))
print('cSize ' + str(cSize))
class BotRightCanvas(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(0, 1, 0, 1)
Line(circle=(self.width / 2, self.height / 2, 25), width=2)
Line(rectangle=(0, 0, self.width, self.height), width=5)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
if __name__ == "__main__":
PlaygroundApp().run()
and .kv
<TestOut>:
BoxLayout:
orientation: 'vertical'
Button:
text:'A'
height: dp(100)
size_hint: 1, None
BoxLayout:
Button:
text:'B'
width: dp(150)
size_hint: None, 1
RelativeLayout:
id: rel_lo
on_size: root.getSize(rel_lo)
Widget:
canvas:
Line:
rectangle: (0,0, self.width, self.height)
width: 2
canvas.before:
Color:
rgba: 0,1,1,1
BotRightCanvas:
I tried printing the final canvas shape in console, seems it initializes with size of 0x0 but later on gets correct size 650x500.
The console output
canvas [0.0, 0]
cSize [0.0, 0]
win (800, 600)
canvas [650.0, 500.0]
cSize [650.0, 500.0]
Any help? Thanks !!
EDIT: Would like to control canvas items from .py
from kivy.graphics import Color
from kivy.graphics.vertex_instructions import Line, Rectangle, Ellipse
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.lang.builder import Builder
from kivy.app import App
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def getSize(self, id):
cSize = id.size
print('win ' + str(Window.size))
print('canvas ' + str(id.size))
print('cSize ' + str(cSize))
class BotRightCanvas(Widget):
pass
Builder.load_string(
'''
<BotRightCanvas>:
canvas:
Color:
rgba:0, 1, 0, 1
Line:
circle:self.width / 2, self.height / 2, 25
width:2
Line:
rectangle:0, 0, self.width, self.height
width:5
<TestOut>:
BoxLayout:
orientation: 'vertical'
Button:
text:'A'
height: dp(100)
size_hint: 1, None
BoxLayout:
Button:
text:'B'
width: dp(150)
size_hint: None, 1
RelativeLayout:
id: rel_lo
on_size: root.getSize(rel_lo)
Widget:
canvas:
Line:
rectangle: (0,0, self.width, self.height)
width: 2
canvas.before:
Color:
rgba: 0,1,1,1
BotRightCanvas:
size:rel_lo.size
'''
)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
for line in open('2.py').readlines():
print(' '+line)
if __name__ == "__main__":
PlaygroundApp().run()
I am trying to initialize the widget class by placing a button at the root.center. Strangely, root.size during initialization is only (100,100). When it's rendered, the actual size is (600,800). So my button is placed in the left bottom instead of in the center.
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.uix.widget import Widget
w = Builder.load_string('''
<MyWidget>:
canvas.before:
Color:
rgba: 1, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
Button:
id: score_button
center: root.score_button_center
text:'Best Scores'
''')
class MyWidget(Widget):
score_center_y = NumericProperty(-100)
score_center_x = NumericProperty(-100)
score_button_center = ReferenceListProperty(score_center_x, score_center_y)
hide_center = (-100, -100)
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.score_button_center = self.width, self.height
class MyApp(App):
def build(self):
g = MyWidget()
return g
if __name__ == '__main__':
MyApp().run()
Just change:
center: root.score_button_center
to:
center: root.center
That will take advantage of the kivy feature that sets up bindings to adjust the Button position whenever the root widget changes size or position.
...
How can I give function path?
I couldn't find how to solve addressing.
Anyone have any idea how to fix this?
When I click on the green area only, I want to do the operation of the button in the white area.
...
'''python
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
class Area(RelativeLayout):
pass
class Sec(RelativeLayout):
""" This is white area """
def chn(self):
self.ids.secbut.disabled = True
class Vec(RelativeLayout):
""" This is green area
I want the button to disable when the green area is clicked.
"""
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
Sec.ids.secbut.disabled = True
class runner(App):
def build(self):
self.area = Area()
return self.area
if __name__ == "__main__":
runner().run()
'''
...
runner.kv
...
'''kivy
<Area>:
size_hint: 1, 1
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.9,0.9,1
Rectangle:
size:self.size
Sec:
Vec:
<Sec>:
size_hint: 1, 0.5
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.8,0.7,1
Rectangle:
size:self.size
Button:
id:secbut
text:"click"
size_hint:0.5,0.5
<Vec>:
size_hint: 1, 0.5
pos_hint: {"x":0,"y":0.5}
canvas:
Color:
rgba:0.2,0.8,0.1,1
Rectangle:
size:self.size
'''
I couldn't find how to solve addressing.
You need to check if the touch actually collides with the area you care about.
if self.collide_point(*touch.pos):
self.ids.secbut.disabled = True
Kind of awkward, but you can do it by adding an id to your Sec like this:
<Area>:
size_hint: 1, 1
pos_hint: {"x":0,"y":0}
canvas:
Color:
rgba:0.9,0.9,0.9,1
Rectangle:
size:self.size
Sec:
id: sec
Vec:
Then use that in your on_touch_down() in Vec:
class Vec(RelativeLayout):
""" This is green area
I want the button to disable when the green area is clicked.
"""
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
App.get_running_app().root.ids.sec.ids.secbut.disabled = True
After an image rotated and window resized, the image moved no assumed location. In my assumption, image always moves to the center of left side pain. Please tell me solution.
This code realize to generate window with both pain(area window)s at left side and right side. The left side was assumed as a menubar. The right side was assumed to manipulate an image with rotating. If you move mouse on window, the image will be rotated according to your mouse distance.
MyEnv.
MacOS: 10.14.5
Kivy: 1.11.1
Python:3.6.8 by conda.
from cmath import sqrt
from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.context_instructions import LoadIdentity
from kivy.input import MotionEvent
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.lang import Builder
Builder.load_string("""
<Example>:
orientation:"horizontal"
BoxLayout:
orientation:"vertical"
size_hint:0.5,1 # rate of win, this is menubar.
canvas.before:
Color:
rgba: 0, 0, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
Button:
size:1,0.5
Button:
size:1,0.5
FloatLayout:
id: near_layout
pos_hint: {'x':0.5,'y':0}
size_hint:0.5,1
canvas.before:
Color:
rgba: 0, 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
Image:
id:img
center: near_layout.center
canvas.before:
Rotate:
angle: root.angle
axis: 0,0,1
origin: self.center
source: 'kivy.jpeg'
""")
class Example(App, BoxLayout):
angle = NumericProperty(0)
def build(self):
Window.bind(on_resize=self.on_resize)
return self
def on_resize(self, obj, x, y):
LoadIdentity()
self.img = self.ids.img # type:Image
self.img.reload() # Necessary,if not, image disappeared
def on_touch_move(self, touch):
self.angle += sqrt((touch.ppos[0] - touch.pos[0]) ** 2 + (touch.ppos[1] - touch.pos[1]) ** 2).real
print("pos:%s,size:%s" % (self.ids.near_layout.pos, self.ids.near_layout.size))
if __name__ == "__main__":
Example().run()
I want a rectangle to stick to the bottom left corner of an image. It works, but when I change window size the rectangle is little bit off. It is where image was before update. Here's the code:
from kivy.app import App, Builder
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.graphics import Color
Builder.load_string(
'''
<Screen>:
canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
Image:
id: im
keep_data: True
source: 'test.png'
allow_stretch: True
size_hint_y: 0.9
pos_hint: {'left':1, 'top':1}
Button:
text: 'Button'
size_hint_y: 0.1
pos_hint: {'x':0, 'y':0}
''')
class Screen(FloatLayout):
def __init__(self, **kwargs):
super(Screen, self).__init__(**kwargs)
with self.ids.im.canvas.after:
Color(1,0,1,.5)
self.rec = Rectangle(pos=(self.ids.im.center_x - self.ids.im.norm_image_size[0] / 2,
self.ids.im.center_y - self.ids.im.norm_image_size[1] / 2),
size=(self.ids.im.norm_image_size[0]/2,
self.ids.im.norm_image_size[1]/2))
self.bind(pos=self.update_canvas, size=self.update_canvas)
def update_canvas(self, *args):
self.rec.pos = (self.ids.im.center_x - self.ids.im.norm_image_size[0] / 2,
self.ids.im.center_y - self.ids.im.norm_image_size[1] / 2)
self.rec.size = (self.ids.im.norm_image_size[0]/2,
self.ids.im.norm_image_size[1]/2)
class QuestionApp(App):
def build(self):
return Screen()
if __name__ == "__main__":
QuestionApp().run()
I'm doing this in Python because I want to randomly spawn rectangles within the image, but I'm stuck here with rectangles when window resizing being wobbly.
If you want the rectangle to use the geometry of the image as reference then you must do the binding with the image, for this change:
self.bind(pos=self.update_canvas, size=self.update_canvas)
to
self.ids.im.bind(pos=self.update_canvas, size=self.update_canvas)