I try to under stand the different types of coordinates:
Global,
Local,
Window and
Widget
coordinates.
using the program:
class TargetUI(BoxLayout):
js_type = NumericProperty(0)
def __init__(self, **arg):
super(TargetUI, self).__init__(**arg)
btn1 = Button(text='Hello world ' + str(self.js_type))
self.add_widget(btn1)
def on_touch_up(self, touch):
# here, you don't check if the touch collides or things like that.
# you just need to check if it's a grabbed touch event
Logger.info("in touch up")
Logger.info("global coordinates: " + str(touch.pos))
if self.collide_point(*touch.pos):
touch.push()
# if the touch collides with our widget, let's grab it
touch.grab(self)
Logger.info("In widget " + str(self.js_type))
touch.apply_transform_2d(self.to_local)
Logger.info("Local coordinates " + str(touch.pos))
touch.apply_transform_2d(self.to_window)
Logger.info("Windows coordinates " + str(touch.pos))
touch.apply_transform_2d(self.to_widget)
Logger.info("Widget coordinates " + str(touch.pos))
touch.ungrab(self)
# and accept the touch.
return True
class CombWidget(Widget):
pass
class MyPaintApp(App):
def build(self):
return CombWidget()
if __name__ == '__main__':
MyPaintApp().run()
and
#:kivy 1.7.1
<CombWidget>:
tg1: tg1
tg2: tg2
tg3: tg3
BoxLayout:
size: root.size
orientation: 'vertical'
padding: 20
TargetUI:
js_type: 1
id: tg1
TargetUI:
js_type: 2
id: tg2
TargetUI:
id: tg3
js_type: 3
All the coordinates written out by on_touch_up is the same, but expected some difference. Why are are all the coordinates the same?
I also expected to see the Button text to end with 1,2 or 3 but their are all 1. How can I make the Button text be depended in self.js_type?
These are useful when there are coordinate changes. For example, with the scatter widget, here is an example where one of the widgets is put in a Scatter and you can move it (somehow it gets back in place when you click it again, but it's convenient). When you do that, you should see that the coordinates are no longer the same. understanding the difference between them is left as an exercise to the reader :)
from kivy.base import runTouchApp
from kivy.lang import Builder
kv = '''
GridLayout:
cols: 2
spacing: 10
ClickBox
Scatter:
ClickBox:
pos: 0, 0
size: self.parent.size
Widget:
ClickBox:
pos: self.parent.pos
size: self.parent.size
<ClickBox#Widget>:
canvas:
Rectangle:
pos: self.pos
size: self.size
on_touch_move:
if self.collide_point(*args[1].pos): print self.to_window(*args[1].pos)
if self.collide_point(*args[1].pos): print self.to_parent(*args[1].pos)
if self.collide_point(*args[1].pos): print self.to_widget(*args[1].pos)
if self.collide_point(*args[1].pos): print self.to_local(*args[1].pos)
'''
if __name__ == '__main__':
runTouchApp(Builder.load_string(kv))
The Documentation for RelativeLayout clarified this issue for me.
Parent coordinates
Other RelativeLayout type widgets are Scatter, ScatterLayout, and
ScrollView. If such a special widget is in the parent stack, only then
does the parent and local coordinate system diverge from the window
coordinate system. For each such widget in the stack, a coordinate
system with (0, 0) of that coordinate system being at the bottom left
corner of that widget is created. Position and touch coordinates
received and read by a widget are in the coordinate system of the most
recent special widget in its parent stack (not including itself) or in
window coordinates if there are none. We
call these coordinates parent coordinates.
So you must use one of the above special widgets for local coordinates to diverge from window coordinates. Tshirtman's answer works because he used the scatter widget which is one of the special widgets.
Related
I am trying to create the layout pictured below in a kv file. I have tried several different methods to define a 30% region containing a label, but the label always ends up in the bottom left corner of the screen. I suspect I'm misusing size, size_hint and text_size, and/or that I need a containing element for the label (I've tried FloatLayout).
sample.kv
#:kivy 1.0.9
<SampleGui>:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "background.jpeg"
Label:
text: "Illum qui labore quia sed quia sint quaerat."
size_hint: (0.3, 1)
pos_hint: {'right': 1}
sample.py
from kivy.app import App
from kivy.config import Config
from kivy.uix.widget import Widget
Config.set('graphics', 'window_state', 'maximized')
class SampleGui(Widget):
pass
class SampleApp(App):
def build(self):
return SampleGui()
if __name__ == '__main__':
SampleApp().run()
How can I define allocate a 30% width to the label? (Padding and other details are not important, unless they impact the answer.)
And with the added code, your error becomes obvious. Your SampleGui extends Widget, but the child Label uses attributes (like size_hint and pos_hint) that are only supported by Layout classes. Try changing your SampleGui to extend FloatLayout instead of Widget.
I want to add two widgets or maybe three to form a page that show me Message and Take my input message and on pressing send button it pass that message to the send_msg() function Something like in Image:
I know the way to do it in py file but I need code for kv file
Thank You in Advance for your help
Here is The Code:
class ChatPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# We are going to use 1 column and 2 rows
self.cols = 1
self.rows = 2
# First row is going to be occupied by our scrollable label
# We want it be take 90% of app height
self.history = Label(height=Window.size[1]*0.9, size_hint_y=None)
self.add_widget(self.history)
# In the second row, we want to have input fields and Send button
# Input field should take 80% of window width
# We also want to bind button click to send_message method
self.new_message = TextInput(width=Window.size[0]*0.8, size_hint_x=None, multiline=False)
self.send = Button(text="Send")
self.send.bind(on_press=self.send_message)
# To be able to add 2 widgets into a layout with just one column, we use additional layout,
# add widgets there, then add this layout to main layout as second row
bottom_line = GridLayout(cols=2)
bottom_line.add_widget(self.new_message)
bottom_line.add_widget(self.send)
self.add_widget(bottom_line)
# Gets called when either Send button or Enter key is being pressed
# (kivy passes button object here as well, but we don;t care about it)
def send_message(self, _):
print("send a message!!!")
It is difficult to get a widget to take up two columns in a GridLayout. A simpler approach would b to use BoxLayouts:
BoxLayout:
orientation: 'vertical'
ScrollView:
size_hint_y: 0.9
Label:
id: label
size_hint_y: None
height: self.texture_size[1]
BoxLayout:
orientation: 'horizontal'
size_hint_y: 0.1
TextInput:
id: ti
size_hint_x: 0.8
Button:
text: 'send'
size_hint_x: 0.2
I am quite a novice programming with kivy, and I just wanted to put a green rectangle under a label to make it look a little bit like a display. I've tried making a label and then, inside of it, a canvas with the rectangle, but when it's time to put them together, the self.pos doesn't put them on the same place... Here's part of the code:
'''.kv file'''
Label:
id: s_label
size:50,50
text:'[font=Digital-7][color=D20000][size=24] S= [/color][/font][/size]'
markup:True
pos_hint:{'x':0.3,'y':-0.2}
canvas:
canvas.before:
Color:
rgba: 0, 153, 0, 0.5
RoundedRectangle:
pos: self.pos[0] , self.pos[1]
size: (self.width/6, self.width/16)
id: S_rect
The Label is inside a FloatLayout, but I don't know if that is relevant.
this is what I get. See, the green rectangle is far away from the red S label
Two problems with your code:
You must include size_hint: None, None in your Label in the kv. Without that, the size has no effect, and the Label fills its parent.
The pos_hint:{'x':0.3,'y':-0.2} positions the Label below the bottom of the parent. Try something like pos_hint:{'x':0.3,'y':0.2}.
Assuming I define a BoxLayout but want to add, say a blue background to it, in Kivy it would look something like this:
BoxLayout:
canvas.before:
Color:
rgb: 0, 0, 1
Rectangle:
size: self.size
pos: self.pos
I tried to do this in Python like this:
box = BoxLayout()
with box.canvas.before:
Color(rgb=(0, 0, 1))
Rectangle(size=box.size, pos=box.pos)
This does draw a rectangle, but not in the correct size or position. My question is: is there a way to create a Rectangle (or another way to add a background to a BoxLayout) the same size and position of the defined BoxLayout? The closest I have ever gotten is physically setting the size and shape, but I would like this Rectangle to be dynamically resizable so that I don't have too many values hard-coded. Thanks in advance!
Typically, that code would appear in some method of a class, so a reference to the Rectangle could be saved as, for example, self.bg. So it would like something like this:
class MyBackground(FloatLayout):
def __init__(self, **kwargs):
super(MyBackground, self).__init__(**kwargs)
self.box = BoxLayout(size_hint=(0.5, 0.5))
with self.box.canvas.before:
Color(rgba=(1, 0, 0, 1))
self.bg = Rectangle(pos=self.pos, size=self.size)
# bindings to keep size and position of the Rectangle up to date
self.box.bind(pos=self.update_bg)
self.box.bind(size=self.update_bg)
# add box to this layout
self.add_widget(self.box)
def update_bg(self, *args):
self.bg.pos = self.box.pos
self.bg.size = self.box.size
Goal: Within a set of buttons, when one is pressed, the all of the buttons colors are reset to its default color, while the button that was pressed changes color. (so there will only be one button with a different color)
setup .py file
class Profiles(Screen):
def __init__(self, **kwargs):
super(Profiles, self).__init__(**kwargs)
Clock.schedule_once(self.create_profs, -1)
def create_profs(self, *args):
#create buttons and screens for each item in acc_abr
for i in acc_abr:
#AccountOne inherits from kivy's Screen class. Store new screen into a screen manager named "am"
self.ids["am"].add_widget(AccountOne(name=i))
#AccButt inherits from kivy's Button class
self.ids["acc_butt_box"].add_widget(AccButt(text=i, id=i, on_press=partial(self.switching_function, i)))
#change current screen
def switching_function(self, screename, *args):
self.ids["am"].current = screename
The buttons are created within a for loop, and given an id based on the items in acc_abr
What I need to work on is the contents of AccButt which looks like:
class AccButt(Button):
def __init__(self, **kwargs):
super(Button,self).__init__(**kwargs)
self.bind(on_press = self.change_butt_color)
def change_butt_color(self, *args):
self.background_color = 0,0,0,0
First of all, I have access to all the buttons id name since its ids are based on the acc_abr list.
So my first idea was to create a for loop that changed all the buttons color to its default before changing it's selfs color. which looks like this:
def change_butt_color(self, *args):
for butts in acc_abr:
self.parent.ids[butts].background_color = 1,1,1,1
self.background_color = 0,0,0,0
The problem is, I'm getting a key error for whatever is in butts. So I'm having a communication issue with the buttons.
I'm probably getting the wrong path as to where the other buttons are, or I did not properly assign an id to the Buttons (AccButt) or timing is messing things up.
SOLUTION
The path that contains the buttons siblings is self.parent.children. With this information, you can change the background_color of all the buttons.
EXAMPLE
def change_butt_color(self, *args):
for child in self.parent.children:
child.background_color = 1,1,1,1
self.background_color = 0,0,0,0
notice how the buttons aren't being searched for by ids. Instead, self.parent.children is used to find the button's parents children, which is, its self of course and the siblings of its self (self).
Solution
Use on_touch_down, reset background_color, and check for collide_point
Widget Touch Event » on_touch_down
on_touch_down(touch)
Receive a touch down event.
Parameters:
touch: MotionEvent class
Touch received. The touch is in parent coordinates. See
relativelayout for a discussion on coordinate systems.
Returns: bool If True, the dispatching of the touch event will stop. If False, the event will continue to be dispatched to the rest
of the widget tree.
Input Management » Touch event basics
By default, touch events are dispatched to all currently displayed
widgets. This means widgets receive the touch event whether it occurs
within their physical area or not.
...
In order to provide the maximum flexibility, Kivy dispatches the
events to all the widgets and lets them decide how to react to them.
If you only want to respond to touch events inside the widget, you
simply check:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Widget touch event bubbling
When you catch touch events between multiple widgets, you often need
to be aware of the order in which these events are propagated. In
Kivy, events bubble up from the first child upwards through the other
children. If a widget has children, the event is passed through its
children before being passed on to the widget after it.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.lang import Builder
class CreateButton(Button):
def on_touch_down(self, touch):
if set(self.background_color) & set([0, 0, 0, 0]) == {0}:
self.background_color = [1, 1, 1, 1] # default texture is grey
if self.collide_point(*touch.pos):
self.background_color = [0, 0, 0, 0] # black color
class OnTouchDownDemo(GridLayout):
def __init__(self, **kwargs):
super(OnTouchDownDemo, self).__init__(**kwargs)
self.build_board()
def build_board(self):
# make 9 buttons in a grid
for i in range(0, 9):
button = CreateButton(id=str(i))
self.add_widget(button)
Builder.load_file('main1.kv')
class OnTouchDownApp(App):
def build(self):
return OnTouchDownDemo()
if __name__ == "__main__":
OnTouchDownApp().run()
main.kv
#:kivy 1.11.0
<CreateButton>:
font_size: 50
<OnTouchDownDemo>:
rows: 3
cols: 3
row_force_default: True
row_default_height: 150
col_force_default: True
col_default_width: 150
padding: [10]
spacing: [10]
Output