Shaobin Jiang
Shaobin Jiang

Reputation: 310

How do I enable Chinese input in pygame?

What I want to do is this: using pinyin input method to input Chinese characters in pygame.

The only way to do this I have figured out is: reading the individual keyboard input, and then use some libraries like Pinyin2Hanzi to convert the strings of letters into a list of possible Chinese characters, quite like how RockStar deals with Chinese input in RDR2. An example of this current solution is provided below:

The problem is, this is entirely different from one's usual way of inputing Chinese characters. Normally, when we input Chinese using the pinyin input method, the available options is listed by the input method itself, like below:

So, one other way of describing my question can be: how do I enable the pinyin input method in pygame and reading the Chinese character I decide to input rather than the combination of English characters that constitute its pinyin?

Upvotes: 2

Views: 504

Answers (1)

Starbuck5
Starbuck5

Reputation: 1874

This IME stuff is not my cup of tea, but I've done some research and I've got you.

This is a modified version of pygame's example "textinput.py", with a patch that makes the candidate window open. I've submitted the patch to pygame on Github. You should also use the latest copy of pygame you can get your hands on for best results. I don't believe 2.1.2 will work for this. Run pip install pygame --upgrade --pre. The --pre gets you the latest pre release version.

#!/usr/bin/env python
""" pg.examples.textinput

A little "console" where you can write in text.

Shows how to use the TEXTEDITING and TEXTINPUT events.
"""
import sys
import os

import pygame
import pygame as pg
import pygame.freetype as freetype

""" This is very important
    If not added the candidate list will not show """
os.environ["SDL_IME_SHOW_UI"] = "1"

class TextInput:
    """
    A simple TextInput class that allows you to receive inputs in pygame.
    """

    # Add font name for each language,
    # otherwise some text can't be correctly displayed.
    FONT_NAMES = [
        "notosanscjktcregular",
        "notosansmonocjktcregular",
        "notosansregular,",
        "microsoftjhengheimicrosoftjhengheiuilight",
        "microsoftyaheimicrosoftyaheiuilight",
        "msgothicmsuigothicmspgothic",
        "msmincho",
        "Arial",
    ]

    def __init__(
        self, prompt: str, pos, screen_dimensions, print_event: bool, text_color="white"
    ) -> None:
        self.prompt = prompt
        self.print_event = print_event
        # position of chatlist and chatbox
        self.CHAT_LIST_POS = pg.Rect((pos[0], pos[1] + 50), (screen_dimensions[0], 400))
        self.CHAT_BOX_POS = pg.Rect(pos, (screen_dimensions[1], 40))
        self.CHAT_LIST_MAXSIZE = 20

        self._ime_editing = False
        self._ime_text = ""
        self._ime_text_pos = 0
        self._ime_editing_text = ""
        self._ime_editing_pos = 0
        self.chat_list = []

        # Freetype
        # The font name can be a comma separated list
        # of font names to search for.
        self.FONT_NAMES = ",".join(str(x) for x in self.FONT_NAMES)
        self.font = freetype.SysFont(self.FONT_NAMES, 24)
        self.font_small = freetype.SysFont(self.FONT_NAMES, 16)
        self.text_color = text_color

        print("Using font: " + self.font.name)

    def update(self, events) -> None:
        """
        Updates the text input widget
        """
        for event in events:
            if event.type == pg.KEYDOWN:
                if self.print_event:
                    print(event)

                if self._ime_editing:
                    if len(self._ime_editing_text) == 0:
                        self._ime_editing = False
                    continue

                if event.key == pg.K_BACKSPACE:
                    if len(self._ime_text) > 0 and self._ime_text_pos > 0:
                        self._ime_text = (
                            self._ime_text[0 : self._ime_text_pos - 1]
                            + self._ime_text[self._ime_text_pos :]
                        )
                        self._ime_text_pos = max(0, self._ime_text_pos - 1)

                elif event.key == pg.K_DELETE:
                    self._ime_text = (
                        self._ime_text[0 : self._ime_text_pos]
                        + self._ime_text[self._ime_text_pos + 1 :]
                    )
                elif event.key == pg.K_LEFT:
                    self._ime_text_pos = max(0, self._ime_text_pos - 1)
                elif event.key == pg.K_RIGHT:
                    self._ime_text_pos = min(
                        len(self._ime_text), self._ime_text_pos + 1
                    )
                # Handle ENTER key
                elif event.key in [pg.K_RETURN, pg.K_KP_ENTER]:
                    # Block if we have no text to append
                    if len(self._ime_text) == 0:
                        continue

                    # Append chat list
                    self.chat_list.append(self._ime_text)
                    if len(self.chat_list) > self.CHAT_LIST_MAXSIZE:
                        self.chat_list.pop(0)
                    self._ime_text = ""
                    self._ime_text_pos = 0

            elif event.type == pg.TEXTEDITING:
                if self.print_event:
                    print(event)
                self._ime_editing = True
                self._ime_editing_text = event.text
                self._ime_editing_pos = event.start

            elif event.type == pg.TEXTINPUT:
                if self.print_event:
                    print(event)
                self._ime_editing = False
                self._ime_editing_text = ""
                self._ime_text = (
                    self._ime_text[0 : self._ime_text_pos]
                    + event.text
                    + self._ime_text[self._ime_text_pos :]
                )
                self._ime_text_pos += len(event.text)

    def draw(self, screen: pygame.Surface) -> None:
        """
        Draws the text input widget onto the provided surface
        """

        # Chat List updates
        chat_height = self.CHAT_LIST_POS.height / self.CHAT_LIST_MAXSIZE
        for i, chat in enumerate(self.chat_list):
            self.font_small.render_to(
                screen,
                (self.CHAT_LIST_POS.x, self.CHAT_LIST_POS.y + i * chat_height),
                chat,
                self.text_color,
            )

        # Chat box updates
        start_pos = self.CHAT_BOX_POS.copy()
        ime_text_l = self.prompt + self._ime_text[0 : self._ime_text_pos]
        ime_text_m = (
            self._ime_editing_text[0 : self._ime_editing_pos]
            + "|"
            + self._ime_editing_text[self._ime_editing_pos :]
        )
        ime_text_r = self._ime_text[self._ime_text_pos :]

        rect_text_l = self.font.render_to(
            screen, start_pos, ime_text_l, self.text_color
        )
        start_pos.x += rect_text_l.width

        # Editing texts should be underlined
        rect_text_m = self.font.render_to(
            screen,
            start_pos,
            ime_text_m,
            self.text_color,
            None,
            freetype.STYLE_UNDERLINE,
        )
        start_pos.x += rect_text_m.width
        self.font.render_to(screen, start_pos, ime_text_r, self.text_color)


