Files
Terminal-DAW/src/ui/widgets/timeline.py

119 lines
3.6 KiB
Python
Raw Normal View History

from textual.containers import Vertical, VerticalScroll, Horizontal, VerticalGroup, HorizontalGroup
from textual.widgets import Rule
2026-01-13 16:06:57 +11:00
from textual.app import ComposeResult
2026-01-14 12:20:53 +11:00
from textual import on, events
2026-01-13 16:06:57 +11:00
from ui.widgets.chunk_types.audio import AudioChunk, Chunk
from ui.widgets.play_head import PlayHead
from project import ChunkType
2026-01-13 20:06:28 +11:00
2026-01-13 16:06:57 +11:00
class TimelineRow(Horizontal):
DEFAULT_CSS = """
TimelineRow {
background: $surface-lighten-1;
max-height: 8;
2026-01-13 16:06:57 +11:00
margin-bottom: 1;
}
"""
class Timeline(Vertical):
2026-01-13 16:06:57 +11:00
DEFAULT_CSS = """
Timeline {
#rows {
hatch: "-" $surface-lighten-1;
padding: 1 0;
.beat-line {
color: $surface-lighten-1;
}
.bar-line {
color: $surface-lighten-2;
}
.beat-line, .bar-line {
dock: left;
margin: 0;
}
}
PlayHead {
layer: top;
}
2026-01-13 16:06:57 +11:00
}
"""
def __init__(self):
2026-01-14 12:20:53 +11:00
super().__init__(id="timeline")
self.calc_bar_offset()
def calc_bar_offset(self):
self.bar_offset = self.app.project.bpm / 8 * (0.03333333333 / self.app.zoom_level)
@on(events.MouseScrollDown)
async def mouse_scroll_down(self, event: events.MouseScrollDown):
self.app.zoom_level += (self.app.scroll_sensitivity_x / 200)
self.calc_bar_offset()
self.app.last_zoom_level = self.app.zoom_level
self.handle_zoom()
2026-01-14 12:20:53 +11:00
@on(events.MouseScrollUp)
async def mouse_scroll_up(self, event: events.MouseScrollUp):
self.app.zoom_level = max(self.app.zoom_level - self.app.scroll_sensitivity_x/200, 0.001)
if self.app.zoom_level != self.app.last_zoom_level:
self.app.last_zoom_level = self.app.zoom_level
self.calc_bar_offset()
self.handle_zoom()
def handle_zoom(self):
for chunk in self.query(Chunk):
chunk.calculate_size()
chunk.update_offset(self)
for bar_line in self.query(Rule):
if not isinstance(bar_line, PlayHead):
bar_line.offset = (self.bar_offset * bar_line.index, 0)
2026-01-14 13:04:38 +11:00
if self.app.zoom_level >= 0.09 and bar_line.has_class("beat-line"):
bar_line.display = False
else:
bar_line.display = True
2026-01-13 16:06:57 +11:00
def compose(self) -> ComposeResult:
2026-01-14 12:20:53 +11:00
with VerticalScroll(id="rows"):
2026-01-14 12:20:53 +11:00
for channel in self.app.project.channels:
with TimelineRow():
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)
2026-01-14 13:04:38 +11:00
for i in range(1, 17):
bar = None
2026-01-14 12:20:53 +11:00
if i % 4 == 0:
bar = Rule.vertical(classes="bar-line", line_style="double")
else:
bar = Rule.vertical(classes="beat-line")
bar.offset = (self.bar_offset * i, 0)
2026-01-14 12:20:53 +11:00
bar.index = i
yield bar
2026-01-14 12:20:53 +11:00
2026-01-14 13:04:38 +11:00
yield PlayHead()