From f45071e62a1918c8c7dda6f4890f286b06c32380 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Wed, 18 Jun 2025 17:09:34 +0200 Subject: [PATCH] Add file and directory selector(file_manager) --- menus/add_music.py | 19 +++++-- menus/file_manager.py | 125 ++++++++++++++++++++++++++++++++++++++++++ menus/new_tab.py | 17 +++++- 3 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 menus/file_manager.py diff --git a/menus/add_music.py b/menus/add_music.py index f6256d6..5299e78 100644 --- a/menus/add_music.py +++ b/menus/add_music.py @@ -1,13 +1,13 @@ import arcade, arcade.gui, os, json -from utils.constants import button_style +from utils.constants import button_style, audio_extensions from utils.preload import button_texture, button_hovered_texture from utils.utils import UIFocusTextureButton - +from menus.file_manager import FileManager from arcade.gui.experimental.focus import UIFocusGroup class AddMusic(arcade.gui.UIView): - def __init__(self, pypresence_client, current_mode, current_music_name, current_length, current_music_player, queue, loaded_sounds, shuffle): + def __init__(self, pypresence_client, current_mode, current_music_name, current_length, current_music_player, queue, loaded_sounds, shuffle, music_file_selected=None): super().__init__() self.current_mode = current_mode @@ -17,6 +17,7 @@ class AddMusic(arcade.gui.UIView): self.queue = queue self.loaded_sounds = loaded_sounds self.shuffle = shuffle + self.music_file_selected = music_file_selected with open("settings.json", "r", encoding="utf-8") as file: self.settings_dict = json.load(file) @@ -33,9 +34,14 @@ class AddMusic(arcade.gui.UIView): self.box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10), anchor_x="center", anchor_y="center") self.playlist_label = self.box.add(arcade.gui.UILabel(text="Playlist", font_name="Roboto", font_size=32)) + self.playlist_option = self.box.add(arcade.gui.UIDropdown(default=list(self.playlists.keys())[0], options=list(self.playlists.keys()), width=self.window.width / 2, height=self.window.height / 15, primary_style=button_style, dropdown_style=button_style, active_style=button_style)) + self.music_label = self.box.add(arcade.gui.UILabel(text="Music File Path", font_name="Roboto", font_size=32)) - self.add_music_input = self.box.add(arcade.gui.UIInputText(font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) + + self.add_music_input = self.box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=f'Select File ({self.music_file_selected})', style=button_style, font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) + self.add_music_input.on_click = lambda event: self.select_file() + self.add_music_button = self.box.add(UIFocusTextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add Music', style=button_style, width=self.window.width / 2, height=self.window.height / 10)) self.add_music_button.on_click = lambda event: self.add_music() @@ -44,8 +50,11 @@ class AddMusic(arcade.gui.UIView): self.anchor.detect_focusable_widgets() + def select_file(self): + self.window.show_view(FileManager(os.path.expanduser("~"), [f".{extension}" for extension in audio_extensions], "file", self.pypresence_client, self.current_mode, self.current_music_name, self.current_length, self.current_music_player, self.queue, self.loaded_sounds, self.shuffle)) + def add_music(self): - music_path = self.add_music_input.text + music_path = self.music_file_selected playlist = self.playlist_option.value if not music_path: diff --git a/menus/file_manager.py b/menus/file_manager.py new file mode 100644 index 0000000..b959093 --- /dev/null +++ b/menus/file_manager.py @@ -0,0 +1,125 @@ +import arcade, arcade.gui, os, time + +from utils.constants import button_style +from utils.preload import button_texture, button_hovered_texture + +from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar + +class FileManager(arcade.gui.UIView): + def __init__(self, start_directory, allowed_extensions, select_mode="dir", *args): + super().__init__() + + self.select_mode = select_mode + self.current_directory = start_directory + self.allowed_extensions = allowed_extensions + self.file_buttons = [] + self.submitted_content = "" + self.done = False + self.args = args + + self.anchor = self.ui.add(arcade.gui.UIAnchorLayout(size_hint=(1, 1))) + self.box = self.anchor.add(arcade.gui.UIBoxLayout(size_hint=(0.7, 0.7)), anchor_x="center", anchor_y="center") + + self.content_cache = {} + self.pre_cache_contents() + + def on_show_view(self): + super().on_show_view() + + self.current_directory_label = self.anchor.add(arcade.gui.UILabel(text=self.current_directory, font_name="Roboto", font_size=24), anchor_x="center", anchor_y="top", align_y=-15) + + self.scroll_area = UIScrollArea(size_hint=(0.95, 1)) # center on screen + self.scroll_area.scroll_speed = -50 + self.box.add(self.scroll_area) + + self.scrollbar = UIScrollBar(self.scroll_area) + self.scrollbar.size_hint = (0.02, 1) + self.anchor.add(self.scrollbar, anchor_x="right", anchor_y="center") + + self.files_box = arcade.gui.UIBoxLayout(space_between=5) + self.scroll_area.add(self.files_box) + + self.back_button = self.anchor.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50), anchor_x="left", anchor_y="top", align_x=5, align_y=-5) + self.back_button.on_click = lambda event: self.change_directory(os.path.dirname(self.current_directory)) + + self.show_directory() + + if self.select_mode == "directory": + self.submit_button = self.anchor.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="Submit", style=button_style, width=self.window.width / 10, height=self.window.height / 10), anchor_x="right", anchor_y="bottom", align_x=-self.window.width / 30) + self.submit_button.on_click = lambda event: self.submit(self.current_directory) + + def submit(self, content): + self.submitted_content = content + self.done = True + + if self.select_mode == "file": + from menus.add_music import AddMusic + self.window.show_view(AddMusic(*self.args, music_file_selected=self.submitted_content)) + elif self.select_mode == "directory": + from menus.new_tab import NewTab + self.window.show_view(NewTab(*self.args, directory_selected=self.submitted_content)) + + def get_content(self, directory): + if not directory in self.content_cache or time.perf_counter() - self.content_cache[directory][-1] >= 30: + entries = os.listdir(directory) + + filtered = [ + entry for entry in entries + if (os.path.isdir(os.path.join(directory, entry)) and not "." in entry) or + os.path.splitext(entry)[1].lower() in self.allowed_extensions + ] + + sorted_entries = sorted( + filtered, + key=lambda x: (0 if os.path.isdir(os.path.join(directory, x)) else 1, x.lower()) + ) + + self.content_cache[directory] = sorted_entries + self.content_cache[directory].append(time.perf_counter()) + + return self.content_cache[directory][:-1] + + def pre_cache_contents(self): + for directory in self.walk_limited_depth(self.current_directory): + self.get_content(directory) + + def walk_limited_depth(self, start_dir, max_depth=2): + start_dir = os.path.abspath(start_dir) + + def _walk(current_dir, current_depth): + if current_depth > max_depth: + return + + yield current_dir + try: + with os.scandir(current_dir) as it: + for entry in it: + if entry.is_dir(follow_symlinks=False): + yield from _walk(entry.path, current_depth + 1) + except PermissionError: + pass # skip directories you can't access + + return _walk(start_dir, 0) + + def show_directory(self): + self.files_box.clear() + self.file_buttons.clear() + + self.current_directory_label.text = self.current_directory + + for file in self.get_content(self.current_directory): + self.file_buttons.append(self.files_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=file, style=button_style, width=self.window.width / 1.5))) + + if os.path.isdir(f"{self.current_directory}/{file}"): + self.file_buttons[-1].on_click = lambda event, directory=f"{self.current_directory}/{file}": self.change_directory(directory) + elif self.select_mode == "file": + self.file_buttons[-1].on_click = lambda event, file=f"{self.current_directory}/{file}": self.submit(file) + + def change_directory(self, directory): + self.current_directory = directory + + self.show_directory() + + def on_mouse_press(self, x, y, button, modifiers): + if button == arcade.MOUSE_BUTTON_RIGHT: + self.change_directory(os.path.dirname(self.current_directory)) \ No newline at end of file diff --git a/menus/new_tab.py b/menus/new_tab.py index 35cf14e..7692f54 100644 --- a/menus/new_tab.py +++ b/menus/new_tab.py @@ -3,10 +3,12 @@ import arcade, arcade.gui, os, json from utils.constants import button_style from utils.preload import button_texture, button_hovered_texture +from menus.file_manager import FileManager + from arcade.gui.experimental.focus import UIFocusGroup class NewTab(arcade.gui.UIView): - def __init__(self, pypresence_client, current_mode, current_music_name, current_length, current_music_player, queue, loaded_sounds, shuffle): + def __init__(self, pypresence_client, current_mode, current_music_name, current_length, current_music_player, queue, loaded_sounds, shuffle, directory_selected=None): super().__init__() self.current_mode = current_mode @@ -16,6 +18,7 @@ class NewTab(arcade.gui.UIView): self.queue = queue self.loaded_sounds = loaded_sounds self.shuffle = shuffle + self.directory_selected = directory_selected with open("settings.json", "r", encoding="utf-8") as file: self.settings_dict = json.load(file) @@ -34,12 +37,17 @@ class NewTab(arcade.gui.UIView): if self.current_mode == "files": self.new_tab_label = self.box.add(arcade.gui.UILabel(text="New Tab Path:", font_name="Roboto", font_size=32)) - self.new_tab_input = self.box.add(arcade.gui.UIInputText(font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) + + self.add_music_input = self.box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=f'Select Directory ({self.directory_selected})', style=button_style, font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) + self.add_music_input.on_click = lambda event: self.select_directory() + self.new_tab_button = self.box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add new tab', style=button_style, width=self.window.width / 2, height=self.window.height / 10)) self.new_tab_button.on_click = lambda event: self.add_tab() elif self.current_mode == "playlist": self.new_tab_label = self.box.add(arcade.gui.UILabel(text="New Playlist Name:", font_name="Roboto", font_size=32)) + self.new_tab_input = self.box.add(arcade.gui.UIInputText(font_name="Roboto", font_size=32, width=self.window.width / 2, height=self.window.height / 10)) + self.new_tab_button = self.box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Add new Playlist', style=button_style, width=self.window.width / 2, height=self.window.height / 10)) self.new_tab_button.on_click = lambda event: self.add_tab() @@ -48,9 +56,12 @@ class NewTab(arcade.gui.UIView): self.anchor.detect_focusable_widgets() + def select_directory(self): + self.window.show_view(FileManager(os.path.expanduser("~"), [], "directory", self.pypresence_client, self.current_mode, self.current_music_name, self.current_length, self.current_music_player, self.queue, self.loaded_sounds, self.shuffle)) + def add_tab(self): if self.current_mode == "files": - tab_path = self.new_tab_input.text + tab_path = self.directory_selected if not tab_path: return