class Game:
    """
    A class that handles the game's events, mainloop etc.
    """

    # CONSTANTS
    # Frames per second, the general speed of the program
    FPS = 50
    # Size of window
    SCREEN_WIDTH, SCREEN_HEIGHT = 640, 480
    BG_COLOR = "black"

    def __init__(self, caption: str) -> None:
        # Initialize
        pg.init()
        self.screen = pg.display.set_mode((self.SCREEN_WIDTH, self.SCREEN_HEIGHT))
        pg.display.set_caption(caption)
        self.clock = pg.time.Clock()

        # Text input
        # Set to true or add 'showevent' in argv to see IME and KEYDOWN events
        self.print_event = "showevent" in sys.argv
        self.text_input = TextInput(
            prompt="> ",
            pos=(0, 20),
            screen_dimensions=(self.SCREEN_WIDTH, self.SCREEN_HEIGHT),
            print_event=self.print_event,
            text_color="green",
        )

    def main_loop(self) -> None:
        pg.key.start_text_input()
        input_rect = pg.Rect(80, 80, 320, 40)
        pg.key.set_text_input_rect(input_rect)

        while True:
            events = pg.event.get()
            for event in events:
                if event.type == pg.QUIT:
                    pg.quit()
                    return

            self.text_input.update(events)

            # Screen updates
            self.screen.fill(self.BG_COLOR)
            self.text_input.draw(self.screen)

            pg.display.update()
            self.clock.tick(self.FPS)


# Main loop process
def main():
    game = Game("Text Input Example")
    game.main_loop()


if __name__ == "__main__":
    main()

Sorry it's so long winded, but hopefully this helps you out.

Upvotes: 5

Related Questions