Files
Terminal-DAW/src/ui/app.py

178 lines
6.4 KiB
Python
Raw Normal View History

2026-01-13 16:06:57 +11:00
from textual.app import App, ComposeResult
2026-01-14 14:43:57 +11:00
from textual.widgets import Footer, Tab, Tabs, Header
2026-01-15 07:58:09 +11:00
from textual import on, events
2026-01-13 16:06:57 +11:00
2026-01-15 16:24:43 +11:00
from textual_fspicker import FileOpen, FileSave, Filters
2026-01-13 16:06:57 +11:00
from ui.widgets.sidebar import Sidebar
2026-01-14 15:51:34 +11:00
from ui.widgets.timeline import Timeline, TimelineRow
2026-01-13 21:33:25 +11:00
from ui.widgets.project_settings import ProjectSettings
2026-01-14 15:51:34 +11:00
from ui.widgets.channel import Channel
2026-01-15 16:24:43 +11:00
from ui.widgets.context_menu import ContextMenu, NoSelectStatic
2026-01-14 15:51:34 +11:00
from project import ProjectChannel, Project, ChunkType
from ui.widgets.chunk_types.audio import AudioChunk, Chunk
2026-01-13 16:06:57 +11:00
class AppUI(App):
CSS_PATH = "../assets/style.tcss"
2026-01-14 09:11:09 +11:00
def __init__(self, project):
2026-01-13 20:06:28 +11:00
super().__init__()
2026-01-14 07:02:32 +11:00
self.zoom_level = 0.05
2026-01-14 12:20:53 +11:00
self.last_zoom_level = self.zoom_level
2026-01-14 09:11:09 +11:00
self.project = project
2026-01-15 07:58:09 +11:00
2026-01-15 16:24:43 +11:00
self.open_project_path = None
self.first_tab_click = True # stupid events firing when the app is first composed :/
2026-01-15 07:58:09 +11:00
@on(events.Key)
async def key_pressed(self, event: events.Key):
if event.key == "space":
timeline = self.query_one(Timeline)
if not timeline.song_player.paused:
timeline.song_player.pause()
else:
timeline.song_player.play_project(self.app.project)
2026-01-14 14:43:57 +11:00
2026-01-14 15:51:34 +11:00
def create_channel(self, name: str):
2026-01-15 16:24:43 +11:00
new_project_channel = ProjectChannel(
name
)
2026-01-14 15:51:34 +11:00
self.query_one("#channels").mount(Channel(
len(self.project.channels),
2026-01-15 16:24:43 +11:00
new_project_channel,
2026-01-14 15:51:34 +11:00
name,
2026-01-15 16:24:43 +11:00
2026-01-14 15:51:34 +11:00
), before=-1)
2026-01-15 16:24:43 +11:00
self.query_one("#rows").mount(TimelineRow(new_project_channel))
2026-01-14 15:51:34 +11:00
2026-01-15 16:24:43 +11:00
self.project.channels.append(new_project_channel)
def handle_menu_click(self, choice: str):
# user clicked off the menu
if choice == None: return
match choice:
case "Save":
if not self.open_project_path:
self.handle_menu_click("Save as") # just move it to save as
return
self.project.write_to_file(self.open_project_path)
self.notify("Saved project.")
case "Save as":
def callback(path: str):
if path == None: return
self.open_project_path = path
self.project.write_to_file(path)
self.notify(f"Saved as \"{path}\"", title="Saved")
self.push_screen(FileSave(
filters=Filters(
("TerminalDAW Project File", lambda p: p.suffix.lower() == ".tdp"),
("Any", lambda _: True)
2026-01-15 16:24:43 +11:00
)
), callback=callback)
case "Open":
def callback(path: str):
if path == None: return
self.project = Project.from_file(path)
self.open_project_path = path
### load all the ui elements
# sidebar channels
sidebar_channels = self.query_one("#channels")
# we cant use sidebar_channels.remove_children() because that would
# also remove the "Add Channel" button
for child in sidebar_channels.children:
if child.id != "add-channel":
child.remove()
for i, channel in enumerate(self.app.project.channels):
sidebar_channels.mount(Channel(
i,
channel,
channel.name,
channel.mute,
channel.solo,
channel.pan,
channel.volume
), before=-1)
# timeline tracks
timeline = self.query_one(Timeline)
rows = timeline.query_one("#rows")
for channel in self.project.channels:
row = TimelineRow(channel)
row.styles.width = timeline.bar_offset * self.project.song_length
rows.mount(row)
2026-01-15 16:24:43 +11:00
for chunk in channel.chunks:
if chunk.chunk_type == ChunkType.CHUNK:
row.mount(Chunk(chunk_name=chunk.name, bar_pos=chunk.position))
elif chunk.chunk_type == ChunkType.AUDIO:
row.mount(AudioChunk(chunk.audio_data, chunk.sample_rate, chunk.name, chunk.position))
self.notify(f"Loaded \"{path}\"")
self.push_screen(FileOpen(
filters=Filters(
("TerminalDAW Project File", lambda p: p.suffix.lower() == ".tdp"),
("Any", lambda _: True)
2026-01-15 16:24:43 +11:00
)
), callback=callback)
case _:
self.notify("Sorry, that isn't implemented yet... ;-;", severity="warning")
@on(Tabs.TabActivated)
async def top_menu_tab_clicked(self, event: Tabs.TabActivated):
if self.first_tab_click:
event.tabs.active = None
self.first_tab_click = False
return
options = {
"File": [
"Save",
"Save as",
"Open",
"Render",
"Settings"
],
"Edit": [
"Copy",
"Paste",
"Cut"
],
"About": [
"Chookspace repo",
"Copyright"
]
}
self.app.push_screen(ContextMenu(options[event.tab.label], self.app.mouse_position + (0, 1)), callback=self.handle_menu_click)
2026-01-14 15:51:34 +11:00
2026-01-13 16:06:57 +11:00
def compose(self) -> ComposeResult:
2026-01-15 16:24:43 +11:00
yield Tabs(
Tab("File"),
Tab("Edit"),
Tab("About"),
id="top-menu")
2026-01-13 16:06:57 +11:00
yield Sidebar()
yield Timeline()
2026-01-13 21:33:25 +11:00
yield ProjectSettings()
2026-01-13 16:06:57 +11:00
yield Footer()