From e2ae2b762a125d87fdfcc2a7828727a551e14d13 Mon Sep 17 00:00:00 2001 From: SpookyDervish Date: Sun, 18 Jan 2026 13:26:11 +1100 Subject: [PATCH] you can drag things left and right and the mute and solo buttons update the ui now --- src/song_player.py | 3 +- src/test_project.tdp | Bin 384239 -> 384239 bytes src/ui/app.py | 47 ++++++++++++------------ src/ui/screens/settings.py | 2 +- src/ui/widgets/channel.py | 25 +++++++++++-- src/ui/widgets/chunk_types/audio.py | 8 ++--- src/ui/widgets/chunk_types/chunk.py | 54 ++++++++++++++++++++++++++-- src/ui/widgets/sidebar.py | 10 ------ src/ui/widgets/timeline.py | 20 +++-------- 9 files changed, 111 insertions(+), 58 deletions(-) diff --git a/src/song_player.py b/src/song_player.py index b811494..4f8a82b 100644 --- a/src/song_player.py +++ b/src/song_player.py @@ -18,7 +18,8 @@ class SongPlayer: self.timer = Timer( timeline, 0.1, - pause=True + pause=True, + skip=False ) def play_callback(self, out, frames, time, status): diff --git a/src/test_project.tdp b/src/test_project.tdp index 1b36e63cee705a4254c5209747784cf807d8ef55..416c74052377008b1c719e30b4af38c2631ef851 100644 GIT binary patch delta 36 ucmV+<0NelXxfk!b7m%t5Kku#-dhX)#fwG0N0fn*xg|Y*MvIMoV1wGtqq!Nn& delta 36 ncmaFAQ~do-@d>Lq7@(kWQ|l(i)=f;Uo0wZSv25MMYI_F&^Y9H{ diff --git a/src/ui/app.py b/src/ui/app.py index 5e22734..84a7b91 100644 --- a/src/ui/app.py +++ b/src/ui/app.py @@ -100,7 +100,27 @@ class AppUI(App): self.open_project_path = path - ### load all the ui elements + ### load all the ui element + + # timeline tracks + timeline = self.query_one(Timeline) + rows = timeline.query_one("#rows") + + for i, channel in enumerate(self.project.channels): + row = TimelineRow(channel, id=f"row-{i}") + + + row.styles.width = timeline.bar_offset * self.project.song_length + rows.mount(row) + + for chunk in channel.chunks: + + if chunk.chunk_type == ChunkType.CHUNK: + row.mount(Chunk(chunk, chunk_name=chunk.name, bar_pos=chunk.position)) + elif chunk.chunk_type == ChunkType.AUDIO: + row.mount(AudioChunk(chunk, chunk.name, chunk.position)) + + # sidebar channels sidebar_channels = self.query_one("#channels") @@ -119,26 +139,7 @@ class AppUI(App): 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) - - 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)) - + ), before=-1) self.notify(f"Loaded \"{path}\"") @@ -162,11 +163,13 @@ class AppUI(App): case _: self.notify("Sorry, that isn't implemented yet... ;-;", severity="warning") + + @on(Tabs.TabActivated) async def top_menu_tab_clicked(self, event: Tabs.TabActivated): + event.tabs.active = None if self.first_tab_click: - event.tabs.active = None self.first_tab_click = False return diff --git a/src/ui/screens/settings.py b/src/ui/screens/settings.py index e7c5714..47bf410 100644 --- a/src/ui/screens/settings.py +++ b/src/ui/screens/settings.py @@ -102,7 +102,7 @@ class SettingsScreen(ModalScreen): with HorizontalGroup(classes="setting"): with VerticalGroup(): yield Label("Colour Theme", classes="setting-name") - yield Label("Colour theme used for the entire Berry app. You can get more themes with plugins!", classes="setting-desc") + yield Label("Colour theme used for the entire app.", classes="setting-desc") yield Select.from_values( (theme_name for theme_name in self.app._registered_themes.keys() if theme_name != "textual-ansi"), diff --git a/src/ui/widgets/channel.py b/src/ui/widgets/channel.py index c976e5d..bb129a5 100644 --- a/src/ui/widgets/channel.py +++ b/src/ui/widgets/channel.py @@ -57,15 +57,36 @@ class Channel(VerticalGroup): self.solo = solo self.pan = pan self.volume = volume - print(self.channel_name) + print("hi") + print(self.timeline_row) + + def on_mount(self): + if self.muted: + self.timeline_row.add_class("-muted") + if self.solo: + self.timeline_row.add_class("-solo") + + @property + def timeline_row(self): + return self.app.query_one(f"#row-{self.channel_index}") def on_checkbox_changed(self, event: Checkbox.Changed): if event.checkbox.id == "mute": self.muted = event.value + self.project_channel.mute = self.muted - self.app.query_one("#timeline").query_one() + if self.muted: + self.timeline_row.add_class("-muted") + else: + self.timeline_row.remove_class("-muted") elif event.checkbox.id == "solo": self.solo = event.value + self.project_channel.solo = self.solo + + if self.solo: + self.timeline_row.add_class("-solo") + else: + self.timeline_row.remove_class("-solo") def on_slider_changed(self, event: Slider.Changed): if event.slider.id == "volume": diff --git a/src/ui/widgets/chunk_types/audio.py b/src/ui/widgets/chunk_types/audio.py index 59cf456..dd84996 100644 --- a/src/ui/widgets/chunk_types/audio.py +++ b/src/ui/widgets/chunk_types/audio.py @@ -23,11 +23,11 @@ class AudioChunk(Chunk): } """ - def __init__(self, audio_data: np.ndarray, sample_rate: int, chunk_name: str = "Sample", bar_pos: float = 0.0): - super().__init__(chunk_name, bar_pos) + def __init__(self, project_chunk, chunk_name: str = "Sample", bar_pos: float = 0.0): + super().__init__(project_chunk, chunk_name, bar_pos) - self.audio = audio_data - self.sample_rate = sample_rate + self.audio = project_chunk.audio_data + self.sample_rate = project_chunk.sample_rate self.num_channels = None if len(self.audio.shape) == 1: diff --git a/src/ui/widgets/chunk_types/chunk.py b/src/ui/widgets/chunk_types/chunk.py index fc9da32..162433e 100644 --- a/src/ui/widgets/chunk_types/chunk.py +++ b/src/ui/widgets/chunk_types/chunk.py @@ -1,4 +1,9 @@ from textual.containers import Container +from textual import on, events +from textual_window import Window +from textual.widgets import Button + +from project import ChannelChunk class Chunk(Container): @@ -11,15 +16,30 @@ class Chunk(Container): border: tab $surface; background: $surface-darken-1; dock: left; + + &:focus { + background-tint: $primary 25%; + } + + &.dragging { + background: $surface-lighten-1; + } + + } """ - def __init__(self, chunk_name: str = "Chunk", bar_pos: float = 0.0): + can_focus = True + + def __init__(self, project_chunk: ChannelChunk, chunk_name: str = "Chunk", bar_pos: float = 0.0): super().__init__() + self.project_chunk = project_chunk self.chunk_name = chunk_name self.border_title = chunk_name self.bar_pos = bar_pos self.update_offset() + + self.being_dragged = False def update_offset(self, timeline=None): if timeline == None: @@ -27,4 +47,34 @@ class Chunk(Container): self.offset = (timeline.bar_offset * self.bar_pos * 4, 0) def calculate_size(self): - pass \ No newline at end of file + pass + + @on(events.MouseMove) + def continue_drag(self, event: events.MouseUp): + if not self.being_dragged: return + + # move the widget in the ui + offset_x = self.styles.offset.x.cells + event.delta_x + self.styles.offset = (offset_x, 0) + + # update the backend as well + timeline = self.app.query_one("#timeline") + pos = offset_x / (timeline.bar_offset * 4) + + self.bar_pos = pos + self.project_chunk.position = pos + + @on(events.MouseUp) + def end_drag(self, event: events.MouseUp): + if event.button == 1: + self.being_dragged = False + self.remove_class("dragging") + self.release_mouse() + + @on(events.MouseDown) + def start_drag(self, event: events.MouseDown): + if event.button == 1: + self.being_dragged = True + self.add_class("dragging") + self.capture_mouse() + self.focus() \ No newline at end of file diff --git a/src/ui/widgets/sidebar.py b/src/ui/widgets/sidebar.py index aa2df5e..6cb08cd 100644 --- a/src/ui/widgets/sidebar.py +++ b/src/ui/widgets/sidebar.py @@ -29,14 +29,4 @@ class Sidebar(Vertical): def compose(self) -> ComposeResult: with VerticalScroll(id="channels"): - for i, channel in enumerate(self.app.project.channels): - yield Channel( - i, - channel, - channel.name, - channel.mute, - channel.solo, - channel.pan, - channel.volume - ) yield Button("+ New Channel", variant="success", id="add-channel") \ No newline at end of file diff --git a/src/ui/widgets/timeline.py b/src/ui/widgets/timeline.py index 90e6f40..02ba0c7 100644 --- a/src/ui/widgets/timeline.py +++ b/src/ui/widgets/timeline.py @@ -18,17 +18,17 @@ class TimelineRow(Horizontal): margin-bottom: 1; &.-muted { - background: $error 25%; + background-tint: $background-darken-2 50%; } &.-solo { - background: $warning 25%; + background-tint: $warning 20%; } } """ - def __init__(self, project_channel): - super().__init__() + def __init__(self, project_channel, id: str | None = None): + super().__init__(id=id) self.project_channel = project_channel class Timeline(Vertical): @@ -139,18 +139,6 @@ class Timeline(Vertical): with VerticalScroll(id="rows"): - for channel in self.app.project.channels: - with TimelineRow(channel) as row: - - row.styles.width = self.bar_offset * self.app.project.song_length - - for chunk in channel.chunks: - - if chunk.chunk_type == ChunkType.CHUNK: - yield Chunk(chunk_name=chunk.name, bar_pos=chunk.position) - elif chunk.chunk_type == ChunkType.AUDIO: - yield AudioChunk(chunk.audio_data, chunk.sample_rate, chunk.name, chunk.position) - for i in range(1, self.app.project.song_length+1): bar = None if i % 4 == 0: