Browse Source

Merge interface branch

interface-2
Lev 3 years ago
parent
commit
d7524cb4bf
  1. BIN
      degeon_py/CRC35.otf
  2. 88
      degeon_py/active_chat.py
  3. 67
      degeon_py/chat_selector.py
  4. 31
      degeon_py/config.py
  5. 92
      degeon_py/degeon.py
  6. 11
      degeon_py/main.py
  7. 42
      degeon_py/message.py

BIN
degeon_py/CRC35.otf

Binary file not shown.

88
degeon_py/active_chat.py

@ -0,0 +1,88 @@
from __future__ import annotations
import typing
from dataclasses import dataclass
import pygame
from config import HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, DARKER_BLUE, CHAT_PREVIEW_HEIGHT, BLUE, WHITE, font, DARK_BLUE, \
MESSAGE_HEIGHT
import degeon_core as dc
from message import Message
@dataclass
class ActiveChat:
"""
The widget with the current chat
Attributes:
:param chat (dc.Chat): the chat (Rust Chat type)
:param height (int): the height of the active chat widget
:param width (int): its width
:param delta_x (int): distance from the left edge of the application to the left edge of the screen
:param header_height (int): height of the header (the title)
"""
chat: dc.Chat
height: int = HEIGHT
width: int = WIDTH - CHAT_SELECTOR_WIDTH - 10
delta_x: int = CHAT_SELECTOR_WIDTH + 10
header_height: int = int(CHAT_PREVIEW_HEIGHT * 1.5)
@classmethod
def new(cls, chat: dc.Chat, **kwargs) -> ActiveChat:
"""
Create a new `Chat` from a rust Chat object
:param chat: rusty chat
:param kwargs: optional other paraeters
:return: the `Chat`
"""
return cls(chat=chat, **kwargs)
def get_messages(self) -> typing.Iterable[Message]:
"""
Get an iterator over all messages in this chat in the backwards order
This function creates a python `message.Message` object from rust instances
:return: an iterator of `message.Message` objects
"""
for msg in reversed(self.chat.messages):
yield Message(text=msg.get_content_py().text, is_from_me=False)
def get_header(self) -> pygame.Surface:
"""
Render a pygame surface with the header.
The header is (for now) just a name of the user written on a background
:return: the header
"""
surface: pygame.Surface = pygame.Surface((self.width, self.header_height))
surface.fill(DARK_BLUE)
name_surface: pygame.Surface = font.render(self.chat.profile.name, True, WHITE)
surface.blit(name_surface, (20, 20))
return surface
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))
surface.fill(DARKER_BLUE)
# Render messages
# This is the y0 for the last message
last_message_y = self.height - 60
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))
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

67
degeon_py/chat_selector.py

@ -0,0 +1,67 @@
from __future__ import annotations
import typing
from dataclasses import dataclass, field
import pygame
from config import CHAT_SELECTOR_WIDTH, HEIGHT, DARK_BLUE, WHITE, GREY, BLUE, font, CHAT_PREVIEW_HEIGHT, MEDIUM_BLUE
@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 = -1
hovered_chat: typing.Optional[int] = None
width: int = CHAT_SELECTOR_WIDTH
height: int = HEIGHT
chat_height: int = CHAT_PREVIEW_HEIGHT
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.height))
surface.fill(GREY)
for i, chat in enumerate(self.chats):
bg_color, text_color = DARK_BLUE, WHITE
if i == self.hovered_chat:
bg_color = MEDIUM_BLUE
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, (3, i * self.chat_height + 1, self.width - 6, self.chat_height - 2))
surface.blit(title_surface, (7, 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)

31
degeon_py/config.py

@ -0,0 +1,31 @@
import pygame
pygame.init()
# Fontss
font = pygame.font.Font('CRC35.otf', 25)
font_large = pygame.font.Font('CRC35.otf', 35)
# Colors used in the app
RED = 0xFF0000
BLUE = 0x0000FF
YELLOW = 0xFFC91F
GREEN = 0x00FF00
MAGENTA = 0xFF03B8
CYAN = 0x00FFCC
BLACK = 0x000
WHITE = 0xFFFFFF
MEDIUM_BLUE = 0x2f2f4e
DARK_BLUE = 0x282e46
DARKER_BLUE = 0x202033
GREY = 0x383e4F
# Geometrical parameters
WIDTH = 1000
HEIGHT = 800
CHAT_PREVIEW_HEIGHT = 80
CHAT_SELECTOR_WIDTH = WIDTH // 3
MESSAGE_HEIGHT = 60
FPS = 30

92
degeon_py/degeon.py

@ -0,0 +1,92 @@
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, DARKER_BLUE, font, WHITE, WIDTH, CHAT_SELECTOR_WIDTH, HEIGHT
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))
if self.active_chat is not None:
active_chat_surface = self.active_chat.render()
screen.blit(active_chat_surface, (self.active_chat.delta_x, 0))
else:
text_surface: pygame.Surface = font.render('<- Select chat in the menu', True, WHITE)
screen.blit(text_surface,
(round(WIDTH / 2 + CHAT_SELECTOR_WIDTH / 2 - text_surface.get_width() / 2), HEIGHT // 2))
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 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
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:
screen.fill(DARKER_BLUE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
self.process_event(event)
self.tick()
self.render(screen)
self.clock.tick(self.fps)
pygame.display.update()

11
degeon_py/main.py

@ -0,0 +1,11 @@
from __future__ import annotations
import pygame
from degeon import Degeon
from config import WIDTH, HEIGHT
deg = Degeon.new()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
deg.main_loop(screen)
pygame.quit()

42
degeon_py/message.py

@ -0,0 +1,42 @@
from __future__ import annotations
from dataclasses import dataclass
import pygame
from config import WIDTH, CHAT_SELECTOR_WIDTH, MESSAGE_HEIGHT, font, BLUE, DARK_BLUE, WHITE, DARKER_BLUE
@dataclass
class Message:
"""
The message (for now, it consists only of text)
Attributes:
:param text (str): the message text
:param is_from_me (bool): False if the message is not from the current user
:param chat_width (int): the width of the active chat widget
"""
text: str
is_from_me: bool
chat_width: int = WIDTH - CHAT_SELECTOR_WIDTH - 10
height: int = MESSAGE_HEIGHT
def render(self) -> pygame.Surface:
"""
Creates a surface with a rectangle and the message text written on it
:return: the surface with rendered message
"""
surface = pygame.Surface((self.chat_width, self.height))
surface.fill(DARKER_BLUE)
bg_color = BLUE * self.is_from_me + DARK_BLUE * (not self.is_from_me)
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
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))
surface.blit(text_surface, (x + padding, padding))
return surface
Loading…
Cancel
Save