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 // 4 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: a 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' height: int = HEIGHT width: int = 3 * (WIDTH // 4) @classmethod def new(cls, chat: 'dc.Chat', **kwargs) -> ActiveChat: return cls(chat=chat, **kwargs) def render(self) -> pygame.Surface: """ Creates a pygame surface and draws the chat on it :return: the surface with the chat on it """ surface: pygame.Surface = pygame.Surface((self.width, self.height)) 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: a pygame event """ pass # todo @dataclass class Degeon: """ The main class with everything connected to the app: the data, the Attributes """ core: 'dc.Degeon' chat_selector: ChatSelector active_chat: typing.Optional[ActiveChat] = None has_profile_popup_opened: bool = False has_no_peers_popup: bool = False clock: pygame.time.Clock = field(default_factory=pygame.time.Clock) fps: int = FPS @classmethod def new(cls) -> Degeon: """ Create a new default instance with settings from file :return: a Degeon instance """ core: dc.Degeon = dc.new_degeon() chat_selector = ChatSelector() return cls(core=core, chat_selector=chat_selector) 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 process_core_messages(self): """ Do all the necessary Rust work """ pass # todo def tick(self): """ Handle incoming messages, update chats, create no_peers popup if necessary """ # process events in core self.process_core_messages() self.chat_selector.chats = self.core.chats if self.chat_selector.active_chat < len(self.chat_selector.chats): self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat]) def process_event(self, event: pygame.event.Event): """ Process an event :param event: pygame event """ self.chat_selector.process_event(event) def main_loop(self, screen: pygame.Surface): """ Drawing everything and handling events """ while True: self.clock.tick(self.fps) for event in pygame.event.get(): if event.type == pygame.QUIT: return self.process_event(event) self.tick() self.render(screen) pygame.display.update() deg = Degeon.new() screen = pygame.display.set_mode((WIDTH, HEIGHT)) deg.main_loop(screen) pygame.quit()