diff --git a/degeon_py/CRC35.otf b/degeon_py/CRC35.otf new file mode 100755 index 0000000..9ec5817 Binary files /dev/null and b/degeon_py/CRC35.otf differ diff --git a/degeon_py/main.py b/degeon_py/main.py new file mode 100644 index 0000000..f35dda3 --- /dev/null +++ b/degeon_py/main.py @@ -0,0 +1,132 @@ +from __future__ import annotations +import typing + +import pygame +import degeon_core as dc +from dataclasses import dataclass, field +from abc import ABC + +# Configuration variables and loading +pygame.init() +font = pygame.font.Font('CRC35.otf', 25) +FPS = 30 + +RED = 0xFF0000 +BLUE = 0x0000FF +YELLOW = 0xFFC91F +GREEN = 0x00FF00 +MAGENTA = 0xFF03B8 +CYAN = 0x00FFCC +BLACK = 0x000 +WHITE = 0xFFFFFF +DARK_BLUE = 0x282e46 +GREY = 0x383e4F +COLORS = [RED, BLUE, YELLOW, GREEN, MAGENTA, CYAN] + +WIDTH = 900 +HEIGHT = 700 + + +@dataclass +class ChatSelector: + """ + The widget with the list of chats. + It's a dataclass, it should be initialized using the `from_chats` classmethod + + Attributes: + chats (List[dc.Chat]): list of all chats, where each chat is a native Rust struct Chat + active_chat (int): the index of the current selected chat + hovered_chat (int or None): the index of the current hovered chat + width (int): the width of this widget + height (int): the height of this widget + chat_height (int): height of one chat + """ + chats: typing.List['dc.Chat'] = field(default_factory=list) + active_chat: int = 0 + hovered_chat: typing.Optional[int] = None + width: int = WIDTH // 3 + height: int = HEIGHT + chat_height: int = HEIGHT // 10 + + def render(self) -> pygame.Surface: + """ + Creates a pygame surface and draws the list of chats on it + :return: the surface with the chat selector + """ + surface: pygame.Surface = pygame.Surface((self.width, self.chat_height * len(self.chats))) + for i, chat in enumerate(self.chats): + bg_color, text_color = DARK_BLUE, WHITE + if i == self.hovered_chat: + bg_color = GREY + if i == self.active_chat: + bg_color = BLUE + title_surface: pygame.Surface = font.render(chat.profile.name, True, text_color) + pygame.draw.rect(surface, bg_color, (0, i * self.chat_height + 1, self.width, self.chat_height - 2)) + surface.blit(title_surface, (i * self.chat_height + 10)) + return surface + + def process_event(self, event: pygame.event.Event): + """ + Process a click: select the necessary chat if this click is in the widget + :param event: pygame event + """ + 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: + self.hovered_chat = event.pos[1] // self.chat_height + else: + self.hovered_chat = None + + @classmethod + def from_chats(cls, chats: typing.List['dc.Chat'], **kwargs) -> ChatSelector: + return cls(chats, **kwargs) + + +@dataclass +class ActiveChat: + """ + The widget with the current chat + + Attributes: + :param chat (dc.Chat): the chat (Rust Chat type) + """ + + chat: 'dc.Chat' + + +@dataclass +class Degeon: + """ + The main class with everything connected to the app: the data, the + + Attributes + """ + core: 'dc.Degeon' + chat_selector: ChatSelector + has_profile_popup_opened: bool = False + has_no_peers_popup: bool = False + + @classmethod + def new(cls) -> Degeon: + """ + Create a new default instance with settings from file + :return: a Degeon instance + """ + pass # todo + + def render(self, screen: pygame.Surface): + """ + Render everything on the screen + :param screen: the main screen + """ + chats_surface = self.chat_selector.render() + screen.blit(chats_surface, (0, 0)) + + def tick(self): + """ + Handle incoming messages, update chats, create no_peers popup if necessary + """ + pass # todo