mirror of
https://github.com/kleinpanic/Media-Tui.git
synced 2025-10-27 15:05:33 -04:00
220 lines
8.0 KiB
Python
220 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
||
|
||
# main.py
|
||
|
||
import curses
|
||
import time
|
||
from local_music import LocalMusicPlayer
|
||
from local_media import LocalMediaPlayer
|
||
from spotify_player import SpotifyPlayer
|
||
from radio_player import RadioPlayer
|
||
|
||
class MediaDashboardApp:
|
||
def __init__(self, stdscr):
|
||
self.stdscr = stdscr
|
||
self.monocle_mode = False
|
||
self.active_window = 0
|
||
self.windows = [
|
||
LocalMusicPlayer(self.stdscr),
|
||
LocalMediaPlayer(self.stdscr),
|
||
SpotifyPlayer(self.stdscr),
|
||
RadioPlayer(self.stdscr)
|
||
]
|
||
self.window_titles = ["Local Music", "Local Media", "Spotify", "Radio"]
|
||
self.setup_curses()
|
||
|
||
def setup_curses(self):
|
||
curses.curs_set(0) # Hide the cursor
|
||
self.stdscr.nodelay(1)
|
||
self.stdscr.timeout(100)
|
||
curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
|
||
|
||
def draw_tiling(self):
|
||
self.stdscr.clear()
|
||
height, width = self.stdscr.getmaxyx()
|
||
mid_y = height // 2
|
||
mid_x = width // 2
|
||
|
||
# Define windows (quadrants), ensuring they fit within the screen dimensions
|
||
for idx, module in enumerate(self.windows):
|
||
if not module:
|
||
continue
|
||
# Calculate positions for quadrants
|
||
if idx == 0:
|
||
row, col = 0, 0
|
||
win_height = mid_y
|
||
win_width = mid_x
|
||
elif idx == 1:
|
||
row, col = 0, mid_x
|
||
win_height = mid_y
|
||
win_width = width - mid_x
|
||
elif idx == 2:
|
||
row, col = mid_y, 0
|
||
win_height = height - mid_y
|
||
win_width = mid_x
|
||
elif idx == 3:
|
||
row, col = mid_y, mid_x
|
||
win_height = height - mid_y
|
||
win_width = width - mid_x
|
||
|
||
# Sub-window for each quadrant
|
||
subwin = self.stdscr.subwin(win_height, win_width, row, col)
|
||
subwin.box()
|
||
subwin.addstr(1, 2, self.window_titles[idx] + ":")
|
||
|
||
# Only render Spotify quadrant if it is the active window
|
||
if isinstance(module, SpotifyPlayer) and self.active_window != 2:
|
||
continue # Skip rendering Spotify unless it’s active in monocle
|
||
|
||
module.render(subwin)
|
||
|
||
# Draw lines separating the windows
|
||
self.stdscr.vline(0, mid_x, curses.ACS_VLINE, height)
|
||
self.stdscr.hline(mid_y, 0, curses.ACS_HLINE, width)
|
||
|
||
self.stdscr.refresh()
|
||
|
||
def draw_monocle(self):
|
||
self.stdscr.clear()
|
||
width = self.stdscr.getmaxyx()[1] # Only get the width, as height is not needed
|
||
|
||
# Draw the active window in monocle mode
|
||
module = self.windows[self.active_window]
|
||
if not module:
|
||
return
|
||
|
||
title = self.window_titles[self.active_window]
|
||
self.stdscr.addstr(0, (width // 2) - (len(title) // 2), f"{title}:", curses.A_BOLD)
|
||
|
||
try:
|
||
module.render(self.stdscr)
|
||
except Exception as e:
|
||
self.stdscr.addstr(1, 1, f"Error loading {title}: {str(e)}")
|
||
logging.error(f"Error rendering module {title}: {str(e)}")
|
||
self.stdscr.refresh()
|
||
|
||
def handle_mouse(self, event):
|
||
"""Handle mouse clicks and interactions."""
|
||
if self.monocle_mode: #and self.active_window is not None:
|
||
module = self.windows[self.active_window]
|
||
if module and hasattr(module, 'handle_mouse'):
|
||
module.handle_mouse(event)
|
||
return
|
||
|
||
_, x, y, _, button = event
|
||
|
||
# Basic idea - Determine the clicked quadrant based on coordinates
|
||
height, width = self.stdscr.getmaxyx()
|
||
mid_y = height // 2
|
||
mid_x = width // 2
|
||
|
||
if button == curses.BUTTON1_CLICKED:
|
||
if y < mid_y and x < mid_x:
|
||
self.active_window = 0 # Local Music
|
||
elif y < mid_y and x >= mid_x:
|
||
self.active_window = 1 # Local Media
|
||
elif y >= mid_y and x < mid_x:
|
||
self.active_window = 2 # Spotify
|
||
elif y >= mid_y and x >= mid_x:
|
||
self.active_window = 3 # Radio
|
||
else:
|
||
return
|
||
|
||
module = self.windows[self.active_window]
|
||
if module is not None:
|
||
self.monocle_mode = True
|
||
# Set current_view to "explorer" for the active windows
|
||
module.current_view = "radio" if isinstance(module, RadioPlayer) else "explorer"
|
||
# Reset any selection indices if needed
|
||
module.selected_index = 0
|
||
if isinstance(module, SpotifyPlayer):
|
||
module.current_playlist = None
|
||
self.draw_monocle()
|
||
else:
|
||
# Display a message
|
||
self.stdscr.addstr(0, 0, "This quadrant is not implemented yet.", curses.A_BOLD)
|
||
self.stdscr.refresh()
|
||
time.sleep(1)
|
||
#pass
|
||
|
||
def cleanup(self):
|
||
"""Clean up resources before exiting."""
|
||
for module in self.windows:
|
||
if module and hasattr(module, 'stop_media'):
|
||
module.stop_media()
|
||
if module and hasattr(module, 'stop_station'):
|
||
module.stop_station()
|
||
|
||
def handle_keypress(self, key):
|
||
"""Handle keypress actions globally and pass them to active modules."""
|
||
module = self.windows[self.active_window]
|
||
key_handled = False
|
||
if self.monocle_mode and module:
|
||
# If module handles the key, skip global handling
|
||
key_handled = module.handle_keypress(key)
|
||
|
||
# Check if the active window wants to exit monocle mode
|
||
if hasattr(module, 'current_view') and module.current_view == "exit":
|
||
self.monocle_mode = False
|
||
self.draw_tiling()
|
||
return True
|
||
|
||
if not key_handled:
|
||
if key == ord('q') or key == 27: # Quit on 'q' or 'Esc'
|
||
self.cleanup()
|
||
return True
|
||
elif key == ord('m'): # Monocle mode
|
||
self.monocle_mode = True
|
||
self.draw_monocle()
|
||
return True
|
||
elif key == ord('t'): # Tiling mode
|
||
self.monocle_mode = False
|
||
self.draw_tiling()
|
||
elif self.monocle_mode and key == ord('j'):
|
||
# Only change monocle window if module is in 'dashboard' view
|
||
if module.current_view == 'dashboard' and self.active_window < len(self.windows) - 1:
|
||
self.active_window += 1
|
||
self.draw_monocle()
|
||
elif self.monocle_mode and key == ord('k'):
|
||
if module.current_view == 'dashboard' and self.active_window > 0:
|
||
self.active_window -= 1
|
||
self.draw_monocle()
|
||
elif key == curses.KEY_MOUSE:
|
||
self.handle_mouse(curses.getmouse())
|
||
|
||
# Handle back to tiling mode directly if module is in 'dashboard' view
|
||
if key in (curses.KEY_BACKSPACE, ord('\b'), 127) and self.monocle_mode:
|
||
if module.current_view == 'dashboard':
|
||
self.monocle_mode = False
|
||
self.draw_tiling()
|
||
|
||
def main_loop(self):
|
||
# Main loop to keep the screen updated
|
||
while True:
|
||
key = self.stdscr.getch()
|
||
|
||
# Break out of loop if keypress handler requests it
|
||
if self.handle_keypress(key):
|
||
break
|
||
|
||
# Periodically check playback status
|
||
if self.monocle_mode and self.active_window is not None:
|
||
module = self.windows[self.active_window]
|
||
if module and hasattr(module, 'check_playback_status'):
|
||
module.check_playback_status()
|
||
|
||
# Redraw based on current mode
|
||
if self.monocle_mode:
|
||
self.draw_monocle()
|
||
else:
|
||
self.draw_tiling()
|
||
|
||
time.sleep(0.1) # Adjust for responsiveness
|
||
|
||
def main(stdscr):
|
||
app = MediaDashboardApp(stdscr)
|
||
app.main_loop()
|
||
|
||
if __name__ == "__main__":
|
||
curses.wrapper(main)
|