you can drag things left and right and the mute and solo buttons update the ui now

This commit is contained in:
2026-01-18 13:26:11 +11:00
parent 7828300565
commit e2ae2b762a
9 changed files with 111 additions and 58 deletions

View File

@@ -18,7 +18,8 @@ class SongPlayer:
self.timer = Timer( self.timer = Timer(
timeline, timeline,
0.1, 0.1,
pause=True pause=True,
skip=False
) )
def play_callback(self, out, frames, time, status): def play_callback(self, out, frames, time, status):

Binary file not shown.

View File

@@ -100,7 +100,27 @@ class AppUI(App):
self.open_project_path = path 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
sidebar_channels = self.query_one("#channels") sidebar_channels = self.query_one("#channels")
@@ -121,25 +141,6 @@ class AppUI(App):
channel.volume channel.volume
), before=-1) ), 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))
self.notify(f"Loaded \"{path}\"") self.notify(f"Loaded \"{path}\"")
self.push_screen(FileOpen( self.push_screen(FileOpen(
@@ -163,10 +164,12 @@ class AppUI(App):
case _: case _:
self.notify("Sorry, that isn't implemented yet... ;-;", severity="warning") self.notify("Sorry, that isn't implemented yet... ;-;", severity="warning")
@on(Tabs.TabActivated) @on(Tabs.TabActivated)
async def top_menu_tab_clicked(self, event: Tabs.TabActivated): async def top_menu_tab_clicked(self, event: Tabs.TabActivated):
if self.first_tab_click:
event.tabs.active = None event.tabs.active = None
if self.first_tab_click:
self.first_tab_click = False self.first_tab_click = False
return return

View File

@@ -102,7 +102,7 @@ class SettingsScreen(ModalScreen):
with HorizontalGroup(classes="setting"): with HorizontalGroup(classes="setting"):
with VerticalGroup(): with VerticalGroup():
yield Label("Colour Theme", classes="setting-name") 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( yield Select.from_values(
(theme_name for theme_name in self.app._registered_themes.keys() if theme_name != "textual-ansi"), (theme_name for theme_name in self.app._registered_themes.keys() if theme_name != "textual-ansi"),

View File

@@ -57,15 +57,36 @@ class Channel(VerticalGroup):
self.solo = solo self.solo = solo
self.pan = pan self.pan = pan
self.volume = volume 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): def on_checkbox_changed(self, event: Checkbox.Changed):
if event.checkbox.id == "mute": if event.checkbox.id == "mute":
self.muted = event.value 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": elif event.checkbox.id == "solo":
self.solo = event.value 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): def on_slider_changed(self, event: Slider.Changed):
if event.slider.id == "volume": if event.slider.id == "volume":

View File

@@ -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): def __init__(self, project_chunk, chunk_name: str = "Sample", bar_pos: float = 0.0):
super().__init__(chunk_name, bar_pos) super().__init__(project_chunk, chunk_name, bar_pos)
self.audio = audio_data self.audio = project_chunk.audio_data
self.sample_rate = sample_rate self.sample_rate = project_chunk.sample_rate
self.num_channels = None self.num_channels = None
if len(self.audio.shape) == 1: if len(self.audio.shape) == 1:

View File

@@ -1,4 +1,9 @@
from textual.containers import Container 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): class Chunk(Container):
@@ -11,16 +16,31 @@ class Chunk(Container):
border: tab $surface; border: tab $surface;
background: $surface-darken-1; background: $surface-darken-1;
dock: left; 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__() super().__init__()
self.project_chunk = project_chunk
self.chunk_name = chunk_name self.chunk_name = chunk_name
self.border_title = chunk_name self.border_title = chunk_name
self.bar_pos = bar_pos self.bar_pos = bar_pos
self.update_offset() self.update_offset()
self.being_dragged = False
def update_offset(self, timeline=None): def update_offset(self, timeline=None):
if timeline == None: if timeline == None:
timeline = self.app.query_one("#timeline") timeline = self.app.query_one("#timeline")
@@ -28,3 +48,33 @@ class Chunk(Container):
def calculate_size(self): def calculate_size(self):
pass 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()

View File

@@ -29,14 +29,4 @@ class Sidebar(Vertical):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
with VerticalScroll(id="channels"): 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") yield Button("+ New Channel", variant="success", id="add-channel")

View File

@@ -18,17 +18,17 @@ class TimelineRow(Horizontal):
margin-bottom: 1; margin-bottom: 1;
&.-muted { &.-muted {
background: $error 25%; background-tint: $background-darken-2 50%;
} }
&.-solo { &.-solo {
background: $warning 25%; background-tint: $warning 20%;
} }
} }
""" """
def __init__(self, project_channel): def __init__(self, project_channel, id: str | None = None):
super().__init__() super().__init__(id=id)
self.project_channel = project_channel self.project_channel = project_channel
class Timeline(Vertical): class Timeline(Vertical):
@@ -139,18 +139,6 @@ class Timeline(Vertical):
with VerticalScroll(id="rows"): 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): for i in range(1, self.app.project.song_length+1):
bar = None bar = None
if i % 4 == 0: if i % 4 == 0: