How to center a text horizontally in a TextInput in Kivy?
I have the following screen:
But I want to centralize my text like this:
And this is part of my kv language:
BoxLayout:
orientation: 'vertical'
Label:
markup: True
text: '[b] Type something... [/b]'
size_hint: 1, 0.6
size: self.parent.size[0], 200
font_size: self.size[0] * 0.1
text_size: self.size
halign: 'center'
valign: 'middle'
canvas.before:
Color:
rgb: 0, 0, 204
Rectangle:
pos: self.pos
size: self.size
TextInput:
focus: True
How can I center my TextInput's text?
Afaik, there's no such thing as aligning in the same way as it's in Label, however, you can use padding to push the position wherever you want. Keep in mind that changing the size of text will affect the centering, therefore you'll need to recalculate on change of size (e.g. when working with multiple devices, sizes, etc).
Or there could be even a workaround, where you could make TextInput invisible, use Label to fetch touch event to trigger TextInput (which would open keyboard) and change Label's text on change of TextInput's text property. You'll lose the possibility to work with cursor this way and you'll need to handle wrapping text.
Example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<Test>:
TextInput:
text: 't'
font_size: 60
# left, right
padding_x:
[self.center[0] - self._get_text_width(max(self._lines, key=len), self.tab_width, self._label_cached) / 2.0,
0] if self.text else [self.center[0], 0]
# top, bottom
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
''')
class Test(BoxLayout):
pass
class TestApp(App):
def build(self):
return Test()
TestApp().run()
self._get_text_width(...) is obviously a method of a TextInput. It's working with the core of the widget so it might be unstable (first example I posted was buggy because of my mistake) ^^
Now if padding_x's values padd from left and right, you'll need only the left side (the difference is only in using addition and substraction in the right place), so let's do that:
get the longest substring in the TextInput
get its width (because it's not consistent!) via the fancy method
substract from center[0] coordinate
When we've already centered the X axis, let's go to the Y. padding_y's values are top and bottom:
padd down the half of the widget's height
get a half of a height of a single line
multiply the number by the count of lines that are in TextInput
substract the number from self.height / 2.0
bottom is 0, we don't care about it
Note: max() expects some arguments and if there's no text, max() will raise its voice. We'll shut it with an alternative left padding for padding_x using only the center:
<padding_x with max> if self.text else [self.center[0], 0]
#pete halign: 'center' plus the padding on y axis only does the job as well
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<Test>:
TextInput:
text: 't'
halign: 'center'
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
''')
class Test(BoxLayout):
pass
class TestApp(App):
def build(self):
return Test()
TestApp().run()
Simply use halign: "center" , in your TextInput
Related
The same code for python layout returns different GUI. I'm terribly confused:
# ---------- VQCIA.kv ----------
VQCIA:
<VQCIA>:
orientation: "vertical"
goi: goi
padding: 10
spacing: 10
size: 400, 200
pos: 200, 200
size_hint:None,None
BoxLayout:
Label:
text: "Enter gene of interest with TAIR ID:"
font_size: '25sp'
BoxLayout:
TextInput:
hint_text: 'AT3G20770'
multiline: False
font_size: '25sp'
id: goi
BoxLayout:
Button:
text: "Submit"
size_hint_x: 15
on_press: root.submit_goi()
# ---------- vqcia.py ----------
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
class VQCIA(BoxLayout):
# Connects the value in the TextInput widget to these
# fields
goi = ObjectProperty()
def submit_goi(self):
# Get the student name from the TextInputs
goi = self.goi.text
print goi
return
class VQCIAApp(App):
def build(self):
return VQCIA()
dbApp = VQCIAApp()
dbApp.run()
my lab computer is macOS Sierra 10.12.6 with Kivy==1.10.1 and has ideal output:
on the other side my personal mac, macOS high Sierra 10.13.6 with Kivy==1.10.1, has wrong outputs:
what happens?
Try using Density-independent Pixels, dp.
In kv file, there are two roots. There is a root rule, VQCIA: and also a class rule, <VQCIA>: for the root. Since in Python code, it is using return VQCIA(), which is associated to class rule, <VQCIA>: in kv file. Therefore, remove root rule, VQCIA: to avoid confusion.
kv file - vqcia.kv
<VQCIA>: # class rule for root widget
orientation: "vertical"
goi: goi
padding: dp(10)
spacing: dp(10)
size: dp(400), dp(200)
pos: dp(200), dp(200)
size_hint: None, None
Dimensions
dp
Density-independent Pixels - An abstract unit that is based on the
physical density of the screen. With a density of 1, 1dp is equal to
1px. When running on a higher density screen, the number of pixels
used to draw 1dp is scaled up a factor appropriate to the screen’s
dpi, and the inverse for a lower dpi. The ratio of dp-to-pixels will
change with the screen density, but not necessarily in direct
proportion. Using the dp unit is a simple solution to making the view
dimensions in your layout resize properly for different screen
densities. In others words, it provides consistency for the real-world
size of your UI across different devices.
sp
Scale-independent Pixels - This is like the dp unit, but it is also
scaled by the user’s font size preference. We recommend you use this
unit when specifying font sizes, so the font size will be adjusted to
both the screen density and the user’s preference.
I am making an app in which, on one screen, I want buttons stacked along the right edge of the screen(for which I need stack layout) and 2 buttons at the centre of the screen(for this I want to use float layout). I have searched for it but nowhere I can see any examples of using two different layouts on one screen.
Can we use two different layouts on a screen? if yes how can we do that?
hers a sample code-
from kivy.uix.stacklayout import StackLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class screen_1(Screen,Stacklayout): ''' here I tried to inherit
floatlayout, but i guess it
doesnt work that way'''
pass
class main(App):
def build(self):
return screen_1()
m = main()
m.run()
kivy code-
<screen_1>:
StackLayout:
orientation: 'tb-rl'
spacing: 10
padding: 90
TextInput:
text: "write your word here"
color: 1,1,1,1
id: word_input
width: 300
size_hint: None, .10
stackLayout:
orientation: 'rl-tb'
spacing: 10
padding: 90
TextInput:
text: "write your word here"
color: 1,1,1,1
Layouts, lake all widgets, can be nested.
If you want two layouts in your screen, you just put them in an appropriate layout to manage their respective size/positions.
First, you likely don't want to do multiple-inheritance with widgets, at least not inheriting from multiple widgets (inheriting from one widget and one or multiple other objects can be fine, but inheriting from multiple widgets will cause issues). Also, Screen is already a RelativeLayout[1] which is almost the same as a FloatLayout (only it makes the pos coordinates of children relative to its own).
Instead, what you want is to compose (by nesting).
Screen:
StackLayout:
size_hint: .5, 1 # let's take half of the screen in width, and all of it in height
# whatever you want inside this layout
BoxLayout:
size_hint: .5, 1 # same, half of the width, all of the height
pos_hint: {'right': 1} # stick to the right of the screen
Button:
[1] https://kivy.org/docs/api-kivy.uix.screenmanager.html?highlight=screen#kivy.uix.screenmanager.Screen
[2] https://kivy.org/docs/api-kivy.uix.relativelayout.html#kivy.uix.relativelayout.RelativeLayout
I will say first off I have tried every single example on the web involving kv lang. Not once have I had any success.
The idea is pretty simple: As I swipe up/down/scroll the contents of GridLayout() within ScrollView() are scrolled up or down.
The best I have been able to do is have the scroll bar fade into view when running the program. Not able to scroll unfortunately.
<Root>
grid_layout: grid_layout
ScreenManager:
...
Screen:
...
ScrollView:
GridLayout:
id: grid_layout
size_hint_y: None
cols: 1
height: self.minimum_height
<list of buttons>
Binding minimum_heightin the __init__ method of the root class (RelativeLayout):
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
I have followed https://github.com/kivy/kivy/blob/master/examples/widgets/scrollview.py converting it to kv lang - scroll bar visible, unable to scroll. Also tried every example on Google Groups and here related to using kv lang. Still no scroll :\
Compiling using buildozer and running on Android fails for an unknown reason.
I would appreciate any assistance that can be given.. I am completely clueless at this point
This:
height: self.minimum_height
should be:
minimum_height: self.height
This is unnecessary:
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
It also won't scroll unless the contents are larger than the scrollview's height:
Full code:
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
Builder.load_string('''
<Root>:
ScrollView:
size_hint: 1, .1
# setting the width of the scrollbar to 50pixels
bar_width: 50
# setting the color of the active bar using rgba
bar_color: 5, 10, 15, .8
# setting the color of the inactive bar using rgba
bar_inactive_color: 5, 20, 10, .5
# setting the content only to scroll via bar, not content
scroll_type: ['bars']
GridLayout:
size_hint_y: None
cols: 1
minimum_height: self.height
Button
text: 'one'
Button:
text: 'two'
Button:
text: 'three'
Button:
text: 'four'
''')
class Root(FloatLayout):
pass
class DemoApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DemoApp().run()
Being unable to scroll was due to a misunderstanding of Kivy's touch handlers. Completely unrelated to the code mentioned in my question.
The key is to have GridLayout be larger than ScrollView, so GridLayout can be panned within ScrollView.
For those wanting to use ScrollView inside ScreenManager using kvlang only:
ScreenManager:
id: screen_manager
Screen:
manager: screen_manager
id: main_screen
name: 'main'
ScrollView:
bar_width: 4
# pos_hint defaults to 1,1 so no need to declare it
GridLayout:
size_hint_y: None
cols: 1
# you do not need to manually bind to setter('height') in
# python - perfectly possible with kv lang
# this allows for height to update depending on the
# collective heights of its child widgets
height: self.minimum_height
<----- widgets here ----->
# for scroll to show/work there must be more widgets
# then can fit root.height. If not there is no need
# for scrollview :)
When I build an application using kivy, I can get everything where I want it, but when I rotate the screen from portrait to landscape, my widgets start colliding with one another. What is a good way to prevent this from happening?
In the attached example, I was able to bind the placement of a settings button to my header label widget, but I was unsuccessful in getting my scrollview to bind to it so when it rotates it stays X amount of space away from the bottom of the label.
Python:
class First_Screen(Screen):
def __init__(self, **kwargs):
super(First_Screen, self).__init__(**kwargs)
list_of_buttons = [
'button1',
'button2',
'button3',
'button4',
'button5',
'button6 ',
'button7',
'button8',
]
class My_Grid(FloatLayout, ScrollView):
grid = ObjectProperty(None)
def on_grid(self, *args):
for btn in list_of_buttons:
btn = Button(
text = btn,
)
btn.bind(on_press= First_Screen.print_message)
self.grid.add_widget(btn)
def print_message(self):
print ('Congratulations! You have clicked a button!')
class ScreenManagement(ScreenManager):
pass
class Stack_Overflow(App):
def build(self):
sm = ScreenManager(transition = FadeTransition())
sc0 = First_Screen(name = 'first_screen')
sm.add_widget(sc0)
return sm
if __name__ == '__main__':
Stack_Overflow().run()
kv:
ScreenManagement:
First_Screen:
<ImageButton#ButtonBehavior+Image>:
<First_Screen>:
name: 'first_screen'
Label:
id: header
text: 'header'
pos_hint: ({'left' : 1, 'top' : 1})
size_hint: (1,None)
height: dp(50)
canvas.before:
Color:
rgba: (.6, .6, .6, 1)
Rectangle:
pos: self.pos
size: self.size
ScrollView:
size_hint: (1,.8)
pos_hint: ({'center_x' : .5, 'center_y' : .5})
My_Grid:
grid: grid
GridLayout:
id: grid
cols: 1
size_hint_y: None
row_default_height: "40dp"
height: self.minimum_height
ImageButton:
id: settings
size_hint: None, None
height: dp(30)
width: dp(30)
pos: header.x, header.y + 10
pos_hint: {'right': 1}
source: 'settings_black.png'
Images: portrait, landscape
Thanks!
Edit: I misunderstood the question, I thought you were unsatisfied with the Image. In the case of ScrollView the problem isn't the ScrollView itself, because that uses relative positions and sizes.
The problem is the Label(header), which has height set as an absolute number i.e. 50 dense pixels. It means that whatever screen you have or however you size anything else, this widget will have 50 pixels corrected with dpi of the screen.
In reality this means that if your screen reaches 400x100 resolution, your Label will take half of the screen and ScrollView will be placed on top of that (most likely you won't see the Label then).
To fix that you have two options. If you want to play with FloatLayout alone, you'll need to use correct positioning/sizing i.e. relative and make sure widgets aren't overlaping.
Otherwise just drop the widgets to BoxLayout or a similar other layout that manages such things on its own:
class First_Screen(BoxLayout, Screen):
def __init__(self, **kwargs):
super(First_Screen, self).__init__(orientation='vertical', **kwargs)
or a little bit different (and better looking):
class First_Screen(BoxLayout, Screen):
def __init__(self, **kwargs):
and in kv:
<First_Screen>:
name: 'first_screen'
orientation: 'vertical'
Also note, that there's a screen module, which makes testing such things a way more easier than dropping the code to apk and to android when it's just unnecessary. Example:
python main.py -m screen:note2,landscape,scale=.5
This seems like something that would be useful, and maybe I've missed it, but does a Kivy Textinput have a method that is equivalent to tkinter's Text.see(END)? I've looked at do_cursor_movement, but that just moves the cursor, not what is displayed.
I've found that the problem is solved by working with the ScrollView that contains the TextInput. With the following in a .kv file
ScrollView:
id: scroll1
size_hint_x: 0.6
TextInput:
readonly: True
id: main
size_hint: 1, None
height: max(self.minimum_height, scroll1.height)
Then all one needs to do is call self.ids['scroll1'].scroll_y = 0 This will scroll the TextInput to the bottom.
I've found a bug with the cursor not being set properly vs the display on start up - in my example below, after adding the lines to the TextInput the cursor reads as (0, 100) even though the top of the text is actually displayed. This causes the Bottom button to do nothing - until you click in the TextInput (changing the cursor pos) or hit the Top button.
To see what I mean, just comment out Clock.schedule_once(lambda _: setattr(root.ids['ti'], 'cursor', (0, 0))).
I've tested this code as working with Kivy 1.8.1-dev (git: 1149da6bf26ff5f27536222b4ba6a874456cde6e) on Ubuntu 14.04:
import kivy
kivy.require('1.8.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
root = Builder.load_string('''
BoxLayout:
orientation: 'vertical'
BoxLayout:
TextInput:
id: ti
Label:
size_hint_x: None
width: sp(80)
text: str(ti.cursor)
BoxLayout:
size_hint_y: None
height: sp(128)
Widget
Button:
text: 'Top'
on_press: ti.cursor = (0, 0)
Button:
text: 'Bottom'
on_press: ti.cursor = (0, len(ti._lines) - 1)
Widget
''')
class TestApp(App):
def build(self):
text = ''
for i in xrange(100):
text += 'Line %d\n' % (i + 1,)
root.ids['ti'].text = text
# fix the cursor pos
Clock.schedule_once(lambda _: setattr(root.ids['ti'], 'cursor', (0, 0)))
return root
if __name__ == '__main__':
TestApp().run()
I'm not familiar to Kivy at all, nor did I test about my answer, but if I understand the docs correctly, by just setting the attribute cursor to the end of the line could do what you want.
Quoting:
cursor
Tuple of (row, col) values indicating the current cursor position.
You can set a new (row, col) if you want to move the cursor. The
scrolling area will be automatically updated to ensure that the cursor
is visible inside the viewport.