From 49824ef937d2a08e02bb5ac8be762304c3b83117 Mon Sep 17 00:00:00 2001 From: Alexey Date: Sun, 12 Dec 2021 07:42:44 +0300 Subject: [PATCH] Input field in interface --- degeon_py/active_chat.py | 13 ++++--- degeon_py/chat_selector.py | 19 +++++++--- degeon_py/degeon.py | 12 ++++--- degeon_py/input_field.py | 73 ++++++++++++++++++++++++++++++++++++++ degeon_py/message.py | 8 ++--- 5 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 degeon_py/input_field.py diff --git a/degeon_py/active_chat.py b/degeon_py/active_chat.py index 7d82732..d0cf65f 100644 --- a/degeon_py/active_chat.py +++ b/degeon_py/active_chat.py @@ -1,13 +1,14 @@ from __future__ import annotations import typing -from dataclasses import dataclass +from dataclasses import dataclass, field import pygame -from config import HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, DARKER_BLUE, CHAT_PREVIEW_HEIGHT, BLUE, WHITE, font, DARK_BLUE, \ +from config import HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, DARKER_BLUE, CHAT_PREVIEW_HEIGHT, WHITE, font, DARK_BLUE, \ MESSAGE_HEIGHT import degeon_core as dc +from input_field import TextField from message import Message @@ -24,6 +25,7 @@ class ActiveChat: :param header_height (int): height of the header (the title) """ chat: dc.Chat + input_field: TextField = field(default_factory=lambda: TextField()) height: int = HEIGHT width: int = WIDTH - CHAT_SELECTOR_WIDTH - 10 delta_x: int = CHAT_SELECTOR_WIDTH + 10 @@ -71,13 +73,16 @@ class ActiveChat: # Render messages # This is the y0 for the last message - last_message_y = self.height - 60 + last_message_y = self.height - MESSAGE_HEIGHT * 2 for i, message in zip(range(30), self.get_messages()): msg_surface = message.render() surface.blit(msg_surface, (0, last_message_y - (MESSAGE_HEIGHT + 30) * (i + 1))) # Render header header = self.get_header() surface.blit(header, (0, 10)) + # Render message input + input_field_surface: pygame.Surface = self.input_field.render() + surface.blit(input_field_surface, (0, self.height - input_field_surface.get_height())) return surface def process_event(self, event: pygame.event.Event): @@ -85,4 +90,4 @@ class ActiveChat: Process a click: select the necessary chat if this click is in the widget :param event: a pygame event """ - pass # todo + self.input_field.process_event(event) diff --git a/degeon_py/chat_selector.py b/degeon_py/chat_selector.py index 78fc73a..daa22f2 100644 --- a/degeon_py/chat_selector.py +++ b/degeon_py/chat_selector.py @@ -47,14 +47,12 @@ class ChatSelector: surface.blit(title_surface, (7, i * self.chat_height + 10)) return surface - def process_event(self, event: pygame.event.Event): + def process_event(self, event: pygame.event.Event) -> bool: """ Process a click: select the necessary chat if this click is in the widget :param event: a pygame event + :return: True if a chat was changed """ - if event.type == pygame.MOUSEBUTTONUP and event.pos[0] < self.width: - self.active_chat = event.pos[1] // self.chat_height - self.hovered_chat = None if event.type == pygame.MOUSEMOTION: if 0 < event.pos[0] < self.width \ and 0 < event.pos[1] < min(self.height, len(self.chats) * self.chat_height) - 2: @@ -62,6 +60,17 @@ class ChatSelector: else: self.hovered_chat = None + if event.type == pygame.MOUSEBUTTONUP and event.pos[0] < self.width: + self.hovered_chat = None + self.active_chat = event.pos[1] // self.chat_height + return True + return False + @classmethod def from_chats(cls, chats: typing.List['dc.Chat'], **kwargs) -> ChatSelector: - return cls(chats, **kwargs) \ No newline at end of file + """ + Create a new ChatSelector from a list of Rust chats + :param chats: the list of chats + :param kwargs: optional additional arguments + """ + return cls(chats, **kwargs) diff --git a/degeon_py/degeon.py b/degeon_py/degeon.py index 4d3aa9c..c7e6fe8 100644 --- a/degeon_py/degeon.py +++ b/degeon_py/degeon.py @@ -64,17 +64,21 @@ class Degeon: # process events in core self.process_core_messages() self.chat_selector.chats = self.core.chats - if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats): + if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats) and self.active_chat is None: self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat]) - else: - self.active_chat = None def process_event(self, event: pygame.event.Event): """ Process an event :param event: pygame event """ - self.chat_selector.process_event(event) + if self.chat_selector.process_event(event): + if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats): + self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat]) + else: + self.active_chat = None + if self.active_chat is not None: + self.active_chat.process_event(event) def main_loop(self, screen: pygame.Surface): """ diff --git a/degeon_py/input_field.py b/degeon_py/input_field.py new file mode 100644 index 0000000..3ee957d --- /dev/null +++ b/degeon_py/input_field.py @@ -0,0 +1,73 @@ +import typing + +import pygame + +from config import font, MESSAGE_HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, HEIGHT, WHITE, DARKER_BLUE, GREY + +from dataclasses import dataclass + + +@dataclass +class TextField: + """ + Field for message input + """ + value: str = '' + width: int = WIDTH - CHAT_SELECTOR_WIDTH - 80 + height: int = MESSAGE_HEIGHT * 2 + top_left_corner: typing.Tuple[int, int] = (CHAT_SELECTOR_WIDTH, HEIGHT - MESSAGE_HEIGHT * 2) + is_focused: bool = False + placeholder: str = '' + cursor_position: int = 0 + + def render(self) -> pygame.Surface: + """ + Render the text field onto a pygame surface + :return: a surface with the field + """ + surface = pygame.Surface((self.width, self.height)) + surface.fill(WHITE) + padding = 5 + pygame.draw.rect(surface, DARKER_BLUE, (padding, padding, self.width - padding * 2, self.height - padding * 2)) + if not self.value and self.placeholder: + placeholder_text: pygame.Surface = font.render(self.placeholder, True, GREY) + surface.blit( + placeholder_text, + ( + (self.width - placeholder_text.get_width()) // 2, + (self.height - placeholder_text.get_height()) // 2 + ) + ) + if self.value: + message_text: pygame.Surface = font.render(self.value, True, WHITE) + surface.blit( + message_text, + ( + (self.width - message_text.get_width()) // 2, + (self.height - message_text.get_height()) // 2 + ) + ) + return surface + + def process_event(self, event: pygame.event.Event): + """ + Handle a typing event or a click (to focus) + :param event: a pygame event + """ + # If we have a click, we should focus the field if the click was inside or unfocus if it was outside + if event.type == pygame.MOUSEBUTTONUP: + self.is_focused = self.top_left_corner[0] <= event.pos[0] < self.top_left_corner[0] + self.width \ + and self.top_left_corner[1] <= event.pos[1] < self.top_left_corner[1] + self.height + print(self.is_focused) + if self.is_focused and hasattr(event, 'key') and event.type == 768: + if event.key == pygame.K_BACKSPACE: + self.value = self.value[:-1] + self.cursor_position -= 1 + elif event.key == pygame.K_LEFT: + self.cursor_position -= 1 + elif event.key == pygame.K_RIGHT: + self.cursor_position += 1 + elif event.unicode: + self.value = self.value[:self.cursor_position] + event.unicode + self.value[self.cursor_position:] + self.cursor_position += 1 + # print(self.is_focused, event.type, getattr(event, 'key', None), getattr(event, 'unicode', None), self.value) diff --git a/degeon_py/message.py b/degeon_py/message.py index b3ba57a..dabd25c 100644 --- a/degeon_py/message.py +++ b/degeon_py/message.py @@ -33,10 +33,10 @@ class Message: text_surface: pygame.Surface = font.render(self.text, True, WHITE) padding = 5 # Size of the scaled text surface - blit_height = self.height - padding * 2 - blit_width = round(text_surface.get_width() * blit_height / text_surface.get_height()) - x = 0 if not self.is_from_me else self.chat_width - blit_width - padding * 2 + blit_height: int = self.height - padding * 2 + blit_width: int = round(text_surface.get_width() * blit_height / text_surface.get_height()) + x: int = 0 if not self.is_from_me else self.chat_width - blit_width - padding * 2 pygame.draw.rect(surface, bg_color, (x, 0, blit_width + padding * 2, self.height)) - text_surface = pygame.transform.smoothscale(text_surface, (blit_width, blit_height)) + text_surface: pygame.Surface = pygame.transform.smoothscale(text_surface, (blit_width, blit_height)) surface.blit(text_surface, (x + padding, padding)) return surface