|
|
|
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()
|