I've tried seek in the internet solving my problem. But it has no result at all.
So. Please look at this code sample:
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
btn = Button(text='Hello world')
btn.size_hint = (1, .3)
btn.pos_hint = {'top':1}
title = Label(text=('[color=ff3333]Hello world[/color]'),
font_size=str(12) + 'sp', markup=True)
self.add_widget(title)
self.add_widget(btn)
title.texture_update()
title.text_size = (Window.width, None)
title.height = title.texture_size[1]
with title.canvas:
Color(1., 1., 0)
Rectangle(size=title.size, pos=title.pos)
print(title.size)
print(title.pos)
print(title.texture_size)
And now look at image:
Can anyone tell me why print(title.pos) say (0,0), canvas draw rectangle at (0,0) but text appear at another position?
I've already overwhelmed with this...
Thank you.
You need to see differences between a Widget as a "container" like thing and its content - what's drawn on the canvas. A Widget needs to take some space and so it does.
The important defaults for a Widget are:
position [0, 0]
size_hint [1, 1]
size [100, 100]
What you did:
change text size to [800 (by default), None]
title.text_size = (Window.width, None)
set height of a Widget to take the same height as the rendered text has
title.height = title.texture_size[1]
These changes didn't do anything with the container, because you forgot a basic thing in this layout, which is:
FloatLayout honors the pos_hint and the size_hint properties of its children.
Therefore either put size_hint_y=None into kwargs or do title.size_hint_y=None before setting height for the first time. When size_hint is properly set/removed from the way, you can manipulate the container, which if used correctly:
title = Label(...)
title.size_hint=[None, None]
title.size = title.texture_size
makes the container encapsulate the rendered text. This makes the rendered text's position the same position which has the container (Widget).
Note: When encountering similar stuff, printing is nice, yet not as useful as using Inspector module, mainly for positioning/sizing/<any layout related thing> debugging:
python main.py -m inspector
the title Label is located inside of the FloatLayout Widget where title.canvas is drawing from. This is why the title.pos is (0,0). title.text is the actual position you are looking for, but the problem with that is title.text is a string and doesn't have a pos attribute :/
Related
I have designed the following in figma. I have created a 500px x 500px window and a widget. I want to place the "in motion" text 160px from the left and 207px from the top.
figma design
For images, that works well, but for text, it does not work at all. This is the code
class ScreenOne(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.gif = Image(source='gifs/ring.gif', pos=(23, 191), size=(126, 119), anim_delay=0.1)
self.gif0 = Image(source='gifs/test500.gif', pos=(0, 0), size=(500, 500), anim_delay=0.1)
self.text1 = Label(text="1", pos=(77, 228), font_name="Inter-Bold", font_size=36)
self.text2 = Label(text="in motion", pos=(160, 207), pos_hint=(0,0), size_hint=(0,0), font_name="Inter-Bold", font_size=64)
self.add_widget(self.gif0)
self.add_widget(self.gif)
self.add_widget(self.text1)
self.add_widget(self.text2)
The ring gif (gif) is perfectly located, but the text2 is completely off. See below:
Anyone any idea how to place the text labels exactly how I want them to be placed in Figma?
Problem text in the wrong area
tried to use the labels pos_hint=(0,0), size_hint=(0,0) etc.
The problem is that the size of the text2 Label is the default size, giving it a width of 100. So the actual text will not fit in that size and gets centered about the actual Label size. So the result is that the Label is correctly positioned, but the text extends beyond that default size.
The fix is to adjust the Label size to correctly reflect what you would expect.
Try adding to your code:
class MyLabel(Label):
pass
Builder.load_string('''
<MyLabel>:
size_hint: None, None
size: self.texture_size
''')
Then use MyLabel in place of Label elsewhere in your code.
Excellent -> This worked #John. Thank you very much. I implemented it in the following way using the dimensions in Figma and added the size attribute in the following way:
self.text2 = Label(text="in motion", pos=(160, 207), pos_hint=(0,0), size_hint=(0,0), size(296, 65), font_name="Inter-Bold", font_size=64)
BTW, thanks to everyone at stackoverflow for being so supportive.
Thanks to crystal clear help from 'John Anderson' I've learned a megaton about using splitters, layouts & widgets in Kivy.
I've achieved the desired 'look' of my GUI thus far, but ran into an odd quirk that eludes me. My buttons stopped accepting clicks.
If one looks closely at the depiction of my GUI below, the buttons named 'White' & 'Black' have a line inside them through the text. When I pull the horizontal splitter bar down enough, the lines in the button texts move as well until they disappear causing my buttons to accept clicks again. I know it must have something to do with the layouts, but don't see how to fix it. Can someone explain what's going on?
Side note:--The positioning of widgets inside layouts within splitters is more convoluted than expected since any adjustments of size_hints & pos_hints, spacing and padding affect each other.
Here's my GUI:
Here's the code:
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.splitter import Splitter
from kivy.uix.image import Image
kivy.require('2.0.0')
class ChessBoardWidget(RelativeLayout):
def __init__(self, **kwargs):
super(ChessBoardWidget, self).__init__(**kwargs)
# adjust where the left side of vertical layout starts by changing 'center_x'
repertoire_boxlayout_vert = BoxLayout(orientation='vertical', size_hint_y=.05,
pos_hint={'center_x': .774}) # >center_x moves right
# Padding between layout box and children: [padding_left, padding_top, padding_right, padding_bottom]
# Padding puts space between widgets and the edge of layout holding the widgets
# Spacing puts space between the widgets inside a layout
repertoire_boxlayout_horz = BoxLayout(orientation='horizontal', size_hint=(.45, .05),
spacing=10, padding=[0, 0, 0, 30])
repertoire_boxlayout_horz.add_widget(Label(text='Repertoire for:', size_hint=(.08, 1)))
repertoire_boxlayout_horz.add_widget(Button(text='White', size_hint=(.04, 1)))
repertoire_boxlayout_horz.add_widget(Button(text='Black', size_hint=(.04, 1)))
repertoire_boxlayout_vert.add_widget(repertoire_boxlayout_horz)
chessboard_gui_boxlayout = BoxLayout(orientation='vertical', spacing=40)
chessboard_gui_boxlayout.add_widget(
Image(source="./data/images/chess-pieces/DarkerGreenGreyChessBoard.png", pos=self.pos, keep_ratio=True,
allow_stretch=True)) # default size_hint of (1,1) claims all of remaining height
chessboard_gui_boxlayout.add_widget(repertoire_boxlayout_vert)
self.add_widget(chessboard_gui_boxlayout)
class SplitterGui(BoxLayout):
def __init__(self, **kwargs):
super(SplitterGui, self).__init__(**kwargs)
self.orientation = 'horizontal'
# Splitter 1
split1_boxlayout = BoxLayout(orientation='vertical')
split1 = Splitter(sizable_from='bottom', min_size=74,
max_size=1100, size_hint=(1, .8)) # size_hint=(..,y is smaller, bar moves up
chessboard_widget = ChessBoardWidget()
split1.add_widget(chessboard_widget)
split1_boxlayout.add_widget(split1)
s3_button = Button(text='s3', size_hint=(1, 1))
split1_boxlayout.add_widget(s3_button)
self.add_widget(split1_boxlayout)
# Splitter 2
split2 = Splitter(sizable_from='left', min_size=74,
max_size=1800, size_hint=(3.33, 1)) # size_hint=(x is larger, bar moves left
s2_button = Button(text='s2', size_hint=(.1, 1))
split2.add_widget(s2_button)
self.add_widget(split2)
class ChessBoxApp(App):
def build(self):
return SplitterGui() # root
if __name__ == '__main__':
ChessBoxApp().run()
The problem is that your repertoire_boxlayout_horz with its size_hint, spacing and padding settings leaves no space for the Buttons and Label. So those three widgets end up with a height of 0 and that results in mouse click calculations saying that the clicks do not occur on those widgets. Even though those three widgets have a height of 0, The text and background is still drawn for each. Possible fixes are to eliminate the spacing and padding settings, or set size_hint_y to None for the repertoire_boxlayout_horz with a specified height that allows for those settings, or switch to kv to define your GUI where you can use the minimum_height property of BoxLayout.
I have made a popup in Kivy, but I'm not sure how to edit the size of the content and label. When I shrink the window down, the content extends off the side of the popup; I want the content (and label) to dynamically shrink as I shrink the window.
Picture of Problem
How might I go about doing this? Here's the code for my popup:
def invalidtop1():
toppop1 = Popup(title = 'Invalid Input', size_hint=(.8, .4), content = Label(text = 'Must have J = |j1 - j2|, |j1 - j2| + 1, ..., j1 + j2.'))
toppop1.open()
As a first cut, you can make the text_size of the Label follow the size of the Label. That will force the text of the Label to be drawn within the Label. You can define a custom Label:
class MyLabel(Label):
pass
Then define a kv rule for that new class:
Builder.load_string('''
<MyLabel>:
text_size: self.size
''')
The above accomplishes binding the text_size to the size of MyLabel. Then use MyLabel in place of Label in your Popup:
toppop1 = Popup(title = 'Invalid Input', size_hint=(.8, .4), content = MyLabel(text = 'Must have J = |j1 - j2|, |j1 - j2| + 1, ..., j1 + j2.'))
You might want to add halign='center', valign='center' to the MyLabel for a bit more pleasing look. This will only help to some extent. A more complex solution would be to calculate the font size needed to fit the text in the Label.
I'm making a table-like widget that displays an image, the file name, and two box-selection areas. I have two objects 'grid_row' & 'grid_table' (both using QGridLayout), grid_row being a single row and grid_table containing x number of grid_rows (I'm designing it like this because it's simply easier to keep track of my custom properties).
The tool looks like this
The final layout is a QVBoxLayout, then from top to bottom, I have QHBoxLayout(the one with a label and combobox), grid_row(for the headers 1,2,3), a scroll_area that contains the grid_table with each one being grid_rows. Lastly another QHBoxLayout for the buttons.
Each grid_row contains a 'image-widget', and two region labels(QLabel). The image widget contains a label(I used setPixmap for display) and a pushbutton. Here are my grid_row and image_widget classes:
class grid_row(QWidget):
def __init__(self, parent=None):
super().__init__()
#self.frame = frame_main()
self.grid_layout = QGridLayout()
self.grid_layout.setSpacing(50)
self.image_widget = image_widget()
self.grid_layout.addWidget(self.image_widget, 0, 0, 1, 1, Qt.AlignHCenter)
self.region_2 = QLabel('null')
self.grid_layout.addWidget(self.region_2, 0, 2, 1, 1, Qt.AlignHCenter)
self.setLayout(self.grid_layout)
self.region_1 = QLabel('null')
self.grid_layout.addWidget(self.region_1, 0, 1, 1, 1, Qt.AlignHCenter)
class image_widget(QWidget):
def __init__(self, parent=None):
super().__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.image_widget_layout = QHBoxLayout()
self.image_widget_label = QLabel()
self.image_widget_label.setPixmap(QPixmap('default.png').scaled(96, 54))
self.image_widget_layout.addWidget(self.image_widget_label)
self.img_btn = QPushButton()
self.img_btn.setEnabled(False)
self.img_btn.setText('Drag Here!')
self.image_widget_layout.addWidget(self.img_btn)
self.setLayout(self.image_widget_layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = QWidget()
layout = QVBoxLayout()
grid_row = grid_row()
layout.addWidget(grid_row)
btn = QPushButton('press')
btn.clicked.connect(lambda: grid_row.region_1.setText('[0,0,1920,1080]'))
layout.addWidget(btn)
widget.setLayout(layout)
scroll_area = QScrollArea()
scroll_area.setWidget(widget)
scroll_area.show()
sys.exit(app.exec_())
So currently, I've implemented events that allow me to drag images into the image_widget and click the push button to modify the two regions that are framed (format: [x1, y1, x2, y2]). The problem is that when I do that(e.g. region values go from 'null' to say '[20,20, 500, 500]', the image gets squished because now the labels are taking up more width.
I realize that some size policy needs to be set (and maybe other properties) but I don't know which property to use and on which widget. I want the image to remain the same. Maybe stretch out the width of each column for the grid_row?
To clarify, I want the label containing the pixmap to remain the same size (always 96*54) and fully displayed(not cropped or stretched) at all times.
I've provided the a simplified executable code to display my problem, the classes are the same as my code, I just only put grid_row inside the scroll_area and added a button to change one of the values of the region to simulate the situation. Can provide additional code if needed. Thanks in advance!
Wow sometimes the answer is really one extra line of code...
So the documentation mentions that QScrollArea by default honors the size of its widget. Which is why when I changed the region (to a value that's wider/ more text) the widget does not auto adjust.
I needed to add
scroll_area.setWidgetResizable(True)
to allow the widget to resize wider thus prompting the scroll bars to appear. This way my pixmap image doesn't get cropped from not having enough space.
The easiest way would be to add size constraints to the label before adding to the layout
self.image_widget_label.adjustSize()
self.image_widget_label.setFixedSize(self.image_widget_label.size())
self.image_widget_layout.addWidget(self.image_widget_label)
adjustSize would resize the label depending on the contents.
The more difficult way is to answer the questions :
"when I change the size of the overall window, how do I want this
particular item to behave? When the window is at its minimal size,
which items do I want hidden or out of view? When the window is full
size, where do I want empty spots?"
To answer these better read a bit on Qt Layout management
I am querying Mongolab for the number of documents in a collection, then I want to add one button per document found. I put this code in the on_enter function of a screen:
def on_enter(self):
topicconcepts = db.topicconcepts
topics = topicconcepts.distinct("topic")
for idx, topic in enumerate(topics):
button = Button(text=topic, id=str(idx), buttonPos = idx, size=(self.width,20), pos_hint = {'top': 1}, color= (1,1,1,1), halign='center', seq = 'pre')
# topicBox points to a BoxLayout
self.topicBox.add_widget(button)
However, the layout turns out to be this:
Basically I want the buttons to be more like this:
Any suggestions will be much appreciated :)
If you want to have fixed size buttons in a GridLayout (or a BoxLayout), you have to unset their size_hints. For example, for fixed height of 50dp, without changing width, it would be:
Button:
size_hint_y: None
height: '50dp'
on in py:
from kivy.metrics import dp
Button(size_hint_y=None, height=dp(50))
PS: remember, the buttons might overflow the screen, if too many topics are given. To correct this, use ScrollView widget to scroll them up and down.