ChewyCarrot
ChewyCarrot

Reputation: 220

Adding text to a rectangle that can be resized, and moved on Pygame without addons

I want to blit text onto a rectangle which can move and readjust its size. I was thinking on making the rectangle a surface and just blitting the text on the surface, but I do not know how to do so :(

The rectangle that can move around and be able to be resized is:

import pygame as pg

pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
rect1 = pg.Rect(100, 100, 161, 100)
rect2 = pg.Rect(300, 200, 161, 100)
selected_rect = None

done = False
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        elif event.type == pg.MOUSEBUTTONDOWN:
            for rect in (rect1, rect2):
                if rect.collidepoint(event.pos):
                    selected_rect = rect  # Select the colliding rect.
        elif event.type == pg.MOUSEBUTTONUP:
            selected_rect = None  # De-select the rect.
        elif event.type == pg.MOUSEMOTION:
            if selected_rect is not None:  # If a rect is selected.
                if event.buttons[0]:  # Left mouse button is down.
                    # Move the rect.
                    selected_rect.x += event.rel[0]
                    selected_rect.y += event.rel[1]
                else:  # Right or middle mouse button.
                    # Scale the rect.
                    selected_rect.w += event.rel[0]
                    selected_rect.h += event.rel[1]
                    selected_rect.w = max(selected_rect.w, 10)
                    selected_rect.h = max(selected_rect.h, 10)

    screen.fill((30, 30, 30))
    pg.draw.rect(screen, (0, 100, 250), rect1)
    pg.draw.rect(screen, (0, 200, 120), rect2)
    pg.display.flip()
    clock.tick(30)

Also if possible can anyone help me with the rectangles a bit, They seem to be able to move off the screen, how is it possible to make the screen size the border and make the rectangles bounce off of it?

Upvotes: 2

Views: 1436

Answers (2)

skrx
skrx

Reputation: 20478

Here's a basic solution. I start by splitting the text into separate words. Then, to create the lines, I add one word after the other to an intermediary list (line) and use the pygame.font.Font.size method to get the size of the word which I add to the line_width variable. When the line_width exceeds the rectangle width, I use the words in the line list to render a text surface and append it to the self.images list.

To blit the text surfaces, I enumerate the self.images and then multiply the index by the height of the font to shift the surfaces.

import pygame as pg


class TextBox:

    def __init__(self, text, pos, font, bg_color, text_color=(255, 255, 255)):
        self.font = font
        self.font_height = font.get_linesize()
        self.text = text.split()  # Single words.
        self.rect = pg.Rect(pos, (200, 200))
        self.bg_color = bg_color
        self.text_color = text_color
        self.render_text_surfaces()

    def render_text_surfaces(self):
        """Create a new text images list when the rect gets scaled."""
        self.images = []  # The text surfaces.
        line_width = 0
        line = []
        space_width = self.font.size(' ')[0]

        # Put the words one after the other into a list if they still
        # fit on the same line, otherwise render the line and append
        # the resulting surface to the self.images list.
        for word in self.text:
            line_width += self.font.size(word)[0] + space_width
            # Render a line if the line width is greater than the rect width.
            if line_width > self.rect.w:
                surf = self.font.render(' '.join(line), True, self.text_color)
                self.images.append(surf)
                line = []
                line_width = self.font.size(word)[0] + space_width

            line.append(word)

        # Need to render the last line as well.
        surf = self.font.render(' '.join(line), True, self.text_color)
        self.images.append(surf)

    def draw(self, screen):
        """Draw the rect and the separate text images."""
        pg.draw.rect(screen, self.bg_color, self.rect)

        for y, surf in enumerate(self.images):
            # Don't blit below the rect area.
            if y * self.font_height + self.font_height > self.rect.h:
                break
            screen.blit(surf, (self.rect.x, self.rect.y+y*self.font_height))

    def scale(self, rel):
        self.rect.w += rel[0]
        self.rect.h += rel[1]
        self.rect.w = max(self.rect.w, 30)  # 30 px is the minimum width.
        self.rect.h = max(self.rect.h, 30)
        self.render_text_surfaces()

    def move(self, rel):
        self.rect.move_ip(rel)
        self.rect.clamp_ip(screen.get_rect())


text = """Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum."""
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
FONT = pg.font.Font(None, 34)
selected_box = None
textbox = TextBox(text, (50, 50), FONT, (20, 50, 120))
textbox2 = TextBox(text, (350, 100), pg.font.Font(None, 22), (20, 80, 60))

done = False
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        elif event.type == pg.MOUSEBUTTONDOWN:
            for box in (textbox, textbox2):
                if box.rect.collidepoint(event.pos):
                    selected_box = box  # Select the colliding box.
        elif event.type == pg.MOUSEBUTTONUP:
            selected_box = None  # De-select the box.
        elif event.type == pg.MOUSEMOTION:
            if selected_box is not None:  # If a box is selected.
                if event.buttons[0]:  # Left mouse button is down.
                    selected_box.move(event.rel)
                else:
                    selected_box.scale(event.rel)

    screen.fill((30, 30, 30))
    textbox.draw(screen)
    textbox2.draw(screen)
    pg.display.flip()
    clock.tick(60)

There are still some things that need to be improved, but I leave that to you. For example:

  • Words that are wider than the width of the rect stick out.
  • Paragraphs are ignored.
  • I just omit lines that are below the rect.
  • If you want to change the text, you should add a getter and a setter method or properties in which you call the render_text_surfaces method to update the surfaces.

Upvotes: 2

Will Keeling
Will Keeling

Reputation: 23024

I can recommend pygame-text which simplifies drawing text onto a surface.

Given a surface:

surface = pg.Surface((200, 100))

You can add text easily enough:

import ptext  # Grab the module from Github

ptext.draw("hello world", (20, 100), surf=surface)
screen.get_surface().blit(surface, (10, 10))

Plenty more info on the above link.

Upvotes: 0

Related Questions