From a509fb8e45fcd7cb6f9aa93bdabf98e2ed6cd580 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 9 Dec 2021 18:30:02 +0300 Subject: [PATCH] Split interface code into separate files --- degeon_py/active_chat.py | 40 ++++++++ degeon_py/chat_selector.py | 66 +++++++++++++ degeon_py/config.py | 22 +++++ degeon_py/degeon.py | 82 ++++++++++++++++ degeon_py/main.py | 188 +------------------------------------ 5 files changed, 212 insertions(+), 186 deletions(-) create mode 100644 degeon_py/active_chat.py create mode 100644 degeon_py/chat_selector.py create mode 100644 degeon_py/config.py create mode 100644 degeon_py/degeon.py diff --git a/degeon_py/active_chat.py b/degeon_py/active_chat.py new file mode 100644 index 0000000..8d7bc7d --- /dev/null +++ b/degeon_py/active_chat.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from dataclasses import dataclass +import pygame + +from config import HEIGHT, WIDTH +import degeon_core as dc + + +@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 diff --git a/degeon_py/chat_selector.py b/degeon_py/chat_selector.py new file mode 100644 index 0000000..57e8078 --- /dev/null +++ b/degeon_py/chat_selector.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import typing +from dataclasses import dataclass, field + +import pygame + +from config import WIDTH, HEIGHT, DARK_BLUE, WHITE, GREY, BLUE, font + + +@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) \ No newline at end of file diff --git a/degeon_py/config.py b/degeon_py/config.py new file mode 100644 index 0000000..60151c5 --- /dev/null +++ b/degeon_py/config.py @@ -0,0 +1,22 @@ +import pygame + + +pygame.init() +font = pygame.font.Font('CRC35.otf', 25) + +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 + +FPS = 30 diff --git a/degeon_py/degeon.py b/degeon_py/degeon.py new file mode 100644 index 0000000..4eaa346 --- /dev/null +++ b/degeon_py/degeon.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +import typing +import pygame + +from chat_selector import ChatSelector +from active_chat import ActiveChat +from config import FPS + +import degeon_core as dc + + +@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() \ No newline at end of file diff --git a/degeon_py/main.py b/degeon_py/main.py index dc17845..ab0fcaa 100644 --- a/degeon_py/main.py +++ b/degeon_py/main.py @@ -1,192 +1,8 @@ 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() +from degeon import Degeon +from config import WIDTH, HEIGHT deg = Degeon.new()