summaryrefslogtreecommitdiffstats
path: root/pygame
diff options
context:
space:
mode:
authorvg <vgm+dev@devys.org>2023-01-02 16:49:22 +0100
committervg <vgm+dev@devys.org>2023-01-02 16:49:22 +0100
commitf79d73440fa6b4e3f2641c86a477b5e5630d7fad (patch)
tree7f3fad78682d271dca8ebfd69091fce378634050 /pygame
parentae768bc9758237bb6f99911dfa66ea5895bbca29 (diff)
downloadgamechest-f79d73440fa6b4e3f2641c86a477b5e5630d7fad.tar.gz
gamechest-f79d73440fa6b4e3f2641c86a477b5e5630d7fad.tar.bz2
gamechest-f79d73440fa6b4e3f2641c86a477b5e5630d7fad.zip
git-sync on dita
Diffstat (limited to 'pygame')
-rw-r--r--pygame/Makefile23
-rwxr-xr-xpygame/__init__.py695
-rw-r--r--pygame/arrow-right.pngbin2840 -> 0 bytes
-rwxr-xr-xpygame/gamechestgui10
-rwxr-xr-xpygame/main.py441
5 files changed, 728 insertions, 441 deletions
diff --git a/pygame/Makefile b/pygame/Makefile
new file mode 100644
index 0000000..36729a7
--- /dev/null
+++ b/pygame/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all
+all: deploy
+
+#.PHONY: zipapp
+#zipapp:
+# rm -rf /tmp/gamechestgui.zipapp
+# mkdir /tmp/gamechestgui.zipapp
+# pip3 install --target /tmp/gamechestgui.zipapp -r ../gamechestcli/requirements.txt
+# pip3 install --target /tmp/gamechestgui.zipapp -r requirements.txt
+# rsync -Pa ../gamechestcli/gamechest /tmp/gamechestgui.zipapp/.
+# rsync -Pa __main__.py profiledb.py /tmp/gamechestgui.zipapp/.
+# python3 -m zipapp -p "/usr/bin/env python3" /tmp/gamechestgui.zipapp
+# if [ -e ~/games/.saves ]; then mv /tmp/gamechestgui.pyz ~/games/.saves/tools/bin/. ; elif [ -e ~/game-saves ]; then mv /tmp/gamechestgui.pyz ~/game-saves/tools/bin/. ; fi
+
+.PHONY: deploy
+deploy:
+ rm -rf ~/games/.saves/tools/bin/libs/gamechestgui
+ mkdir -p ~/games/.saves/tools/bin/libs/gamechestgui
+ pip3 install --target ~/games/.saves/tools/bin/libs/gamechestgui -r ../gamechestcli/requirements.txt
+ pip3 install --target ~/games/.saves/tools/bin/libs/gamechestgui -r requirements.txt
+ rsync -Pa ../gamechestcli/gamechest ~/games/.saves/tools/bin/libs/gamechestgui/.
+ rsync -Pa __init__.py profiledb.py ~/games/.saves/tools/bin/libs/gamechestgui/.
+ rsync -Pa gamechestgui ~/games/.saves/tools/bin/.
diff --git a/pygame/__init__.py b/pygame/__init__.py
new file mode 100755
index 0000000..b6ec930
--- /dev/null
+++ b/pygame/__init__.py
@@ -0,0 +1,695 @@
+#!/usr/bin/python3
+
+from dataclasses import dataclass
+from functools import lru_cache, partial
+from pathlib import Path
+import subprocess
+import selectors
+
+#import pygame_sdl2 as pygame
+import pygame
+import yaml
+
+from .profiledb import ProfileDB
+
+from gamechest.cliactions import run
+from gamechest.gamedb import GameDB
+from gamechest.statusdb import StatusDB
+from gamechest.gameconfig import config
+from gamechest.runners.install import Install
+from gamechest.runners.remove import Remove
+
+
+# 16:9 ratio
+#WINDOW_RESOLUTION = (1280, 720)
+#WINDOW_RESOLUTION = (800, 600)
+WINDOW_RESOLUTION = (1024, 576)
+
+if pygame.ver[0] != '2':
+ raise ValueError(f'pygame2 required, got {pygame.ver}')
+
+
+class Event(Exception): pass
+class QuitEvent(Event): pass
+
+
+@lru_cache(maxsize=40)
+def get_text_surface(text, font, color):
+ new_surface = font.render(text, Parameters.font_antialias, color)
+ print('cache miss for', text)
+ return new_surface
+
+
+def calculate_new_size(size_max, size):
+ width_max, height_max = size_max
+ width, height = size
+
+ if width > width_max:
+ ratio = width_max / width
+ width = width_max
+ height = height * ratio
+
+ if height > height_max:
+ ratio = height_max / height
+ height = height_max
+ width = width * ratio
+
+ return width, height
+
+
+@lru_cache(maxsize=40)
+def get_image_surface(path, resize=None, alpha=False):
+ print('cache miss for', path)
+ image = pygame.image.load(str(path))
+ if alpha:
+ image = image.convert_alpha()
+ else:
+ image = image.convert()
+ if resize:
+ new_size = calculate_new_size(resize, (image.get_width(),
+ image.get_height()))
+ image = pygame.transform.scale(image, new_size)
+ print('new_size', new_size)
+ return image
+
+
+@dataclass
+class AppState:
+ menu_selected_index: int = 0
+
+
+class Parameters:
+ key_delay = 330
+ key_interval = 1000//25
+ font_antialias = True
+
+
+
+
+class GuiApp:
+
+ def __init__(self):
+ pygame.init()
+ pygame.key.set_repeat(Parameters.key_delay, Parameters.key_interval)
+ #print(pygame.display.list_modes())
+ self.window = pygame.display.set_mode(WINDOW_RESOLUTION)
+ self.clock = pygame.time.Clock()
+ self.cycles_per_second = 60
+ self.menu_font = pygame.font.Font(pygame.font.get_default_font(), 16)
+ self.menu_color = (0, 0, 200)
+ self.render_timer = 0
+ self.render_timeout = 1000//30 # 30 fps
+
+ def process_input(self, last_frame_time, event=None):
+ if event is None:
+ event = pygame.event.poll()
+ if event.type == pygame.KEYDOWN:
+ print('event', event, 'key name', pygame.key.name(event.key))
+ if event.key in (pygame.K_q, pygame.K_ESCAPE):
+ raise QuitEvent()
+ if event.type == pygame.QUIT:
+ raise QuitEvent()
+
+ def update(self, last_frame_time):
+ pass
+
+ def render(self, last_frame_time):
+ self.render_timer += last_frame_time
+ if self.render_timer < self.render_timeout:
+ return
+ self.render_timer = 0
+
+ self.window.fill((0, 0, 0))
+
+ # Initial y
+ y = 50
+ x = 50
+
+ self.window.blit(surface, (x, y))
+
+ pygame.display.update()
+
+
+ def loop(self):
+ last_frame_time = 1 # equivalent to 0 but avoid div0
+ last_render_time = 1
+ try:
+ while 1:
+ self.process_input(last_frame_time)
+ self.update(last_frame_time)
+ self.render(last_frame_time)
+ last_frame_time = self.clock.tick(self.cycles_per_second)
+ except QuitEvent:
+ pass
+
+ print('quitting')
+ pygame.quit()
+
+
+class ProfileDataObserver:
+
+ def event_current_profile_changed(self, new_profile):
+ pass
+
+
+class ObservedSubject:
+ def __init__(self):
+ self.observers = []
+
+ def add_observer(self, observer):
+ self.observers.append(observer)
+
+ def del_observer(self, observer):
+ self.observers.remove(observer)
+
+ def notify_event(self, event_name, *args, **kws):
+ for observer in self.observers:
+ getattr(observer, event_name)(*args, **kws)
+
+
+class ProfileData(ObservedSubject):
+
+ def __init__(self):
+ super().__init__()
+ self.profiledb = ProfileDB()
+ self.profiles_len = len(self.profiledb.get_profiles())
+ self.current_profile_index = 0
+ configured_default_profile = config.get_profile_id()
+ for index, profile in enumerate(self.profiledb.get_profiles()):
+ if profile['name'] == configured_default_profile:
+ self.current_profile_index = index
+
+ def change_active_profile(self, new_index):
+ if new_index < 0:
+ new_index = 0
+ elif new_index >= self.profiles_len:
+ new_index = self.profiles_len-1
+ if self.current_profile_index != new_index:
+ self.current_profile_index = new_index
+ self.notify_event('event_current_profile_changed',
+ self.get_current_profile())
+
+ def change_profile_increase(self):
+ self.change_active_profile(self.current_profile_index +1)
+
+ def change_profile_decrease(self):
+ self.change_active_profile(self.current_profile_index -1)
+
+ def get_current_profile(self):
+ return self.profiledb.get_profiles()[self.current_profile_index]['name']
+
+ def get_current_profile_avatar(self):
+ return self.profiledb.get_profiles()[self.current_profile_index].get('avatar', None)
+
+ def get_current_display(self):
+ return self.profiledb.get_profiles()[self.current_profile_index]['display']
+
+
+class ProfileRenderer(ProfileDataObserver):
+
+ def __init__(self, profiledata):
+ self.profiledata = profiledata
+ self.surface_to_render = None
+ profiledata.add_observer(self)
+ self.font_filename = pygame.font.match_font('bitstreamverasans')
+ print('ProfileRenderer font:', self.font_filename)
+ self.font = pygame.font.Font(self.font_filename, 24)
+ self.font_fg_color = (0, 200, 200)
+ self.avatar_surface = None
+ self.avatar_size = (100, 100)
+ self.image_box = get_image_surface(config.get_data_d() / 'asset/box.png', resize=(170, 170), alpha=True)
+ self.load_avatar()
+
+ def load_avatar(self):
+ self.avatar_surface = None
+ if path := self.profiledata.get_current_profile_avatar():
+ path = config.get_data_d() / path
+ if path.exists():
+ self.avatar_surface = get_image_surface(path,
+ self.avatar_size)
+ else:
+ print('no avatar found at', path)
+
+ def event_current_profile_changed(self, new_profile):
+ self.surface_to_render = None
+ self.load_avatar()
+ print('changing rendering profile to', new_profile)
+
+ def render(self, surface, pos):
+ if self.surface_to_render is None:
+ profile_name = self.profiledata.get_current_profile()
+ new_surface = get_text_surface(
+ self.profiledata.get_current_display(),
+ self.font, self.font_fg_color)
+ #new_surface = self.font.render(self.profiledata.get_current_display(), Parameters.font_antialias, self.font_fg_color)
+ self.surface_to_render = new_surface
+ avatar_pos = (pos[0] + 15, pos[1] + 15)
+ #text_pos = (avatar_pos[0] + self.avatar_size[0] + 5, avatar_pos[1])
+ text_pos = (avatar_pos[0], avatar_pos[1] + self.avatar_size[1])
+ surface.blit(self.image_box, pos)
+ if self.avatar_surface:
+ surface.blit(self.avatar_surface, avatar_pos)
+ surface.blit(self.surface_to_render, text_pos)
+
+
+
+class GameData(ObservedSubject):
+
+ def __init__(self):
+ super().__init__()
+ self.game_db = GameDB()
+ self.games_idtitles = list(self.game_db.get_idtitles())
+ self.games_len = len(self.games_idtitles)
+ self.game_index = 0
+
+ def change_active_game(self, new_index):
+ if new_index < 0:
+ new_index = 0
+ elif new_index >= self.games_len:
+ new_index = self.games_len-1
+ if self.game_index != new_index:
+ self.game_index = new_index
+ self.notify_event('event_current_game_changed',
+ self.get_current_gameinfo())
+
+ #def get_idtitles(self):
+ # return self.games_idtitles
+
+ def get_games_subset(self):
+ low_index = max(self.game_index-3, 0)
+ high_index = low_index + 6
+ for index, item in enumerate(self.games_idtitles[low_index:high_index], start=low_index):
+ selected = 1 if self.game_index == index else 0
+ game_info = self.game_db.get_info(item[0])
+ current_icon = game_info.get(
+ 'icon', None)
+ if current_icon is not None:
+ current_icon = config.get_data_d() / current_icon
+ yield selected, item[0], item[1], current_icon, game_info
+
+ def get_current_gameinfo(self):
+ return self.game_db.get_info(self.get_current_id())
+
+ def get_current_id(self):
+ current_id = self.games_idtitles[self.game_index][0]
+ return current_id
+
+ def get_current_title(self):
+ current_title = self.games_idtitles[self.game_index][1]
+ return current_title
+
+ def change_game_increase(self):
+ self.change_active_game(self.game_index +1)
+
+ def change_game_decrease(self):
+ self.change_active_game(self.game_index -1)
+
+class GameDataObserver:
+
+ def event_current_game_changed(self, new_game_info): pass
+
+class GameRenderer(GameDataObserver):
+
+ def __init__(self, gamedata, status_db):
+ self.gamedata = gamedata
+ self.status_db = status_db
+ gamedata.add_observer(self)
+ self.font_filename = pygame.font.match_font('bitstreamverasans')
+ print('GameRenderer font:', self.font_filename)
+ self.font = pygame.font.Font(self.font_filename, 16)
+ self.font_selected = pygame.font.Font(self.font_filename, 24)
+ self.font_fg_color = (90, 90, 200)
+ #self.image_right_arrow = get_image_surface(config.get_data_d() / 'asset/arrow-right.png',
+ # resize=(16, 16),
+ # alpha=True)
+ self.image_right_arrow = get_image_surface(config.get_data_d() / 'asset/bulb.png',
+ resize=(16, 16),
+ alpha=True)
+ self.image_box = get_image_surface(config.get_data_d() / 'asset/box.png', resize=(400, 400), alpha=True)
+ self.image_box.set_alpha(220)
+
+ def event_current_game_changed(self, new_game_info):
+ print('game changed, new game:', new_game_info['title'])
+
+ def render(self, dest_surface, pos):
+ x = pos[0]
+ y = pos[1]
+ y_pad = 32
+ surface_height = None
+ #selected_item_y = y
+ #low_index = max(selected_index-3, 0)
+ #high_index = low_index + 6
+ dest_surface.blit(self.image_box, pos)
+ x += 15
+ y += 45
+ for selected, game_id, game_title, game_icon, game_info in self.gamedata.get_games_subset():
+ text = game_title
+ if not self.status_db.is_installed(game_info):
+ text = f'[u] {text}'
+ else:
+ text = f'[i] {text}'
+ arrow_width = self.image_right_arrow.get_width()
+ text_x = x + y_pad
+ surface = get_text_surface(text,
+ self.font_selected if selected else
+ self.font,
+ self.font_fg_color)
+ if game_icon and game_icon.exists():
+ icon_surface = get_image_surface(game_icon,
+ resize=(y_pad, y_pad),
+ alpha=True)
+ icon_pos = (x + arrow_width,
+ y + (surface.get_height()
+ - icon_surface.get_height())/2)
+
+ dest_surface.blit(icon_surface, icon_pos)
+ dest_surface.blit(surface, (text_x
+ + arrow_width + 5, y))
+ if selected:
+ surface_height = surface.get_height()
+ #selected_item_y = y
+ dest_surface.blit(self.image_right_arrow, (x, y + (surface_height - self.image_right_arrow.get_height())/2))
+ y += surface.get_height() + y_pad
+
+
+class GameCoverRenderer(GameDataObserver):
+
+ def __init__(self, gamedata):
+ self.gamedata = gamedata
+ gamedata.add_observer(self)
+ self.current_game_info = self.gamedata.get_current_gameinfo()
+ self.cover_shift = (0, 0)
+ self.default_cover_surface = get_image_surface(config.get_data_d() / 'asset/default_cover.jpeg',
+ resize=WINDOW_RESOLUTION)
+ self.default_cover_shift = (
+ (WINDOW_RESOLUTION[0] - self.default_cover_surface.get_width())/2,
+ (WINDOW_RESOLUTION[1] - self.default_cover_surface.get_height())/2,
+ )
+ self.load_cover()
+
+ def event_current_game_changed(self, new_game_info):
+ print('game changed, cover:', new_game_info.get('cover', None))
+ self.current_game_info = new_game_info
+ self.load_cover()
+
+ def load_cover(self):
+ self.cover_surface = None
+ path = self.current_game_info.get('cover', None)
+ if path is None:
+ self.cover_surface = self.default_cover_surface
+ self.cover_shift = self.default_cover_shift
+ return
+ path = config.get_data_d() / Path(path)
+ if path.exists():
+ self.cover_surface = get_image_surface(path,
+ resize=WINDOW_RESOLUTION)
+ self.cover_shift = (
+ (WINDOW_RESOLUTION[0] - self.cover_surface.get_width())/2,
+ (WINDOW_RESOLUTION[1] - self.cover_surface.get_height())/2,
+ )
+ else:
+ self.cover_surface = self.default_cover_surface
+ self.cover_shift = self.default_cover_shift
+
+ def render(self, dest_surface, pos):
+ if self.cover_surface is None:
+ return
+ pos = (
+ pos[0] + self.cover_shift[0],
+ pos[1] + self.cover_shift[1],
+ )
+ dest_surface.blit(self.cover_surface, pos)
+
+
+class ProgressBarRenderer:
+
+ def __init__(self):
+ self.progress = .0
+ self.back_color = 0xEEEEEEF0
+ self.front_color = 0x0000FF00
+
+ def set_progress(self, progress):
+ 'progress is between .0 and 1.0'
+ self.progress = progress
+
+ def render(self, dest_surface, pos):
+ size = (100, 10)
+ background_rect = pygame.Rect(pos[0], pos[1], size[0], size[1])
+ inside_rect = pygame.Rect(pos[0]+2, pos[1]+2, int(96*self.progress), 6)
+
+ dest_surface.lock()
+ pygame.draw.rect(dest_surface, self.back_color, background_rect,
+ border_radius=5)
+ pygame.draw.rect(dest_surface, self.front_color, inside_rect,
+ border_radius=5)
+ dest_surface.unlock()
+
+
+class DoubleProgressBarRenderer:
+ def __init__(self):
+ self.progress_bar_global = ProgressBarRenderer()
+ self.progress_bar_step = ProgressBarRenderer()
+ self.title = ""
+ self.title_surface = None
+ self.font_filename = pygame.font.match_font('bitstreamverasans')
+ print('GameRenderer font:', self.font_filename)
+ self.font = pygame.font.Font(self.font_filename, 10)
+ self.font_color = 0xFFFFFFFF
+ self.set_title("")
+
+ def set_progress(self, global_progress, step_progress):
+ self.progress_bar_global.set_progress(global_progress)
+ self.progress_bar_step.set_progress(step_progress)
+
+ def set_title(self, title):
+ self.title = title or ""
+ self.title_surface = get_text_surface(title, self.font, self.font_color)
+
+ def render(self, dest_surface, pos):
+ x = pos[0]
+ y = pos[1]
+ rect = pygame.Rect(x, y, 104, 10+10+10+2+2+2+2)
+ x += 2
+ y += 2
+ pygame.draw.rect(dest_surface, 0x00000000, rect, border_radius=3)
+ if self.title_surface:
+ dest_surface.blit(self.title_surface, (x, y))
+ #y += self.title_surface.get_height() + 2
+ y += 10 + 2
+ self.progress_bar_global.render(dest_surface, (x, y))
+ self.progress_bar_step.render(dest_surface, (x, y+12))
+
+
+
+class GuiAppSub1(GuiApp):
+
+ def __init__(self):
+ super().__init__()
+ self.input_timer = 0
+ self.input_timeout = 1000//10
+ self.state = AppState()
+ self.base_window_fill_color = (0, 0, 0)
+ self.user_move_vector = (0, 0)
+ self.game_db = GameDB()
+ #self.menu_font_cache = ({}, [])
+ #self.menu_font_cache_max = 40
+ #self.last_movement_update_time = self.clock.
+ self.joysticks = {}
+ self.joysticks_axis_threshold = 0
+ self.profile_data = ProfileData()
+ self.profile_renderer = ProfileRenderer(self.profile_data)
+ self.game_data = GameData()
+ self.status_db = StatusDB()
+ self.game_renderer = GameRenderer(self.game_data, self.status_db)
+ self.game_cover_renderer = GameCoverRenderer(self.game_data)
+ self.action = None
+ self.runner = None
+ self.progress_bar = DoubleProgressBarRenderer()
+ self.selector = selectors.DefaultSelector()
+ self.delay_change = None
+
+ def process_input(self, last_frame_time):
+
+ movement_up_keys = (
+ pygame.K_UP,
+ pygame.K_k,
+ )
+ movement_down_keys = (
+ pygame.K_DOWN,
+ pygame.K_j,
+ )
+ axis_change = 0
+ for event in pygame.event.get():
+ go_super = 1
+ x, y = self.user_move_vector
+ if event.type == pygame.KEYDOWN:
+ if event.key in (*movement_up_keys, *movement_down_keys):
+ y = 1 if event.key in movement_down_keys else -1
+ go_super = 0
+ elif event.key in (pygame.K_LEFT, pygame.K_RIGHT):
+ x = -1 if event.key == pygame.K_LEFT else 1
+ go_super = 0
+ elif event.type == pygame.KEYUP:
+ x, y = self.user_move_vector
+ if event.key in (*movement_up_keys, *movement_down_keys):
+ y = 0
+ go_super = 0
+ elif event.key in (pygame.K_LEFT, pygame.K_RIGHT):
+ x = 0
+ go_super = 0
+ elif event.key == pygame.K_RETURN:
+ self.action = 'run'
+ elif event.key == pygame.K_i:
+ self.action = 'install'
+ elif event.key == pygame.K_d:
+ self.action = 'remove'
+ elif event.type in (pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP):
+ go_super = 0
+ factor = 1 if pygame.JOYBUTTONDOWN else 0
+ joystick = self.joysticks[event.instance_id]
+ print('gamepad event', event)
+ #if event.button == 0:
+ #y += 1 * factor
+ if event.type == pygame.JOYBUTTONDOWN:
+ joystick.rumble(0.3, 0.8, 40)
+ if event.button == 1:
+ raise QuitEvent()
+ elif event.button == 2:
+ self.action = 'run'
+ #if event.button == 1:
+ #y -= 1 * factor
+ elif event.type == pygame.JOYAXISMOTION:
+ go_super = 0
+ if event.axis == 1: # left stick up-down axis
+ new_axis = self.joysticks_axis_threshold
+ #print('motion event', event)
+ if event.value > 0.70:
+ new_axis = 1 # down activated
+ elif event.value < 0.5 and event.value > -0.5:
+ new_axis = 0 # down and up deactivated
+ elif event.value < -0.70:
+ new_axis = -1 # up activated
+ if new_axis != self.joysticks_axis_threshold:
+ axis_change = 1
+ self.joysticks_axis_threshold = new_axis
+ print('threshold', self.joysticks_axis_threshold)
+ elif event.type == pygame.JOYDEVICEADDED:
+ # This event will be generated when the program starts for every
+ # joystick, filling up the list without needing to create them manually.
+ joy = pygame.joystick.Joystick(event.device_index)
+ self.joysticks[joy.get_instance_id()] = joy
+ print(f"Gamepad {joy.get_instance_id()} connected")
+ elif event.type == pygame.JOYDEVICEREMOVED:
+ del self.joysticks[event.instance_id]
+ print(f"Gamepad {event.instance_id} disconnected")
+
+ if go_super == 0:
+ if axis_change:
+ self.user_move_vector = (x, self.joysticks_axis_threshold)
+ else:
+ self.user_move_vector = (x, y)
+ #print('new move vector:', self.user_move_vector)
+ else:
+ super().process_input(last_frame_time, event)
+
+ def update(self, last_frame_time):
+ #self.input_timer += last_frame_time
+ #if self.input_timer < self.input_timeout:
+ # return
+ #self.input_timer = 0
+
+ if self.user_move_vector[1] < 0:
+ self.game_data.change_game_decrease()
+ elif self.user_move_vector[1] > 0:
+ self.game_data.change_game_increase()
+
+ if self.user_move_vector[0] < 0:
+ self.profile_data.change_profile_decrease()
+ elif self.user_move_vector[0] > 0:
+ self.profile_data.change_profile_increase()
+
+ if action := self.action:
+ self.action = None
+ profile_id = self.profile_renderer.profiledata.get_current_profile()
+ game_id = self.game_data.get_current_id()
+ game_info = self.game_data.get_current_gameinfo()
+ print('current id', game_id)
+ print('profile id', profile_id)
+ if not self.runner:
+ # action not already running, else skip action
+ if action == 'run':
+ if not self.status_db.is_installed(game_info):
+ print('Game', game_id, 'is not installed, aborting.', file=sys.stderr)
+ else:
+ command = self.game_db.get_game_command(profile_id, game_id)
+ subprocess.run(command)
+ elif action == 'install':
+ #subprocess.run([
+ # 'gamechest',
+ # 'install',
+ # game_id])
+ remote_basedir = config.get_remote_basedir()
+ source = f'{remote_basedir}/{game_info["package_name"]}'
+ dest = config.get_games_install_basedir() / game_info['id']
+ dest.mkdir(parents=True, exist_ok=True)
+ self.runner = Install(source, dest)
+ self.selector.register(self.runner.get_read_fd(),
+ selectors.EVENT_READ)
+ self.delay_change = partial(self.status_db.set_installed, game_info)
+ self.progress_bar.set_title("installing...")
+ elif action == 'remove':
+ remote_basedir = config.get_remote_basedir()
+ path = (
+ config.get_games_install_basedir()
+ / game_id
+ )
+ #subprocess.run([
+ # 'gamechest',
+ # 'remove',
+ # game_id])
+ self.runner = Remove(path)
+ self.selector.register(self.runner.get_read_fd(),
+ selectors.EVENT_READ)
+ self.delay_change = partial(self.status_db.set_uninstalled, game_info)
+ self.progress_bar.set_title("removing...")
+
+ if self.runner:
+ if self.runner.poll() is None:
+ if self.selector.select(0):
+ progress = self.runner.progress_read()
+ self.progress_bar.set_progress(progress.step / progress.steps_count, progress.percent / 100)
+ else:
+ self.progress_bar.set_progress(1, 1)
+ self.progress_bar.set_title("done")
+ self.runner = None
+ if self.delay_change is not None:
+ self.delay_change()
+ self.delay_change = None
+
+
+ self.user_move_vector = (0, 0)
+
+ def render(self, last_frame_time):
+ self.window.fill(self.base_window_fill_color)
+
+ self.game_cover_renderer.render(self.window, (0, 0))
+
+ # Initial y
+ y = 50
+ x = 50 + 64 + 5
+ self.game_renderer.render(self.window, (x, y))
+
+
+ fps_surface = get_text_surface(f'{self.clock.get_fps():02.0f}',
+ self.menu_font, self.menu_color)
+ fps_pos = (self.window.get_width() - fps_surface.get_width(), 0)
+ self.window.blit(fps_surface, fps_pos)
+
+ self.profile_renderer.render(self.window, (700, 50))
+ self.progress_bar.render(self.window, (500, 50))
+
+ pygame.display.update()
+
+
+app = GuiAppSub1()
+app.loop()
diff --git a/pygame/arrow-right.png b/pygame/arrow-right.png
deleted file mode 100644
index c88334f..0000000
--- a/pygame/arrow-right.png
+++ /dev/null
Binary files differ
diff --git a/pygame/gamechestgui b/pygame/gamechestgui
new file mode 100755
index 0000000..5cedfaa
--- /dev/null
+++ b/pygame/gamechestgui
@@ -0,0 +1,10 @@
+#!/usr/bin/python3
+
+import sys
+import os
+file_path = os.path.realpath(__file__)
+sys.path.insert(0, os.path.join(os.path.dirname(file_path), 'libs'))
+sys.path.insert(0, os.path.join(os.path.dirname(file_path), 'libs/gamechestgui'))
+
+import gamechestgui
+#gamechestgui.__main__()
diff --git a/pygame/main.py b/pygame/main.py
deleted file mode 100755
index ba61077..0000000
--- a/pygame/main.py
+++ /dev/null
@@ -1,441 +0,0 @@
-#!/usr/bin/python3
-
-from dataclasses import dataclass
-from pathlib import Path
-
-#import pygame_sdl2 as pygame
-import pygame
-import yaml
-
-from profiledb import ProfileDB
-
-from gamechest.cliactions import run
-from gamechest.gamedb import GameDB
-
-
-#WINDOW_RESOLUTION = (1280, 720)
-WINDOW_RESOLUTION = (800, 600)
-
-if pygame.ver[0] != '2':
- raise ValueError(f'pygame2 required, got {pygame.ver}')
-
-
-class Event(Exception): pass
-class QuitEvent(Event): pass
-
-
-@dataclass
-class AppState:
- menu_selected_index: int = 0
-
-
-class Parameters:
- key_delay = 330
- key_interval = 1000//25
-
-
-
-
-class GuiApp:
-
- def __init__(self):
- pygame.init()
- pygame.key.set_repeat(Parameters.key_delay, Parameters.key_interval)
- #print(pygame.display.list_modes())
- self.window = pygame.display.set_mode(WINDOW_RESOLUTION)
- self.clock = pygame.time.Clock()
- self.cycles_per_second = 60
- #self.render_factor_div = 2 # 60/2 => 30fps
- #self.input_factor_div = 6 # 60/6 => 10 input repeat per second
- self.menu_font = pygame.font.Font(pygame.font.get_default_font(), 32)
- self.font_antialias = True
- #self.font_antialias = False
- self.menu_color = (0, 0, 200)
- self.render_timer = 0
- self.render_timeout = 1000//30 # 30 fps
-
- def process_input(self, last_frame_time, event=None):
- if event is None:
- event = pygame.event.poll()
- if event.type == pygame.KEYDOWN:
- print('event', event, 'key name', pygame.key.name(event.key))
- if event.key in (pygame.K_q, pygame.K_ESCAPE):
- raise QuitEvent()
- if event.type == pygame.QUIT:
- raise QuitEvent()
-
- def update(self, last_frame_time):
- pass
-
- def render(self, last_frame_time):
- self.render_timer += last_frame_time
- if self.render_timer < self.render_timeout:
- return
- self.render_timer = 0
-
- self.window.fill((0, 0, 0))
-
- # Initial y
- y = 50
-
- # Title
- #surface = self.titleFont.render("TANK BATTLEGROUNDS !!", True, (200, 0, 0))
- #x = (self.window.get_width() - surface.get_width()) // 2
- #self.window.blit(surface, (x, y))
- #y += (200 * surface.get_height()) // 100
-
- x = 50
-
- # Compute menu width
- #menuWidth = 0
- #for item in self.menuItems:
- # surface = self.itemFont.render(item['title'], True, (200, 0, 0))
- # menuWidth = max(menuWidth, surface.get_width())
- # item['surface'] = surface
-
- surface = self.menu_font.render("I love my cat !",
- self.font_antialias, self.menu_color)
- self.window.blit(surface, (x, y))
-
- ## Draw menu items
- #x = (self.window.get_width() - menuWidth) // 2
- #for index, item in enumerate(self.menuItems):
- # # Item text
- # surface = item['surface']
- # self.window.blit(surface, (x, y))
-
- # # Cursor
- # if index == self.currentMenuItem:
- # cursorX = x - self.menuCursor.get_width() - 10
- # cursorY = y + (surface.get_height() - self.menuCursor.get_height()) // 2
- # self.window.blit(self.menuCursor, (cursorX, cursorY))
-
- # y += (120 * surface.get_height()) // 100
-
-
- #pygame.draw.rect(self.window,
- # (0,0,255),
- # (120,120,400,240))
-
- pygame.display.update()
-
-
- def loop(self):
- #current_render_div = self.render_factor_div
- #current_input_div = self.input_factor_div
- last_frame_time = 1 # equivalent to 0 but avoid div0
- last_render_time = 1
- try:
- while 1:
- #current_render_div -= 1
- #current_input_div -= 1
- self.process_input(last_frame_time)
- #self.update(current_input_div)
- self.update(last_frame_time)
- self.render(last_frame_time)
- #if current_render_div <= 0:
- # self.render(last_render_time)
- # current_render_div = self.render_factor_div
- # last_render_time = 0
- #if current_input_div <= 0:
- # current_input_div = self.input_factor_div
- last_frame_time = self.clock.tick(self.cycles_per_second)
- #last_render_time += last_frame_time
- except QuitEvent:
- pass
-
- print('quitting')
- pygame.quit()
-
-
-class ProfileDataObserver:
-
- def event_current_profile_changed(self, new_profile):
- pass
-
-
-class ObservedSubject:
- def __init__(self):
- self.observers = []
-
- def add_observer(self, observer):
- self.observers.append(observer)
-
- def del_observer(self, observer):
- self.observers.remove(observer)
-
- def notify_event(self, event_name, *args, **kws):
- for observer in self.observers:
- getattr(observer, event_name)(*args, **kws)
-
-
-class ProfileData(ObservedSubject):
-
- def __init__(self):
- super().__init__()
- self.profiledb = ProfileDB()
- self.profiles_len = len(self.profiledb.get_profiles())
- self.current_profile_index = 0
-
- def change_active_profile(self, new_index):
- if new_index < 0:
- new_index = 0
- elif new_index >= self.profiles_len:
- new_index = self.profiles_len-1
- if self.current_profile_index != new_index:
- print('new index', self.current_profile_index, '->', new_index)
- self.current_profile_index = new_index
- print(id(self), 'new index', self.current_profile_index)
- self.notify_event('event_current_profile_changed',
- self.get_current_profile())
-
- def change_profile_increase(self):
- self.change_active_profile(self.current_profile_index +1)
-
- def change_profile_decrease(self):
- self.change_active_profile(self.current_profile_index -1)
-
- def get_current_profile(self):
- return self.profiledb.get_profiles()[self.current_profile_index]['name']
-
- def get_current_display(self):
- return self.profiledb.get_profiles()[self.current_profile_index]['display']
-
-
-class ProfileRenderer(ProfileDataObserver):
-
- def __init__(self, profiledata):
- self.profiledata = profiledata
- self.surface_to_render = None
- profiledata.add_observer(self)
- self.font_filename = pygame.font.match_font('bitstreamverasans')
- print('ProfileRenderer font:', self.font_filename)
- self.font = pygame.font.Font(self.font_filename, 24)
- self.font_antialias = True
- self.font_fg_color = (0, 200, 200)
-
- def event_current_profile_changed(self, new_profile):
- self.surface_to_render = None
- print('changing rendering profile to', new_profile)
-
- def render(self, surface):
- if self.surface_to_render is None:
- profile_name = self.profiledata.get_current_profile()
- new_surface = self.font.render(self.profiledata.get_current_display(), self.font_antialias, self.font_fg_color)
- self.surface_to_render = new_surface
- surface.blit(self.surface_to_render, (50, 50))
-
-
-class GuiAppSub1(GuiApp):
-
- def __init__(self):
- super().__init__()
- self.input_timer = 0
- self.input_timeout = 1000//10
- self.state = AppState()
- self.base_window_fill_color = (0, 0, 0)
- self.user_move_vector = (0, 0)
- #with Path('~/games/.saves/gamedata.yaml').expanduser().open() as fp:
- # self.gamedb = yaml.safe_load(fp)
- self.game_db = GameDB()
- #self.menu_items = [x['title'] for x in self.gamedb['games']]
- self.menu_items = list(self.game_db.get_ids())
- self.menu_font_cache = ({}, [])
- self.menu_font_cache_max = 40
- self.image_right_arrow = pygame.image.load('arrow-right.png').convert()
- #self.last_movement_update_time = self.clock.
- self.joysticks = {}
- self.joysticks_axis_threshold = 0
- self.profile_data = ProfileData()
- self.profile_renderer = ProfileRenderer(self.profile_data)
-
-
- def menu_font_render(self, text):
- cached_surface = self.menu_font_cache[0].get(text, None)
- if cached_surface is not None:
- self.menu_font_cache[1].remove(text)
- self.menu_font_cache[1].append(text) # refresh mru place
- return cached_surface
-
- new_surface = self.menu_font.render(text,
- self.font_antialias,
- self.menu_color)
-
- self.menu_font_cache[0][text] = new_surface
- self.menu_font_cache[1].append(text)
-
- if len(self.menu_font_cache[1]) > self.menu_font_cache_max:
- removed_item_text = self.menu_font_cache[1].pop(0)
- del self.menu_font_cache[0][removed_item_text]
-
- # debug for cache miss:
- print('cache miss for', text)
-
- return new_surface
-
- def process_input(self, last_frame_time):
-
- movement_up_keys = (
- pygame.K_UP,
- pygame.K_k,
- )
- movement_down_keys = (
- pygame.K_DOWN,
- pygame.K_j,
- )
- axis_change = 0
- for event in pygame.event.get():
- go_super = 1
- x, y = self.user_move_vector
- if event.type == pygame.KEYDOWN:
- if event.key in (*movement_up_keys, *movement_down_keys):
- y = 1 if event.key in movement_down_keys else -1
- go_super = 0
- elif event.key in (pygame.K_LEFT, pygame.K_RIGHT):
- x = -1 if event.key == pygame.K_LEFT else 1
- go_super = 0
- elif event.type == pygame.KEYUP:
- x, y = self.user_move_vector
- if event.key in (*movement_up_keys, *movement_down_keys):
- y = 0
- go_super = 0
- elif event.key in (pygame.K_LEFT, pygame.K_RIGHT):
- x = 0
- go_super = 0
- elif event.type in (pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP):
- go_super = 0
- factor = 1 if pygame.JOYBUTTONDOWN else 0
- joystick = self.joysticks[event.instance_id]
- print('gamepad event', event)
- #if event.button == 0:
- #y += 1 * factor
- if event.type == pygame.JOYBUTTONDOWN:
- joystick.rumble(0.3, 0.8, 40)
- if event.button == 1:
- raise QuitEvent()
- #if event.button == 1:
- #y -= 1 * factor
- elif event.type == pygame.JOYAXISMOTION:
- go_super = 0
- if event.axis == 1: # left stick up-down axis
- new_axis = self.joysticks_axis_threshold
- #print('motion event', event)
- if event.value > 0.70:
- new_axis = 1 # down activated
- elif event.value < 0.5 and event.value > -0.5:
- new_axis = 0 # down and up deactivated
- elif event.value < -0.70:
- new_axis = -1 # up activated
- if new_axis != self.joysticks_axis_threshold:
- axis_change = 1
- self.joysticks_axis_threshold = new_axis
- print('threshold', self.joysticks_axis_threshold)
- elif event.type == pygame.JOYDEVICEADDED:
- # This event will be generated when the program starts for every
- # joystick, filling up the list without needing to create them manually.
- joy = pygame.joystick.Joystick(event.device_index)
- self.joysticks[joy.get_instance_id()] = joy
- print(f"Gamepad {joy.get_instance_id()} connected")
- elif event.type == pygame.JOYDEVICEREMOVED:
- del self.joysticks[event.instance_id]
- print(f"Gamepad {event.instance_id} disconnected")
-
- if go_super == 0:
- if axis_change:
- self.user_move_vector = (x, self.joysticks_axis_threshold)
- else:
- self.user_move_vector = (x, y)
- print('new move vector:', self.user_move_vector)
- else:
- super().process_input(last_frame_time, event)
-
- def update(self, last_frame_time):
- #self.input_timer += last_frame_time
- #if self.input_timer < self.input_timeout:
- # return
- #self.input_timer = 0
-
- menu_new_index = self.state.menu_selected_index
- if self.user_move_vector[1] < 0:
- menu_new_index -= 1
- elif self.user_move_vector[1] > 0:
- menu_new_index += 1
- if menu_new_index < 0:
- menu_new_index = 0
- elif menu_new_index > (len(self.menu_items)-1):
- menu_new_index = len(self.menu_items)-1
- self.state.menu_selected_index = menu_new_index
-
- if self.user_move_vector[0] < 0:
- self.profile_data.change_profile_decrease()
- elif self.user_move_vector[0] > 0:
- self.profile_data.change_profile_increase()
-
- self.user_move_vector = (0, 0)
-
- def render(self, last_frame_time):
- self.window.fill(self.base_window_fill_color)
-
- # Initial y
- y = 50
-
- # Title
- #surface = self.titleFont.render("TANK BATTLEGROUNDS !!", True, (200, 0, 0))
- #x = (self.window.get_width() - surface.get_width()) // 2
- #self.window.blit(surface, (x, y))
- #y += (200 * surface.get_height()) // 100
-
- x = 50 + 64 + 5
-
- # Compute menu width
- #menuWidth = 0
- #for item in self.menuItems:
- # surface = self.itemFont.render(item['title'], True, (200, 0, 0))
- # menuWidth = max(menuWidth, surface.get_width())
- # item['surface'] = surface
-
- y_pad = 5
- font_selected_item_height = None
- selected_item_y = y
- for index, item in enumerate(self.menu_items):
- surface = self.menu_font_render(item)
- self.window.blit(surface, (x, y))
- if self.state.menu_selected_index == index:
- font_selected_item_height = surface.get_height()
- selected_item_y = y
- y += surface.get_height() + y_pad
-
- self.window.blit(self.image_right_arrow, (50, selected_item_y - 64//2
- + font_selected_item_height//2))
-
-
- fps_surface = self.menu_font_render(f'{self.clock.get_fps():02.0f}')
- fps_pos = (self.window.get_width() - fps_surface.get_width(), 0)
- self.window.blit(fps_surface, fps_pos)
-
- ## Draw menu items
- #x = (self.window.get_width() - menuWidth) // 2
- #for index, item in enumerate(self.menuItems):
- # # Item text
- # surface = item['surface']
- # self.window.blit(surface, (x, y))
-
- # # Cursor
- # if index == self.currentMenuItem:
- # cursorX = x - self.menuCursor.get_width() - 10
- # cursorY = y + (surface.get_height() - self.menuCursor.get_height()) // 2
- # self.window.blit(self.menuCursor, (cursorX, cursorY))
-
- # y += (120 * surface.get_height()) // 100
-
-
- #pygame.draw.rect(self.window,
- # (0,0,255),
- # (120,120,400,240))
- self.profile_renderer.render(self.window)
-
- pygame.display.update()
-
-
-app = GuiAppSub1()
-app.loop()