I've got a problem with my kivy program... Especially with the width of my root, it's more less than the width of the window...
Like this :
here
I don't understand...
Here my code:
First python file :
from kivy.app import App
from kivy.config import Config
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
Config.set('graphics','width','450')
Config.set('graphics','height','800')
class Saisi(Widget):
pass
class Jeu(Widget):
pass
class WorDown(App):
def build(self):
return Jeu()
if __name__ == '__main__':
WorDown().run()
And my kivy file:
<Saisi>:
canvas:
Rectangle:
pos: self.pos
size: root.width , 50 ← I think, this is it...
<Jeu>:
Saisi:
y: root.height / 2
Someone can help me ? I just want to "resize" the "root width", because all my elements have a max width like this...
Thanks for reading.
<Jeu>:
Saisi:
y: root.height / 2
Jeu is a widget and not a special layout type, so it doesn't impose any position or size on its children, therefore the Saisi instance has the default position of (0, 0) and size of (100, 100).
Make Jeu inherit from e.g. BoxLayout (recommended), or alternatively manually set the Saisi pos/size to match that of the Jeu in the above rule.
Related
I'm making an App in kivy, and I want to be able to drag a widget anywhere in my "tacscreen" how can I do that? I have a GridLayout with an Image in my "tacscreen". Below is my code! I already looked at the documents still couldn't find a solution. Any help is appreciated! Thank You!
main.py
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.clock import Clock
from functools import partial
class StartScreen(Screen):
pass
class TacScreen(Screen):
pass
class MainApp(App):
def on_start(self):
Clock.schedule_once(partial(self.change_screen, "tac_screen"), 5)
def change_screen(self, screen_name, *args):
self.root.current = screen_name
MainApp().run()
tacscreen.kv
#:import utils kivy.utils
<TacScreen>:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Game.png"
GridLayout:
rows: 1
pos: 0, 102
size_hint: 1, .1
Image:
source: "Pencil.png"
In the official Kivy documentation there is an example that you may find helpful.
I'm not familiar with the .kv file so I use kivy.lang.Builder.load_string instead
and one small thing is that seems like the build function which helps build the GUI part is missing ( maybe ) just added
my solution is :
when pointer is touch inside the widget ( by binding on_touch_move as follow ) , the position of the center of the widget ( you can also change it by <widget_instance>.<x / right / y / top / etc> = value ) will be the position of the pointer touch
when releases , the widget will also adjust itself too : ) from the funciton on_touch_up
so the full code will be as follow :
import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager # added ScreenManager
from kivy.clock import Clock
# from functools import partial
kivy.lang.Builder.load_string("""
#:kivy 2.0.0
#:import utils kivy.utils
<StartScreen>: # added
Label:
text: "Hello World !"
font_size: 25
size: self.texture_size
<TacScreen>:
# I skipped the canvas , add it back later lol : )
GridLayout:
id: mygridlayout # add an id to the widget ( a name which represent the widget )
rows: 1
pos: 0, 102
size_hint: 1, .1
Button: # widget
text: "HI !"
# skipped the Image part
""")
class StartScreen(Screen):
pass
class TacScreen(Screen):
def on_touch_up(self, touch):
# to prevent it dragging from out the screen
if (self.ids.mygridlayout.x < 0) or (
self.ids.mygridlayout.right > self.right):
self.ids.mygridlayout.x = 0
if self.ids.mygridlayout.top > self.top:
self.ids.mygridlayout.top = self.top
if self.ids.mygridlayout.y < 0:
self.ids.mygridlayout.y = 0
def on_touch_move(self, touch): # bind when pointer touch + move on screen
# use self.ids.<widget_id> to get the instance of widget
if (self.ids.mygridlayout.x < touch.x < self.ids.mygridlayout.right) and (
self.ids.mygridlayout.top > touch.y > self.ids.mygridlayout.y):
self.ids.mygridlayout.center = (touch.x, touch.y)
class MainApp(App):
screen_manager = ScreenManager()
def on_start(self):
Clock.schedule_once(lambda dt: self.change_screen("tac_screen"), 5)
def change_screen(self, screen_name: str, *args):
# self.root.current = screen_name
self.screen_manager.current = screen_name
def build(self): # we use this method build to return stuff to the GUI window ( maybe )
# add screens to screen manager
self.screen_manager.add_widget(StartScreen(name = "start_screen"))
self.screen_manager.add_widget(TacScreen(name = "tac_screen"))
# screen manager transition direction
self.screen_manager.transition.direction = "up" # left, right, up, down
self.on_start() # maybe you want to call it here ?
return self.screen_manager
MainApp().run()
any suggestions / improvement etc. is welcome : )
iam trying to show the some text right side and some text in left side
here is what i tried :-
from kivy.app import App,runTouchApp
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from io import BytesIO
from PIL import ImageOps, ImageDraw
from PIL import Image as img
from kivy.uix.image import Image, AsyncImage
from kivy.core.image import Image as CoreImage, Texture
from kivy.core.window import Window
from kivy.uix.label import Label
class m(App):
def build(self):
f=FloatLayout()
layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
# Make sure the height is such that there is something to scroll.
layout.bind(minimum_height=layout.setter('height'))
list=[('hhhhbjjggjgjgii',' '),(' ','hgghughuffooeoeo'),(' ','hgghughuffooeoeo'),(' ','hgghughuffooeoeo'),('hhhhbjjggjgjgii\nvhhge\nshh',' '),(' ','hgghughuf\nfooeoeo'),('hhhhbjjggjgjgii',' '),(' ','hgghughuffooeoeo'),('hhhhbjjggjgjgii',' '),(' ','hgghughuffooeoeo'),('hhhhbjj\nggjgjgii',' '),(' ','hgghughuf\nsjsjfooeoeo'),('hhhhbjjg\ngg\nfgg\ngg\ngjgjgii',' '),(' ','hgghughuffooeoeo'),('hhhhbjjggjgjgii',' '),(' ','ab'),('hhhhbjjggjgjgii',' '),(' ','hgghughuffooeoeo')]
for ifu in list:
g=GridLayout(cols=2,rows=1,size_hint_y=None,spacing=200)
if ifu[1]=='ab':
l=Label(text=' ')
g.add_widget(l)
t=Button(background_normal='person-light.png',background_down='person-light.png',size_hint=(1,1))
g.add_widget(t)
else:
if ifu[0]==' ':
l=Label(text=' ')
g.add_widget(l)
ime=Button(text='[color=ffffff]'+ifu[1]+'[/color]',markup=True,size_hint_y=None)
g.add_widget(ime)
else:
ime=Button(text='[color=ffffff]'+ifu[0]+'[/color]',markup=True,size_hint_y=None)
g.add_widget(ime)
l=Label(text=' ')
g.add_widget(l)
layout.add_widget(g)
root = ScrollView(size_hint=(1, None), size=(Window.width, Window.height-100))
root.add_widget(layout)
f.add_widget(root)
return f
m().run()
but it isn't adjusting widtch and hight according to text, i want it to if text is small then width and height of button is also small, and if it is possible make that button rounded at corner's
pls help me out this
You can adjust the size of a Button (or Label) by using its texture_size. You can use:
size_hint: None, None
size: self.texture_size
for a Button or Label, but that make the Button just big enough to hold the text. You can add a few pixels to provide a small space around the text like this in kv:
<MyButt>:
size_hint: None, None
height: self.texture_size[1] + 10
width: self.texture_size[0] + 10
Then in the python code:
class MyButt(Button):
pass
So just load the kv string above and replace usages of Button with MyButt.
You can get rounded buttons by using something like MDFillRoundFlatButton in KivyMD.
by #John Anderson and to adjust the height of GridLayout that of the button i used height: self.minimum_height to avoid overlapping for big buttons
In the following code, there are problems (1) and (2) according to the title.
If kvLang is used as described in this code, the figure (blue ellipse) will be drawn to the expected Cell position (#(1, 1) = upper left).
(However, the character string specified by text: is not displayed at this time. Please tell me how to display text characters .... Problem (1))
I have intended to have coded a Python script to draw with .add_widget method followed to the kvLang.
In this script, the yellow ellipse appears in the lower right instead of the expected Cell position ((2, 2) => lower left) ... problem (2)
For the purpose, it is necessary to add a widget to Grid Laytout using the .add_widget method so that the shape drawn in canvas can be displayed in the cell.
Please tell us how to solve it.
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Ellipse
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.app import App
Builder.load_string('''
<MyGridLayout#GridLayout>:
cols: 2
Label:
text:"From Kv_1" #(1)Not show. What's problem?
canvas:
Color:
rgb:0,0,1
Ellipse:
pos:self.pos
size:self.size
''')
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(Button(text="From_Py_1"))
self.add_widget(Button(text="From_Py_2"))
labl = Button(text="From_Py_canvas_1")
self.add_widget(labl)
with labl.canvas:
# (2) Expected to draw at cell(2,2) which is same locaton as the lable of "From_Py_canvas_1" but not.
Color(rgb=(1, 1, 0))
Ellipse(pos=labl.pos, size_hint=labl.size_hint)
class MyApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
MyApp().run()
Problem 1: You just need to use canvas.before: instead of canvas: in your kv. This draws the ellipse before the text, instead of after (obscuring the text).
Problem 2: Your Ellipse is the using the current properties of the labl, which are default values until the app is displayed. To fix that, create the Ellipse after by using Clock.schedule_once(). In the modified code below, I saved a reference to labl in order to access it in the draw_yellow_ellipse() method:
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Ellipse
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.app import App
Builder.load_string('''
<MyGridLayout#GridLayout>:
cols: 2
Label:
text:"From Kv_1" #(1)Not show. What's problem?
canvas.before:
Color:
rgb:0,0,1
Ellipse:
pos:self.pos
size:self.size
''')
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(Button(text="From_Py_1"))
self.add_widget(Button(text="From_Py_2"))
self.labl = Button(text="From_Py_canvas_1")
self.add_widget(self.labl)
Clock.schedule_once(self.draw_yellow_ellipse)
def draw_yellow_ellipse(self, dt):
with self.labl.canvas:
# (2) Expected to draw at cell(2,2) which is same locaton as the lable of "From_Py_canvas_1" but not.
Color(rgb=(1, 1, 0))
Ellipse(pos=self.labl.pos, size_hint=self.labl.size_hint)
class MyApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
MyApp().run()
Also, I don't think the Ellipse honors size_hint. Perhaps you meant to use size=self.labl.size. And if you do that, you will again have an Ellipse obscuring, in this case, your Button.
I've started playing with Kivy, but I'm rather new to all this and am already struggling.
I'm trying to make a board game - I want the window to be resizeable, but I don't want resizing the window to mess up the aspect ratio of the game (so in other words, I want the window to have black bars above or to the sides of the content if the window is resized to something other than the intended aspect ratio of the content)
The easiest way I could see to ensure this is to either:
a) Lock the aspect ratio of the Window itself so it's always 10:9 (the aspect ratio of the board and all on-screen elements) - even when fullscreen.
or
b) Use some sort of widget/surface/layout that is centered on the window and has a locked 10:9 aspect ratio. I then subsequently use this as the base onto which all my other images, widgets, grids etc are placed.
However, I really don't know how to do either of these. I'm not sure if I can lock the Window aspect ratio, and the only kivy-specific object I've found that lets me lock the aspect ratio is from kivy.graphics.image... which I don't seem to be able to use as a 'base' for my other stuff.
EDIT: So far I've written the code below: it creates a layout (and colours it slightly red) that 'fixes' its own aspect ratio whenever it is resized. However, it still isn't centered in the window, and more problematic, it results in an endless loop (probably because the aspect fix code corrects the size, but then kivy 'corrects' its size back to being the size of the window, triggering the aspect fix again, thought I could be wrong).
EDIT: Modified the code again, but it's still an endless loop. I though that referring only to the parent for size info would fix that, but apparently not.
I'd appreciate anyone helping me fix my code.
Code below:
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Board(AnchorLayout):
pass
class BackgroundLayout(RelativeLayout):
def FixAspectRatio(self, *args):
correctedsize = self.parent.size
if correctedsize[0] > correctedsize[1]*(10/9):
correctedsize[0] = correctedsize[1]*(10/9)
elif correctedsize[0] < correctedsize[1]*(10/9):
correctedsize[1] = correctedsize[0]/(10/9)
return correctedsize
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Board()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Board>
BackgroundLayout:
canvas.before:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: self.pos
size: self.FixAspectRatio(self.parent.size)
pos: self.parent.pos
One approach would be to create a Layout that always maximize the children given an aspect ratio. Here is an example::
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
kv = """
ARLayout:
Widget:
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
pos: self.pos
"""
class ARLayout(RelativeLayout):
# maximize the children given the ratio
ratio = NumericProperty(10 / 9.)
def do_layout(self, *args):
for child in self.children:
self.apply_ratio(child)
super(ARLayout, self).do_layout()
def apply_ratio(self, child):
# ensure the child don't have specification we don't want
child.size_hint = None, None
child.pos_hint = {"center_x": .5, "center_y": .5}
# calculate the new size, ensure one axis doesn't go out of the bounds
w, h = self.size
h2 = w * self.ratio
if h2 > self.height:
w = h / self.ratio
else:
h = h2
child.size = w, h
class TestApp(App):
def build(self):
return Builder.load_string(kv)
TestApp().run()
I managed to find a solution I was happy with with a lot of help from kived from the kivy chat.
The below bases the game bounds on a 'background' image, then places a Relative Layout over this background image. For all subsequent children, the coordinate (0, 0) will refer to the bottom left of the (aspect locked) background image, and they can use the 'magnification' property of the Background to adjust their size/position according to the screen size.
Provided any gamebounds.png image, the below code will implement this. Note that the RelativeLayout is shaded red (with 50% transparency) just to show its position (and show that it matches the gamebounds.png image size and position at all times):
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty,
ListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Game(FloatLayout):
pass
class Background(Image):
offset = ListProperty()
magnification = NumericProperty(0)
class Bounds(RelativeLayout):
pass
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Game()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Game>
Background:
id: BackgroundId
#Provide the image that will form the aspect-locked bounds of the
#game. Image widgets have aspect_lock set to True by default.
source: 'gamebounds.png'
#All the image to stretch to fill the available window space.
allow_stretch: True
size: self.parent.size
#Find the coordinates of the bottom-left corner of the image from
#the bottom-left corner of the window. Call this the 'offset'.
offset: [self.center_x - (self.norm_image_size[0]/2),
self.center_y - (self.norm_image_size[1]/2)]
#Find out the factor by which the image is magnified/shrunk from
#its 'default' value:
magnification: self.norm_image_size[0] / self.texture_size[0]
Bounds:
#The canvas below isn't needed, it's just used to show the
#position of the RelativeLayout
canvas:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: (0, 0)
#Set the position of the RelativeLayout so it starts at the
#bottom left of the image
pos: self.parent.offset
#Set the size of the RelativeLayout to be equal to the size of
#the image in Background
size: self.parent.norm_image_size
Summary of test application: I am writing a Kivy app with a scrollable view (named Scroller) with many fields (named Field) to look at. These separate fields are really difficult to distinguish on occasion, so I decided to use alternating background colors for each field to help distinguish each other. My testing application uses 20 individual fields each of which alternates between dark grey and darker grey.
Testing trials:
Starting the application, the program looks great. The alternating background appear just fine. Even when I scroll down the application looks fine. However, the application seems to get bizarre when I scroll up on the application. The text scrolls with the application, but the background does not. Even better (sarcastically), the text starts to fade away into their neighbors background. The problem just seems to vanish when I scroll down again (passed the point of the furthest scroll up point).
Brief problem description: The Field's "background color" messes up the application during scrolling up events.
Side note: I have also noticed that the application got a little sluggish after scrolling too much. I am not that familiar with the drawing cycle of Kivy, but blitting backgrounds should not yield an excessive slowdown.
Testing application:
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
class Main(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(Field('Test field {}'.format(i),i%2 is 0))
class Field(GridLayout):
def __init__(self, name, bg):
assert isinstance(name, str)
assert isinstance(bg, bool)
self.bg = bg
GridLayout.__init__(self,
rows = 1,
padding = 10,
size = (0, 60),
size_hint = (1, None))
self.add_widget(Label(text = name))
self.add_widget(Button(text = 'Test button',
size = (200, 0),
size_hint = (None, 1)))
self.bind(pos = self.change_background)
self.bind(size = self.change_background)
def change_background(self, *args):
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()
def change_background(self, *args):
self.canvas.before.clear()#<- clear previous instructions
with self.canvas.before:
if self.bg:
Color(0.2, 0.2, 0.2, mode = 'rgb')
else:
Color(0.1, 0.1, 0.1, mode = 'rgb')
Rectangle(pos = self.pos, size = self.size)
You are adding/piling instructions to the canvas every time the Field's position/size changes, without clearing the previous instructions.
You should also look into using kv as for anything more than a small snippet it ends up saving you a lot of time. You can convert you code using kv like so ::
import kivy
kivy.require('1.0.7')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
<Scroller>
# root is Scroller here
# create a new ObjectProperty in kv that holds the ref to Gridlayout
# so you can access the instance in python code
view: glayout
GridLayout:
id: glayout
cols: 1
size_hint: (1, None)
height: self.minimum_height
<Field>
canvas.before:
Color:
rgba: (0.2, 0.2, 0.2, 1) if self.bg else (0.1, 0.1, 0.1, 1)
Rectangle:
# binding properties is done implicitly and instructions aren't
# piled up while doing that.
pos: self.pos
# self here refers to Field as `self` is supposed to refer to the
# Widget not the drawing instruction
size: self.size
rows: 1
padding: 10
size: (0, 60)
size_hint: (1, None)
Label:
text: root.name
Button:
text: 'test button'
size: (200, 0)
size_hint: (None, 1)
''')
class Main(App):
def build(self):
self.root = GridLayout(rows = 1)
self.root.add_widget(Scroller())
return self.root
class Scroller(ScrollView):
def __init__(self, **kwargs):
super(Scroller, self).__init__(**kwargs)
for i in range(20):
# access self.view that was set in kv
self.view.add_widget(
Field(
name = 'Test field {}'.format(i),
bg = i%2 is 0))
class Field(GridLayout):
# use kivy's Properties so it becomes easier to observe and apply changes
# as a plus these can also be directly used in kv. As a advantage of using this now
# you can change name and bg dynamically and the changes should be reflected on
# screen
name = ObjectProperty('Test field uninitialized')
bg = BooleanProperty(False)
if __name__ in ('__main__', '__android__'):
app = Main()
app.run()