package page import ( "fmt" "strconv" "github.com/go-gl/gl/v3.3-core/gl" "github.com/go-gl/mathgl/mgl32" "github.com/veandco/go-sdl2/sdl" "gorm.io/gorm" // Ensure correct GORM import "rhemawavetech.com/rhemawave/internal/colors" "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/fontManager" "rhemawavetech.com/rhemawave/internal/shaderManager" ) // Scrollbar Visuals var ( colorScrollTrack = sdl.Color{R: 255, G: 255, B: 255, A: 30} colorScrollThumb = sdl.Color{R: 255, G: 255, B: 255, A: 100} ) type FlatItem struct { Label StringItem ID string IsCategory bool Depth int Expanded bool } type ProclamationOptions struct { Base font *fontManager.FontEntry textShader *shaderManager.Shader solidShader *shaderManager.Shader title StringItem prompt StringItem // Tree State allItems []*FlatItem activeMap map[string]bool highlightIndex int // Scrolling State scrollOffset int maxVisible int // Scrollbar Dimens scrollTrackWidth int32 scrollTrackX int32 scrollTrackHeight int32 scrollTrackY int32 indentSize float32 verticalMargin int32 } func (p *ProclamationOptions) Init(ctx *PageCtx) error { p.verticalMargin = 25 p.indentSize = 40.0 p.activeMap = make(map[string]bool) p.allItems = []*FlatItem{} p.scrollOffset = 0 p.scrollTrackWidth = 30 p.scrollTrackX = p.ScreenWidth - p.scrollTrackWidth fm := fontManager.Get() font, _ := fm.GetFont("Roboto-Regular") p.font = font sm := shaderManager.Get() p.textShader, _ = sm.Get("text") p.solidShader, _ = sm.Get("solid_color") p.Base.Init(ctx) itemScale := float32(32) / float32(ctx.CfgPtr.StandardFontSize) p.title, _ = NewStringItem("Proclamation Browser", p.font, colors.White) p.title.Scale(float32(64) / float32(ctx.CfgPtr.StandardFontSize)) p.prompt, _ = NewStringItem("Arrows: Nav | Enter: Exp/Col | Space: Toggle | A: Accept | Back: Cancel", p.font, colors.White) p.prompt.Scale(itemScale * 0.8) // Calculation for visible area areaHeight := float32(p.ScreenHeight) - 250 p.maxVisible = int(areaHeight / 47) // Load current selections var selected []db.Proclamation p.Context.DbPtr.Where("selected = ?", true).Find(&selected) for _, s := range selected { p.activeMap[s.ID] = true } // Load Root Categories var roots []db.ProclamationCategory p.Context.DbPtr.Where("parent_id IS NULL OR parent_id = 0").Find(&roots) for _, c := range roots { p.allItems = append(p.allItems, p.createFlatItem(c.Name, fmt.Sprint(c.ID), true, 0)) } p.layout() return nil } func (p *ProclamationOptions) createFlatItem(name, id string, isCat bool, depth int) *FlatItem { text := name if isCat { text = "[+] " + name } itemScale := float32(32) / float32(p.Context.CfgPtr.StandardFontSize) lbl, _ := NewStringItem(text, p.font, colors.White) lbl.Scale(itemScale) return &FlatItem{Label: lbl, ID: id, IsCategory: isCat, Depth: depth} } func (p *ProclamationOptions) layout() { p.title.X = p.ScreenCenterX - p.title.W/2 p.title.Y = 40 p.prompt.X = p.ScreenCenterX - p.prompt.W/2 p.prompt.Y = p.ScreenHeight - 60 startY := float32(p.ScreenHeight - 150) p.scrollTrackY = 130 p.scrollTrackHeight = int32(startY) - 130 + 50 for i, item := range p.allItems { item.Label.X = -5000 // Hidden if i >= p.scrollOffset && i < p.scrollOffset+p.maxVisible { visibleIdx := i - p.scrollOffset // Cast to int32 for StringItem coordinates item.Label.X = int32(60.0 + (float32(item.Depth) * p.indentSize)) item.Label.Y = int32(startY - (float32(visibleIdx) * 47.0)) } } } func (p *ProclamationOptions) toggleExpansion(index int) { item := p.allItems[index] if !item.IsCategory { return } pureName := item.Label.content[4:] gl.DeleteTextures(1, &item.Label.ID) itemScale := float32(32) / float32(p.Context.CfgPtr.StandardFontSize) if item.Expanded { p.collapse(index) item.Label, _ = NewStringItem("[+] "+pureName, p.font, colors.White) } else { p.expand(index) item.Label, _ = NewStringItem("[-] "+pureName, p.font, colors.White) } item.Label.Scale(itemScale) item.Expanded = !item.Expanded p.layout() } func (p *ProclamationOptions) expand(index int) { parent := p.allItems[index] parentID, _ := strconv.Atoi(parent.ID) var newItems []*FlatItem var cats []db.ProclamationCategory p.Context.DbPtr.Where("parent_id = ?", parentID).Find(&cats) for _, c := range cats { newItems = append(newItems, p.createFlatItem(c.Name, fmt.Sprint(c.ID), true, parent.Depth+1)) } var procs []db.Proclamation p.Context.DbPtr.Where("category_id = ?", parentID).Find(&procs) for _, pr := range procs { newItems = append(newItems, p.createFlatItem(pr.Name, pr.ID, false, parent.Depth+1)) } tail := append([]*FlatItem{}, p.allItems[index+1:]...) p.allItems = append(p.allItems[:index+1], append(newItems, tail...)...) } func (p *ProclamationOptions) collapse(index int) { depth := p.allItems[index].Depth i := index + 1 for i < len(p.allItems) && p.allItems[i].Depth > depth { gl.DeleteTextures(1, &p.allItems[i].Label.ID) i++ } p.allItems = append(p.allItems[:index+1], p.allItems[i:]...) } func (p *ProclamationOptions) HandleEvent(event *sdl.Event) error { switch e := (*event).(type) { case *sdl.KeyboardEvent: if e.Type != sdl.KEYDOWN { return nil } switch e.Keysym.Sym { case sdl.K_UP: if p.highlightIndex > 0 { p.highlightIndex-- if p.highlightIndex < p.scrollOffset { p.scrollOffset-- } } case sdl.K_DOWN: if p.highlightIndex < len(p.allItems)-1 { p.highlightIndex++ if p.highlightIndex >= p.scrollOffset+p.maxVisible { p.scrollOffset++ } } case sdl.K_PAGEUP: p.highlightIndex = p.scrollOffset p.scrollOffset -= (p.maxVisible - 1) if p.scrollOffset < 0 { p.scrollOffset = 0 p.highlightIndex = 0 } case sdl.K_PAGEDOWN: bottomIdx := p.scrollOffset + p.maxVisible - 1 if bottomIdx >= len(p.allItems) { bottomIdx = len(p.allItems) - 1 } p.scrollOffset = bottomIdx p.highlightIndex = bottomIdx case sdl.K_RETURN: p.toggleExpansion(p.highlightIndex) case sdl.K_SPACE: item := p.allItems[p.highlightIndex] if !item.IsCategory { p.activeMap[item.ID] = !p.activeMap[item.ID] } case sdl.K_a: p.acceptChanges() case sdl.K_BACKSPACE, sdl.K_ESCAPE: p.Context.NavigateTo(SETTINGS) } p.layout() } return nil } func (p *ProclamationOptions) acceptChanges() { p.Context.DbPtr.Transaction(func(tx *gorm.DB) error { tx.Model(&db.Proclamation{}).Where("1 = 1").Update("selected", false) var ids []string for id, active := range p.activeMap { if active { ids = append(ids, id) } } if len(ids) > 0 { tx.Model(&db.Proclamation{}).Where("id IN ?", ids).Update("selected", true) } return nil }) p.Context.NavigateTo(SETTINGS) } func (p *ProclamationOptions) Render() error { p.RenderTexture(p.textShader, p.title.ID, p.title.Position(), p.title.RawDimensions(), p.title.Color) p.RenderTexture(p.textShader, p.prompt.ID, p.prompt.Position(), p.prompt.RawDimensions(), p.prompt.Color) if len(p.allItems) == 0 { return nil } // Highlight Box if p.highlightIndex >= p.scrollOffset && p.highlightIndex < p.scrollOffset+p.maxVisible { target := p.allItems[p.highlightIndex].Label p.RenderSolidColorQuad(p.solidShader, target.Position(), target.RawDimensions(), colors.White) } for i := p.scrollOffset; i < p.scrollOffset+p.maxVisible && i < len(p.allItems); i++ { item := p.allItems[i] if i == p.highlightIndex { item.Label.Color = colors.Black } else if item.IsCategory { item.Label.Color = colors.Cyan } else if p.activeMap[item.ID] { item.Label.Color = colors.Green } else { item.Label.Color = colors.White } p.RenderTexture(p.textShader, item.Label.ID, item.Label.Position(), item.Label.RawDimensions(), item.Label.Color) } p.renderScrollbar() return nil } func (p *ProclamationOptions) renderScrollbar() { total := len(p.allItems) if total <= p.maxVisible { return } // 1. Render Track p.RenderSolidColorQuad(p.solidShader, mgl32.Vec2{float32(p.scrollTrackX), float32(p.scrollTrackY)}, mgl32.Vec2{float32(p.scrollTrackWidth), float32(p.scrollTrackHeight)}, colorScrollTrack) // Thumb Math visibleRatio := float32(p.maxVisible) / float32(total) scrollRatio := float32(p.scrollOffset) / float32(total) thumbHeight := float32(p.scrollTrackHeight) * visibleRatio if thumbHeight < 20 { thumbHeight = 20 } thumbY := float32(p.scrollTrackY) + (float32(p.scrollTrackHeight) * scrollRatio) // 2. Render Thumb p.RenderSolidColorQuad(p.solidShader, mgl32.Vec2{float32(p.scrollTrackX + 2), thumbY}, mgl32.Vec2{float32(p.scrollTrackWidth - 4), thumbHeight}, colorScrollThumb) } func (p *ProclamationOptions) Destroy() error { gl.DeleteTextures(1, &p.title.ID) gl.DeleteTextures(1, &p.prompt.ID) for _, item := range p.allItems { gl.DeleteTextures(1, &item.Label.ID) } return nil }