I've got a problem; I have boxes with text inside. The boxes have set sizes, but the text in them can chainge. I don't want the text going outside of them. Is there a build-in way or another way to get the text to stop at the edge of an box?
Before you blit text on the screen you can check it dimensions with get_rect() method. And depending on it you can choose whether to display it or not. I made a simple example:
import pygame as pg
pg.init()
clock = pg.time.Clock()
win = pg.display.set_mode((800, 600))
FONT = pg.font.SysFont("Times New Roman", 20, 1, 1)
class Field(pg.Rect):
def __init__(self, rect, text=None):
super().__init__(rect)
self.text = text
def get_text(self):
return self.text
def check_bounds(self, text_rect):
if text_rect.width > self.width:
return False
return True
def redraw(win):
win.fill(pg.Color("white"))
pg.draw.rect(win, pg.Color("green"), button_1, 1)
text = FONT.render("Some textdasdsdsafdss", 1, pg.Color("red"))
if button_1.check_bounds(text.get_rect()):
win.blit(text, (button_1.x, button_1.y))
else:
text = FONT.render("Text is too long!", 1, pg.Color("red"))
win.blit(text, (button_1.x, button_1.y))
pg.display.update()
def main_loop(win):
clock.tick(10)
for event in pg.event.get():
if event.type == pg.QUIT:
return False
redraw(win)
return True
button_1 = Field((100, 100, 170, 70))
while main_loop(win):
pass
pg.quit()
I hope this answers your question
EDIT: Answer for the question in comments by OP
Trim the user input to fit the textbox, add the following:
if len(user_input) >= MAX_STRING_LEN:
user_input = user_input[0:MAX_STRING_LEN]
text = FONT.render(user_input, 1, pg.Color("red"))
user_input is the string you loaded from the file and the MAX_STRING_LEN is a constant. If you have textboxes with different sizes you will need more constants.
In this case you do not need else part which blits "text is too long".
Related
#After clicking the button_next, nothing happens, the interface dosnt change at all
#After i clicked the button_next i would like the text to change which includes the input_name Entry()
def stage_0():
label_welcome = Label(text= "Welcome to Calorie counter", font =("Arial",24,"bold"),fg = "blue", bg ="#FFFF00")
label_welcome.pack(side = "top")
label_name=Label(text ="What is your name?",font = 18, bg ="#FFFF00")
label_name.pack()
input_name = Entry()
input_name.pack()
button_next = Button(text = "next",font = 18,command = name_input)
button_next.pack()
def name_input():
name = input_name.get()
print(name)
if name.isalpha()== True:
label_welcome["text"]= f"Nice to meet you {input_name.get()}!"
button_next.pack_forget()
input_name.pack_forget()
label_name["text"] ="What is your age"
scale_age = Scale(from_=0, to=100, orient = HORIZONTAL)
scale_age.pack()
else:
label_welcome["text"]= "Only alphabets allowed. Please try again"
I am trying to display both an image and a box with an Entry widget. I can do that, but the window is so large that the widget at the bottom is mostly out of view. I have tried several calls to set the window's size or unmaximize it, but they seem to have no effect. I determined that the problem only occurs when the image is large, but still wonder how to display a large image in a resizable window or, for that matter, to make any changes to the window's geometry from code. All the function call I tried seem to have no effect.
Here is my code:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from urllib.request import urlopen
class Display(object):
def __init__(self):
self.window = Gtk.Window()
self.window.connect('destroy', self.destroy)
self.window.set_border_width(10)
# a box underneath would be added every time you do
# vbox.pack_start(new_widget)
vbox = Gtk.VBox()
self.image = Gtk.Image()
response = urlopen('http://1.bp.blogspot.com/-e-rzcjuCpk8/T3H-mSry7PI/AAAAAAAAOrc/Z3XrqSQNrSA/s1600/rubberDuck.jpg').read()
pbuf = GdkPixbuf.PixbufLoader()
pbuf.write(response)
pbuf.close()
self.image.set_from_pixbuf(pbuf.get_pixbuf())
self.window.add(vbox)
vbox.pack_start(self.image, False, False, 0)
self.entry = Gtk.Entry()
vbox.pack_start(self.entry, True,True, 0)
self.image.show()
self.window.show_all()
def main(self):
Gtk.main()
def destroy(self, widget, data=None):
Gtk.main_quit()
a=Display()
a.main()
Most of the posted information seems to pertain to Gtk2 rather than Gtk3, but there is a solution: to use a pix buf loader and set the size:
from gi.repository import Gtk, Gdk, GdkPixbuf
#more stuff
path = config['DEFAULT']['datasets']+'working.png'
with open(path,'rb') as f:
pixels = f.read()
loader = GdkPixbuf.PixbufLoader()
loader.set_size(400,400)
loader.write(pixels)
pb = GdkPixbuf.Pixbuf.new_from_file(path)
self.main_image.set_from_pixbuf(loader.get_pixbuf())
loader.close()
Here is my code, it is copyrighted cited here
Al Sweigart. Invent Your Own Computer Games with Python. August 28, 2015. https://inventwithpython.com/invent4thed/chapter5.html. Accessed December 4, 2020.
#import modules
import random
import time
#def functions
def intro():
guess = int(input("There are two caves, in one is magic treasure and in the other is a
terrible monster. Hit 1 or 2 to guess. "))
return guess
def check_cave(guess):
print("the cave is dank")
time.sleep(2)
print("you hear a sound")
time.sleep(2)
print("a monster jumps out and opens its jaws and...")
time.sleep(2)
friendly_cave = random.randint(1,2)
if str(guess) == str(friendly_cave):
print("leaves so you can take the treasure, congratulations!")
else:
print("kills you bahahaha")
print(guess)
#main body of code
play_again = 'no'
guess = int(input("please enter a guess of one or two "))
while play_again == 'yes' or 'y':
intro()
check_cave(guess)
play_again = input("want to play again? yes or no...")
if play_again == 'yes':
continue
else:
break
I expect the code to ask the user for input from the main body of the code, then ask for same input from inside a function later (the function returns the input), I expect the function's returned value to overwrite the original input. but it doesnt. Instead it keeps the original input from outside the function what can I do about this?
You must assign value returned from function intro() to a variable:
while play_again == 'yes' or 'y':
guess = intro() # modified this line
check_cave(guess)
play_again = input("want to play again? yes or no...")
if play_again == 'yes':
continue
else:
break
In this image:
I would like to access the actual tabs, rather than the content, so I can set a QPropertyAnimation on the actual tab when it is hovered on. I know how to get the hover event working, and I can get the tab index on the hover, I just can't access the actual tab when I hover on it. Is there a list of the tabs somewhere as an attribute of the QTabBar or the QTabWidget, or where can I find the tabs? Or do I have to subclass the addTab function to create the tabs individually?
Extra Info
Using PyQt5.14.1
Windows 10
Python 3.8.0
You cannot access "tabs", as they are not objects, but an abstract representation of the contents of the tab bar list.
The only way to customize their appearance is by subclassing QTabBar and overriding the paintEvent().
In order to add an over effect, you have to provide a unique animation for each tab, so you have to keep track of all tabs that are inserted or removed. The addTab, insertTab and removeTab methods are not valid options, since they are not used by QTabWidget. It uses instead tabInserted() and tabRemoved(), so those are to be overridden too.
This could be a problem with stylesheets, though, especially if you want to set fonts or margins.
Luckily, we can use the qproperty-* declaration with custom PyQt properties, and in the following example I'm using them for the tab colors.
class AnimatedTabBar(QtWidgets.QTabBar):
def __init__(self, *args):
super().__init__(*args)
palette = self.palette()
self._normalColor = palette.color(palette.Dark)
self._hoverColor = palette.color(palette.Mid)
self._selectedColor = palette.color(palette.Light)
self.animations = []
self.lastHoverTab = -1
#QtCore.pyqtProperty(QtGui.QColor)
def normalColor(self):
return self._normalColor
#normalColor.setter
def normalColor(self, color):
self._normalColor = color
for ani in self.animations:
ani.setEndValue(color)
#QtCore.pyqtProperty(QtGui.QColor)
def hoverColor(self):
return self._hoverColor
#hoverColor.setter
def hoverColor(self, color):
self._hoverColor = color
for ani in self.animations:
ani.setStartValue(color)
#QtCore.pyqtProperty(QtGui.QColor)
def selectedColor(self):
return self._selectedColor
#selectedColor.setter
def selectedColor(self, color):
self._selectedColor = color
self.update()
def tabInserted(self, index):
super().tabInserted(index)
ani = QtCore.QVariantAnimation()
ani.setStartValue(self.normalColor)
ani.setEndValue(self.hoverColor)
ani.setDuration(150)
ani.valueChanged.connect(self.update)
self.animations.insert(index, ani)
def tabRemoved(self, index):
super().tabRemoved(index)
ani = self.animations.pop(index)
ani.stop()
ani.deleteLater()
def event(self, event):
if event.type() == QtCore.QEvent.HoverMove:
tab = self.tabAt(event.pos())
if tab != self.lastHoverTab:
if self.lastHoverTab >= 0:
lastAni = self.animations[self.lastHoverTab]
lastAni.setDirection(lastAni.Backward)
lastAni.start()
if tab >= 0:
ani = self.animations[tab]
ani.setDirection(ani.Forward)
ani.start()
self.lastHoverTab = tab
elif event.type() == QtCore.QEvent.Leave:
if self.lastHoverTab >= 0:
lastAni = self.animations[self.lastHoverTab]
lastAni.setDirection(lastAni.Backward)
lastAni.start()
self.lastHoverTab = -1
return super().event(event)
def paintEvent(self, event):
selected = self.currentIndex()
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
style = self.style()
fullTabRect = QtCore.QRect()
tabList = []
for i in range(self.count()):
tab = QtWidgets.QStyleOptionTab()
self.initStyleOption(tab, i)
tabRect = self.tabRect(i)
fullTabRect |= tabRect
if i == selected:
# make the selected tab slightly bigger, but ensure that it's
# still within the tab bar rectangle if it's the first or the last
tabRect.adjust(
-2 if i else 0, 0,
2 if i < self.count() - 1 else 0, 1)
pen = QtCore.Qt.lightGray
brush = self._selectedColor
else:
tabRect.adjust(1, 1, -1, 1)
pen = QtCore.Qt.NoPen
brush = self.animations[i].currentValue()
tabList.append((tab, tabRect, pen, brush))
# move the selected tab to the end, so that it can be painted "over"
if selected >= 0:
tabList.append(tabList.pop(selected))
# ensure that we don't paint over the tab base
margin = max(2, style.pixelMetric(style.PM_TabBarBaseHeight))
qp.setClipRect(fullTabRect.adjusted(0, 0, 0, -margin))
for tab, tabRect, pen, brush in tabList:
qp.setPen(pen)
qp.setBrush(brush)
qp.drawRoundedRect(tabRect, 4, 4)
style.drawControl(style.CE_TabBarTabLabel, tab, qp, self)
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.tabWidget = QtWidgets.QTabWidget()
layout.addWidget(self.tabWidget)
self.tabBar = AnimatedTabBar(self.tabWidget)
self.tabWidget.setTabBar(self.tabBar)
self.tabWidget.addTab(QtWidgets.QCalendarWidget(), 'tab 1')
self.tabWidget.addTab(QtWidgets.QTableWidget(4, 8), 'tab 2')
self.tabWidget.addTab(QtWidgets.QGroupBox('Group'), 'tab 3')
self.tabWidget.addTab(QtWidgets.QGroupBox('Group'), 'tab 4')
self.setStyleSheet('''
QTabBar {
qproperty-hoverColor: rgb(128, 150, 140);
qproperty-normalColor: rgb(150, 198, 170);
qproperty-selectedColor: lightgreen;
}
''')
Some final notes:
I only implemented the top tab bar orientation, if you want to use tabs in the other directions, you'll have change the margins and rectangle adjustments;
remember that using stylesheets will break the appearence of the arrow buttons;(when tabs go beyond the width of the tab bar), you'll need to set them carefully
painting of movable (draggable) tabs is broken;
right now I don't really know how to fix that;
EDIT: After playing around a bit in main(), it seems like the program is choosing whichever function is being called in the if/elif block regardless of input. I have no idea why it started doing this after working fine
I've gone over and over my code, and I can't figure out what I'm not seeing.
It's a number sequence game, and it asks the user to choose a difficulty between easy and hard. It was working just fine at one point, but now no matter what is selected, it goes to easy mode each time. Even you hit enter without any input at all.
#main program function and difficulty selection
def main():
print('-----------------------------------------------')
print('Please choose a difficulty.')
difficulty = str(input('(e)asy|(h)ard: '))
print('-----------------------------------------------')
if difficulty == 'easy'or'e':
easy()
elif difficulty == 'hard'or'h':
hard()
Then I have a function for easy and one for hard.
Hard is just the easy function, with only a change to the size of the sequence generated and nothing else. I've gone over each block and nothing is changed that would affect which function is called.
It happens regardless of how many times the game is played, so it has to be something wrong with my main() function
The rest of the code is here if that helps, maybe I'm missing something obvious.
import random
def easy():
print ('Easy Mode','\n')
#Generates inital number, step value, and upper limit
num_sequence = 5
numbers = random.randint(1,101)
step = random.randint(1,20)
#Accumulates and prints all but last number in sequence
for num_generated in range (1, num_sequence):
print(numbers)
numbers = numbers + step
#Sets tries allowed and subtracts wrong attempts
guesses = 3
while guesses > 0:
user_num = int(input('Next Number: '))
guesses = guesses - 1
if user_num != numbers:
if guesses == 0:
break
else:
print('Try Again (Attempts Remaining:', guesses,')')
if user_num == numbers:
break
#Prints appropriate message based on game results
if user_num == numbers:
print ('Correct','\n')
if user_num != numbers:
print ('Attempts Exceeded: The answer was',numbers,'\n')
#block for hard difficulty (same as above, sequence size changed to 4)
def hard():
print ('Hard Mode','\n')
num_sequence = 4
#main program function and difficulty selection
def main():
print('-----------------------------------------------')
print('Please choose a difficulty.')
difficulty = str(input('(e)asy|(h)ard: '))
print('-----------------------------------------------')
if difficulty == 'easy'or'e':
easy()
elif difficulty == 'hard'or'h':
hard()
#block for replay selection
replay = 'y'
while replay == 'y':
main()
replay = input('Play again? (y)|(n): ',)
print ('\n')
if replay == 'n':
print('-----------------------------------------------')
print('Goodbye!')
print('-----------------------------------------------')
break
hard() is the same code as easy() line for line after those first few
When you are making a compound comparison (using the or) both sides of the condition must be complete. In other words,
if difficulty == 'easy'or difficulty == 'e':
easy()
elif difficulty == 'hard'or difficulty == 'h':
hard()
Otherwise you are saying "if difficulty == 'easy' >> which is false, then assign difficulty to 'e'" which was not the intent.