Remove track list from AudioOptions
This commit is contained in:
+25
-350
@@ -1,16 +1,12 @@
|
|||||||
package page
|
package page
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-gl/gl/v3.3-core/gl"
|
"github.com/go-gl/gl/v3.3-core/gl"
|
||||||
"github.com/go-gl/mathgl/mgl32"
|
"github.com/go-gl/mathgl/mgl32"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
"gorm.io/gorm"
|
|
||||||
"rhemawavetech.com/rhemawave/internal/colors"
|
"rhemawavetech.com/rhemawave/internal/colors"
|
||||||
"rhemawavetech.com/rhemawave/internal/db"
|
"rhemawavetech.com/rhemawave/internal/db"
|
||||||
"rhemawavetech.com/rhemawave/internal/fontManager"
|
"rhemawavetech.com/rhemawave/internal/fontManager"
|
||||||
@@ -19,6 +15,9 @@ import (
|
|||||||
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AudioFileList is kept as the canonical seed data for the audio_files table.
|
||||||
|
// Track selection UI no longer lives on this page, but other parts of the
|
||||||
|
// app still read these rows from the database.
|
||||||
var AudioFileList = []db.AudioFile{
|
var AudioFileList = []db.AudioFile{
|
||||||
{ID: 14, Name: "Original Audio", Path: "OriginalAudio.wav", Selected: false, Order: 1},
|
{ID: 14, Name: "Original Audio", Path: "OriginalAudio.wav", Selected: false, Order: 1},
|
||||||
{ID: 1, Name: "174 Hz", Path: "174Hz.wav", Selected: false, Order: 2},
|
{ID: 1, Name: "174 Hz", Path: "174Hz.wav", Selected: false, Order: 2},
|
||||||
@@ -36,13 +35,7 @@ var AudioFileList = []db.AudioFile{
|
|||||||
{ID: 13, Name: "Pineal Gland", Path: "PinealGland.wav", Selected: false, Order: 14},
|
{ID: 13, Name: "Pineal Gland", Path: "PinealGland.wav", Selected: false, Order: 14},
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const triangleSize = float32(16)
|
||||||
triangleSize = float32(16)
|
|
||||||
visibleCount = 9
|
|
||||||
listShiftDown = int32(50)
|
|
||||||
scrollbarWidth = int32(4)
|
|
||||||
scrollbarGap = int32(16) // gap between right edge of list and scrollbar
|
|
||||||
)
|
|
||||||
|
|
||||||
type AudioOptions struct {
|
type AudioOptions struct {
|
||||||
Base
|
Base
|
||||||
@@ -58,7 +51,7 @@ type AudioOptions struct {
|
|||||||
// Audio toggle
|
// Audio toggle
|
||||||
AudioStatusToggle StringItem
|
AudioStatusToggle StringItem
|
||||||
|
|
||||||
// Volume row (excluded from focus)
|
// Volume row
|
||||||
VolumeLabel StringItem
|
VolumeLabel StringItem
|
||||||
volumePercent StringItem
|
volumePercent StringItem
|
||||||
volumeBarX int32
|
volumeBarX int32
|
||||||
@@ -66,33 +59,6 @@ type AudioOptions struct {
|
|||||||
volumeBarW int32
|
volumeBarW int32
|
||||||
volumeBarH int32
|
volumeBarH int32
|
||||||
|
|
||||||
// Separator + track section
|
|
||||||
separatorY int32
|
|
||||||
tracksHeading StringItem
|
|
||||||
trackItems []StringItem
|
|
||||||
|
|
||||||
// Selected track (-1 = none)
|
|
||||||
selectedTrack int
|
|
||||||
|
|
||||||
// Navigation
|
|
||||||
// focusedIndex: 0 = AudioStatusToggle, 1..N = trackItems[focusedIndex-1]
|
|
||||||
// Volume is intentionally excluded from focus.
|
|
||||||
focusedIndex int
|
|
||||||
|
|
||||||
// Scrolling window
|
|
||||||
windowStart int
|
|
||||||
listTopY int32
|
|
||||||
listLeftX int32
|
|
||||||
itemHeight int32
|
|
||||||
trackSpacing int32
|
|
||||||
|
|
||||||
// Scrollbar geometry (computed once in layout, thumb Y updated on scroll)
|
|
||||||
scrollbarX int32
|
|
||||||
scrollbarTrackY int32 // bottom of the scrollbar track
|
|
||||||
scrollbarTrackH int32 // total height of the track
|
|
||||||
scrollbarThumbH int32 // height of the thumb
|
|
||||||
scrollbarThumbY int32 // current bottom Y of the thumb (updated on scroll)
|
|
||||||
|
|
||||||
// Triangle cursor
|
// Triangle cursor
|
||||||
triVAO uint32
|
triVAO uint32
|
||||||
triVBO uint32
|
triVBO uint32
|
||||||
@@ -106,10 +72,6 @@ type AudioOptions struct {
|
|||||||
func (p *AudioOptions) Init(ctx *PageCtx) error {
|
func (p *AudioOptions) Init(ctx *PageCtx) error {
|
||||||
var err error
|
var err error
|
||||||
p.verticalMargin = int32(20)
|
p.verticalMargin = int32(20)
|
||||||
p.focusedIndex = 0
|
|
||||||
p.selectedTrack = -1
|
|
||||||
p.windowStart = 0
|
|
||||||
p.trackSpacing = p.verticalMargin / 2
|
|
||||||
|
|
||||||
// Font
|
// Font
|
||||||
fm := fontManager.Get()
|
fm := fontManager.Get()
|
||||||
@@ -146,7 +108,7 @@ func (p *AudioOptions) Init(ctx *PageCtx) error {
|
|||||||
p.title.Scale(titleScale)
|
p.title.Scale(titleScale)
|
||||||
|
|
||||||
// Prompt
|
// Prompt
|
||||||
p.prompt, err = NewStringItem("Up/Down to navigate -- Enter to select -- Left/Right to change volume -- Backspace to go back", p.font, colors.White)
|
p.prompt, err = NewStringItem("Enter to toggle audio -- Left/Right to change volume -- Backspace to go back", p.font, colors.White)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating prompt: %w", err)
|
return fmt.Errorf("creating prompt: %w", err)
|
||||||
}
|
}
|
||||||
@@ -167,17 +129,11 @@ func (p *AudioOptions) Init(ctx *PageCtx) error {
|
|||||||
logger.Error("failed to create AudioStatusItem in AudioOptions.Init()")
|
logger.Error("failed to create AudioStatusItem in AudioOptions.Init()")
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Available Tracks" heading
|
// Make sure the audio_files table is populated. This page no longer
|
||||||
headingScale := float32(40) / float32(baseFontSize)
|
// presents any track selection UI, but other parts of the app still
|
||||||
p.tracksHeading, err = NewStringItem("Available Tracks", p.font, colors.White)
|
// read from this table.
|
||||||
if err != nil {
|
if err = p.seedAudioFiles(); err != nil {
|
||||||
return fmt.Errorf("creating tracks heading: %w", err)
|
logger.Error("failed to seed audio files: %v", err)
|
||||||
}
|
|
||||||
p.tracksHeading.Scale(headingScale)
|
|
||||||
|
|
||||||
// Track items
|
|
||||||
if err = p.buildTrackItems(); err != nil {
|
|
||||||
return fmt.Errorf("building track items: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triangle cursor VAO/VBO — stride is vec2 only, no tex coords
|
// Triangle cursor VAO/VBO — stride is vec2 only, no tex coords
|
||||||
@@ -200,84 +156,18 @@ func (p *AudioOptions) Init(ctx *PageCtx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Track items ───────────────────────────────────────────────────────────────
|
// seedAudioFiles ensures the audio_files table has its default rows.
|
||||||
|
func (p *AudioOptions) seedAudioFiles() error {
|
||||||
func (p *AudioOptions) buildTrackItems() error {
|
var existing []db.AudioFile
|
||||||
var audioFileList = []db.AudioFile{}
|
if err := p.Context.DbPtr.Find(&existing).Error; err != nil {
|
||||||
if err := p.Context.DbPtr.Find(&audioFileList).Error; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(existing) == 0 {
|
||||||
if len(audioFileList) == 0 {
|
return p.Context.DbPtr.Save(&AudioFileList).Error
|
||||||
audioFileList = AudioFileList
|
|
||||||
p.Context.DbPtr.Save(&audioFileList)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range p.trackItems {
|
|
||||||
if p.trackItems[i].ID != 0 {
|
|
||||||
gl.DeleteTextures(1, &p.trackItems[i].ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.trackItems = make([]StringItem, len(AudioFileList))
|
|
||||||
|
|
||||||
for i, entry := range AudioFileList {
|
|
||||||
color := colors.White
|
|
||||||
if i == p.selectedTrack {
|
|
||||||
color = colors.Green
|
|
||||||
}
|
|
||||||
item, err := NewStringItem(entry.Name, p.font, color)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating track item %d: %w", i, err)
|
|
||||||
}
|
|
||||||
item.Scale(p.ItemScale)
|
|
||||||
p.trackItems[i] = item
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// positionTrackItems places the visible window of items and parks the rest off-screen.
|
|
||||||
func (p *AudioOptions) positionTrackItems() {
|
|
||||||
peekCount := visibleCount + 1
|
|
||||||
|
|
||||||
for i := range p.trackItems {
|
|
||||||
slot := i - p.windowStart
|
|
||||||
|
|
||||||
if slot < 0 || slot >= peekCount {
|
|
||||||
p.trackItems[i].X = p.listLeftX
|
|
||||||
p.trackItems[i].Y = -9999
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
itemY := p.listTopY - int32(slot)*(p.itemHeight+p.trackSpacing)
|
|
||||||
p.trackItems[i].X = p.listLeftX
|
|
||||||
p.trackItems[i].Y = itemY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Scrollbar ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// updateScrollbarThumb recalculates the thumb Y position based on windowStart.
|
|
||||||
// Called whenever windowStart changes.
|
|
||||||
func (p *AudioOptions) updateScrollbarThumb() {
|
|
||||||
total := len(AudioFileList)
|
|
||||||
if total <= visibleCount {
|
|
||||||
// Entire list fits — thumb fills the track, nothing to scroll
|
|
||||||
p.scrollbarThumbY = p.scrollbarTrackY
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// How far through the scrollable range are we?
|
|
||||||
// maxWindowStart is the furthest windowStart can go
|
|
||||||
maxWindowStart := total - visibleCount
|
|
||||||
fraction := float32(p.windowStart) / float32(maxWindowStart)
|
|
||||||
|
|
||||||
// Thumb travels from the top of the track down to (trackTop - thumbH)
|
|
||||||
// "top" in our Y-up projection means the larger Y value
|
|
||||||
trackTop := p.scrollbarTrackY + p.scrollbarTrackH
|
|
||||||
travelRange := p.scrollbarTrackH - p.scrollbarThumbH
|
|
||||||
p.scrollbarThumbY = trackTop - p.scrollbarThumbH - int32(fraction*float32(travelRange))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Audio status toggle ───────────────────────────────────────────────────────
|
// ── Audio status toggle ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (p *AudioOptions) CreateAudioStatusItem() error {
|
func (p *AudioOptions) CreateAudioStatusItem() error {
|
||||||
@@ -345,7 +235,7 @@ func (p *AudioOptions) layout() {
|
|||||||
p.prompt.X = p.ScreenCenterX - p.prompt.W/2
|
p.prompt.X = p.ScreenCenterX - p.prompt.W/2
|
||||||
p.prompt.Y = p.title.Y - p.verticalMargin - p.prompt.H
|
p.prompt.Y = p.title.Y - p.verticalMargin - p.prompt.H
|
||||||
|
|
||||||
// Volume row (not focusable)
|
// Volume row
|
||||||
barH := int32(float32(p.Context.CfgPtr.StandardFontSize) * p.ItemScale)
|
barH := int32(float32(p.Context.CfgPtr.StandardFontSize) * p.ItemScale)
|
||||||
barW := int32(float32(p.ScreenWidth) * 0.30)
|
barW := int32(float32(p.ScreenWidth) * 0.30)
|
||||||
barX := p.ScreenCenterX - barW/2
|
barX := p.ScreenCenterX - barW/2
|
||||||
@@ -363,100 +253,14 @@ func (p *AudioOptions) layout() {
|
|||||||
p.volumePercent.X = barX + barW + gap
|
p.volumePercent.X = barX + barW + gap
|
||||||
p.volumePercent.Y = barY + (barH-p.volumePercent.H)/2
|
p.volumePercent.Y = barY + (barH-p.volumePercent.H)/2
|
||||||
|
|
||||||
// "Available Tracks" heading — centred, left edge becomes the list anchor
|
// Audio toggle — centred below the volume row
|
||||||
p.tracksHeading.X = p.ScreenCenterX - p.tracksHeading.W/2
|
p.AudioStatusToggle.X = p.ScreenCenterX - p.AudioStatusToggle.W/2
|
||||||
|
p.AudioStatusToggle.Y = barY - p.verticalMargin*3 - p.AudioStatusToggle.H
|
||||||
|
|
||||||
// Toggle — left-aligned to the heading
|
// Triangle cursor — just outside the left edge of the toggle, vertically centred on it
|
||||||
p.AudioStatusToggle.X = p.tracksHeading.X
|
|
||||||
p.AudioStatusToggle.Y = barY - p.verticalMargin*2 - p.AudioStatusToggle.H
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
p.separatorY = p.AudioStatusToggle.Y - p.verticalMargin*3
|
|
||||||
|
|
||||||
// Heading Y — below separator
|
|
||||||
p.tracksHeading.Y = p.separatorY - p.verticalMargin - p.tracksHeading.H
|
|
||||||
|
|
||||||
// Track list geometry — shifted down by listShiftDown
|
|
||||||
if len(p.trackItems) > 0 {
|
|
||||||
p.itemHeight = p.trackItems[0].H
|
|
||||||
}
|
|
||||||
|
|
||||||
p.listLeftX = p.tracksHeading.X
|
|
||||||
p.listTopY = p.tracksHeading.Y - p.verticalMargin - listShiftDown
|
|
||||||
|
|
||||||
// ── Scrollbar geometry ────────────────────────────────────────────────────
|
|
||||||
// The scrollbar track spans the full visible window height.
|
|
||||||
// It sits to the right of the list with a small gap.
|
|
||||||
windowH := int32(visibleCount)*(p.itemHeight+p.trackSpacing) - p.trackSpacing
|
|
||||||
scrollbarBottom := p.listTopY - windowH
|
|
||||||
p.scrollbarTrackH = windowH
|
|
||||||
p.scrollbarTrackY = scrollbarBottom // bottom Y of the track (Y-up coords)
|
|
||||||
|
|
||||||
// Right edge of the list: use the widest track name as an approximation.
|
|
||||||
// Since items are left-aligned and widths vary, we use a fixed width based
|
|
||||||
// on the heading width which is a reasonable proxy for the list column width.
|
|
||||||
listRightX := p.listLeftX + p.tracksHeading.W
|
|
||||||
p.scrollbarX = listRightX + scrollbarGap
|
|
||||||
|
|
||||||
// Thumb height is proportional: visibleCount / total items
|
|
||||||
total := len(AudioFileList)
|
|
||||||
if total > 0 {
|
|
||||||
thumbFraction := float32(visibleCount) / float32(total)
|
|
||||||
p.scrollbarThumbH = int32(thumbFraction * float32(p.scrollbarTrackH))
|
|
||||||
if p.scrollbarThumbH < 8 {
|
|
||||||
p.scrollbarThumbH = 8 // minimum thumb size so it's always clickable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Triangle cursor X — just outside the left edge of the list
|
|
||||||
cursorGap := int32(12)
|
cursorGap := int32(12)
|
||||||
p.triX = float32(p.listLeftX) - float32(triangleSize) - float32(cursorGap)
|
p.triX = float32(p.AudioStatusToggle.X) - float32(triangleSize) - float32(cursorGap)
|
||||||
|
p.triCenterY = float32(p.AudioStatusToggle.Y) + float32(p.AudioStatusToggle.H)/2
|
||||||
p.positionTrackItems()
|
|
||||||
p.updateScrollbarThumb()
|
|
||||||
p.repositionCursor()
|
|
||||||
}
|
|
||||||
|
|
||||||
// repositionCursor vertically centres the triangle on the focused item.
|
|
||||||
func (p *AudioOptions) repositionCursor() {
|
|
||||||
var targetY, targetH int32
|
|
||||||
|
|
||||||
if p.focusedIndex == 0 {
|
|
||||||
targetY = p.AudioStatusToggle.Y
|
|
||||||
targetH = p.AudioStatusToggle.H
|
|
||||||
} else {
|
|
||||||
trackIdx := p.focusedIndex - 1
|
|
||||||
slot := trackIdx - p.windowStart
|
|
||||||
targetY = p.listTopY - int32(slot)*(p.itemHeight+p.trackSpacing)
|
|
||||||
targetH = p.itemHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
p.triCenterY = float32(targetY) + float32(targetH)/2
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Scroll ────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
func (p *AudioOptions) scrollToFocus() {
|
|
||||||
if p.focusedIndex == 0 {
|
|
||||||
p.windowStart = 0
|
|
||||||
p.positionTrackItems()
|
|
||||||
p.updateScrollbarThumb()
|
|
||||||
p.repositionCursor()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
trackIdx := p.focusedIndex - 1
|
|
||||||
|
|
||||||
if trackIdx >= p.windowStart+visibleCount {
|
|
||||||
p.windowStart = trackIdx - visibleCount + 1
|
|
||||||
}
|
|
||||||
if trackIdx < p.windowStart {
|
|
||||||
p.windowStart = trackIdx
|
|
||||||
}
|
|
||||||
|
|
||||||
p.positionTrackItems()
|
|
||||||
p.updateScrollbarThumb()
|
|
||||||
p.repositionCursor()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Toggle ────────────────────────────────────────────────────────────────────
|
// ── Toggle ────────────────────────────────────────────────────────────────────
|
||||||
@@ -469,46 +273,6 @@ func (p *AudioOptions) ToggleAudio() {
|
|||||||
p.layout()
|
p.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Track selection ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
func (p *AudioOptions) onTrackSelected(index int, entry db.AudioFile) {
|
|
||||||
p.selectedTrack = index
|
|
||||||
if err := p.buildTrackItems(); err != nil {
|
|
||||||
logger.Error("onTrackSelected: failed to rebuild track items: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.layout()
|
|
||||||
|
|
||||||
// Create file names and paths
|
|
||||||
// Set newly selected entry as Selected
|
|
||||||
entry.Selected = true
|
|
||||||
// Set path
|
|
||||||
if !strings.Contains(entry.Path, ".local/") {
|
|
||||||
entry.Path = filepath.Join(p.Context.CfgPtr.AssetsDir, "audio/", entry.Path)
|
|
||||||
}
|
|
||||||
// Clear previously selected.
|
|
||||||
var previouslySelectedAudio db.AudioFile
|
|
||||||
if err := p.Context.DbPtr.Where("Selected = ?", true).First(&previouslySelectedAudio).Error; err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
logger.Info("No audio previously selected")
|
|
||||||
} else {
|
|
||||||
logger.Error("Unable to fetch previously selected audio from database %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
previouslySelectedAudio.Selected = false
|
|
||||||
if err = p.Context.DbPtr.Save(&previouslySelectedAudio).Error; err != nil {
|
|
||||||
logger.Error("Unable to update selected audio: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save it to the database
|
|
||||||
if err := p.Context.DbPtr.Save(&entry).Error; err != nil {
|
|
||||||
logger.Error("Error saving to database: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Info("Updated audio selection: %s", entry.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Events ────────────────────────────────────────────────────────────────────
|
// ── Events ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (p *AudioOptions) HandleEvent(event *sdl.Event) error {
|
func (p *AudioOptions) HandleEvent(event *sdl.Event) error {
|
||||||
@@ -518,25 +282,8 @@ func (p *AudioOptions) HandleEvent(event *sdl.Event) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch e.Keysym.Sym {
|
switch e.Keysym.Sym {
|
||||||
case sdl.K_UP:
|
|
||||||
if p.focusedIndex > 0 {
|
|
||||||
p.focusedIndex--
|
|
||||||
p.scrollToFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
case sdl.K_DOWN:
|
|
||||||
if p.focusedIndex < len(AudioFileList) {
|
|
||||||
p.focusedIndex++
|
|
||||||
p.scrollToFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
case sdl.K_RETURN:
|
case sdl.K_RETURN:
|
||||||
if p.focusedIndex == 0 {
|
p.ToggleAudio()
|
||||||
p.ToggleAudio()
|
|
||||||
} else {
|
|
||||||
trackIdx := p.focusedIndex - 1
|
|
||||||
p.onTrackSelected(trackIdx, AudioFileList[trackIdx])
|
|
||||||
}
|
|
||||||
|
|
||||||
case sdl.K_RIGHT:
|
case sdl.K_RIGHT:
|
||||||
p.UpdateVolume(10)
|
p.UpdateVolume(10)
|
||||||
@@ -563,17 +310,6 @@ func (p *AudioOptions) Update(dt float32) error {
|
|||||||
|
|
||||||
// ── Render helpers ────────────────────────────────────────────────────────────
|
// ── Render helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (p *AudioOptions) renderSeparator() error {
|
|
||||||
lineW := float32(p.ScreenWidth) * 0.80
|
|
||||||
lineX := float32(p.ScreenCenterX) - lineW/2
|
|
||||||
return p.RenderSolidColorQuad(
|
|
||||||
p.solidShader,
|
|
||||||
mgl32.Vec2{lineX, float32(p.separatorY)},
|
|
||||||
mgl32.Vec2{lineW, 2},
|
|
||||||
colors.White,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *AudioOptions) renderVolumeBar() error {
|
func (p *AudioOptions) renderVolumeBar() error {
|
||||||
vol := p.Context.CfgPtr.SavedVolume
|
vol := p.Context.CfgPtr.SavedVolume
|
||||||
|
|
||||||
@@ -609,37 +345,6 @@ func (p *AudioOptions) renderVolumeBar() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderScrollbar draws the scrollbar track (dim) and thumb (white).
|
|
||||||
// Only rendered when the list is longer than the visible window.
|
|
||||||
func (p *AudioOptions) renderScrollbar() error {
|
|
||||||
if len(AudioFileList) <= visibleCount {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track — a thin dim line the full height of the window
|
|
||||||
trackColor := sdl.Color{R: 80, G: 80, B: 80, A: 255}
|
|
||||||
if err := p.RenderSolidColorQuad(
|
|
||||||
p.solidShader,
|
|
||||||
mgl32.Vec2{float32(p.scrollbarX), float32(p.scrollbarTrackY)},
|
|
||||||
mgl32.Vec2{float32(scrollbarWidth), float32(p.scrollbarTrackH)},
|
|
||||||
trackColor,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("rendering scrollbar track: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thumb — white, positioned proportionally within the track
|
|
||||||
if err := p.RenderSolidColorQuad(
|
|
||||||
p.solidShader,
|
|
||||||
mgl32.Vec2{float32(p.scrollbarX), float32(p.scrollbarThumbY)},
|
|
||||||
mgl32.Vec2{float32(scrollbarWidth), float32(p.scrollbarThumbH)},
|
|
||||||
colors.White,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("rendering scrollbar thumb: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderCursor draws the spinning triangle cursor.
|
// renderCursor draws the spinning triangle cursor.
|
||||||
func (p *AudioOptions) renderCursor() error {
|
func (p *AudioOptions) renderCursor() error {
|
||||||
scaleY := float32(math.Cos(2 * math.Pi * float64(p.spinElapsed)))
|
scaleY := float32(math.Cos(2 * math.Pi * float64(p.spinElapsed)))
|
||||||
@@ -693,32 +398,6 @@ func (p *AudioOptions) Render() error {
|
|||||||
return fmt.Errorf("render audio toggle: %w", err)
|
return fmt.Errorf("render audio toggle: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separator
|
|
||||||
if err := p.renderSeparator(); err != nil {
|
|
||||||
return fmt.Errorf("render separator: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Available Tracks" heading — fixed, never moves
|
|
||||||
if err := p.RenderTexture(p.textShader, p.tracksHeading.ID, p.tracksHeading.Position(), p.tracksHeading.RawDimensions(), p.tracksHeading.Color); err != nil {
|
|
||||||
return fmt.Errorf("render tracks heading: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track list
|
|
||||||
for i := range p.trackItems {
|
|
||||||
item := &p.trackItems[i]
|
|
||||||
if item.Y < 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := p.RenderTexture(p.textShader, item.ID, item.Position(), item.RawDimensions(), item.Color); err != nil {
|
|
||||||
return fmt.Errorf("render track item %d: %w", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scrollbar
|
|
||||||
if err := p.renderScrollbar(); err != nil {
|
|
||||||
return fmt.Errorf("render scrollbar: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spinning triangle cursor — always on top
|
// Spinning triangle cursor — always on top
|
||||||
if err := p.renderCursor(); err != nil {
|
if err := p.renderCursor(); err != nil {
|
||||||
return fmt.Errorf("render cursor: %w", err)
|
return fmt.Errorf("render cursor: %w", err)
|
||||||
@@ -735,10 +414,6 @@ func (p *AudioOptions) Destroy() error {
|
|||||||
gl.DeleteTextures(1, &p.VolumeLabel.ID)
|
gl.DeleteTextures(1, &p.VolumeLabel.ID)
|
||||||
gl.DeleteTextures(1, &p.volumePercent.ID)
|
gl.DeleteTextures(1, &p.volumePercent.ID)
|
||||||
gl.DeleteTextures(1, &p.AudioStatusToggle.ID)
|
gl.DeleteTextures(1, &p.AudioStatusToggle.ID)
|
||||||
gl.DeleteTextures(1, &p.tracksHeading.ID)
|
|
||||||
for i := range p.trackItems {
|
|
||||||
gl.DeleteTextures(1, &p.trackItems[i].ID)
|
|
||||||
}
|
|
||||||
gl.DeleteBuffers(1, &p.triVBO)
|
gl.DeleteBuffers(1, &p.triVBO)
|
||||||
gl.DeleteVertexArrays(1, &p.triVAO)
|
gl.DeleteVertexArrays(1, &p.triVAO)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user