Initial release: RadiantWave v1.0
This commit is contained in:
@@ -0,0 +1,697 @@
|
||||
// ATTENTION: TED
|
||||
//
|
||||
// # Integrated Multi-Domain Rhema Wave Generation System
|
||||
//
|
||||
// This module implements the complete Rhema Wave generation system by synchronizing multiple
|
||||
// subsystems that operate simultaneously across spatial, temporal, and chromatic
|
||||
// domains to produce a unified frequency-structured visual output for subliminal proclamation display.
|
||||
//
|
||||
// The technology addresses the fundamental challenge of presenting textual proclamation content
|
||||
// in a format optimized for subconscious processing rather than conscious reading. Conventional
|
||||
// subliminal text systems use isolated techniques (simple masking, single-frequency flashing, or
|
||||
// uniform scrolling) that operate in only one domain. Our system integrates four mathematically-
|
||||
// synchronized subsystems, each generating Fibonacci-based patterns in different domains, creating
|
||||
// a compound frequency signature designed to enhance subconscious absorption while maintaining
|
||||
// conscious illegibility.
|
||||
//
|
||||
// The system architecture integrates:
|
||||
//
|
||||
// 1. Spatial Character Transformation - Each rendered character undergoes Fibonacci-based
|
||||
// block scrambling (described separately), creating local spatial frequency patterns at
|
||||
// the character level.
|
||||
//
|
||||
// 2. Spatial Line Distribution - Text lines contain varying word counts following an oscillating
|
||||
// Fibonacci sequence (1, 1, 2, 3, 5, 8, 13... then reversing), creating a macro-level
|
||||
// two-dimensional wave pattern in the display geometry and ensuring balanced temporal
|
||||
// exposure of proclamation content segments.
|
||||
//
|
||||
// 3. Temporal Color Modulation - Sequential words receive colors from an intent-based palette,
|
||||
// with color duration determined by an oscillating Fibonacci sequence, creating rhythmic
|
||||
// chromatic variation synchronized with natural mathematical progression.
|
||||
//
|
||||
// 4. Temporal Velocity Modulation - Display scroll velocity oscillates between negative and
|
||||
// positive extremes with acceleration/deceleration rates and hold periods, creating a
|
||||
// "breathing" temporal rhythm for the entire visual field.
|
||||
//
|
||||
// The key innovation lies in the temporal and mathematical synchronization of these subsystems.
|
||||
// All four operate on Fibonacci-derived parameters, creating harmonic relationships between the
|
||||
// spatial frequencies (character scrambling, line wave patterns), chromatic frequencies (color
|
||||
// duration oscillation), and temporal frequencies (scroll velocity modulation). This multi-domain
|
||||
// integration produces what we term the "Rhema Wave" - a compound frequency signature where
|
||||
// spatial, temporal, and chromatic patterns reinforce each other through mathematical harmony.
|
||||
//
|
||||
// Key differentiators from existing solutions:
|
||||
// - Integrates four distinct Fibonacci-based frequency generators in a single system
|
||||
// - Synchronizes pattern generation across spatial, temporal, and chromatic domains
|
||||
// - Creates compound frequency signatures rather than isolated single-domain effects
|
||||
// - Maintains character-specific and content-specific mathematical structure throughout
|
||||
// - Uses bidirectional oscillation across multiple subsystems for rhythmic coherence
|
||||
// - Combines macro-level (line patterns, velocity) and micro-level (character, color) modulation
|
||||
//
|
||||
// The system loads proclamation text content, processes it through all four subsystems simultaneously,
|
||||
// and generates a continuously scrolling display where every element - from individual character
|
||||
// pixels to overall scroll rhythm - contributes to the unified Rhema Wave output. The mathematical
|
||||
// relationships between subsystems create interference patterns and harmonic resonances designed
|
||||
// to enhance subconscious processing of the proclamation content while maintaining conscious
|
||||
// illegibility through the character scrambling subsystem.
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
"rhemawavetech.com/rhemawave/internal/colors"
|
||||
"rhemawavetech.com/rhemawave/internal/db"
|
||||
"rhemawavetech.com/rhemawave/internal/fontManager"
|
||||
"rhemawavetech.com/rhemawave/internal/mixer"
|
||||
colorPattern "rhemawavetech.com/rhemawave/internal/pattern/color"
|
||||
linePattern "rhemawavetech.com/rhemawave/internal/pattern/line"
|
||||
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
||||
)
|
||||
|
||||
type Line struct {
|
||||
words []StringItem
|
||||
width int32
|
||||
}
|
||||
|
||||
// LineDeque is a double-ended queue for Line values backed by a circular buffer.
|
||||
// It avoids O(n) prepend and prevents capacity leaks from head-of-slice removal.
|
||||
type LineDeque struct {
|
||||
buf []Line
|
||||
head, tail int
|
||||
count int
|
||||
}
|
||||
|
||||
func NewLineDeque(cap int) *LineDeque {
|
||||
if cap < 4 {
|
||||
cap = 4
|
||||
}
|
||||
return &LineDeque{buf: make([]Line, cap)}
|
||||
}
|
||||
|
||||
func (d *LineDeque) Len() int { return d.count }
|
||||
|
||||
func (d *LineDeque) grow() {
|
||||
newCap := len(d.buf) * 2
|
||||
newBuf := make([]Line, newCap)
|
||||
for i := 0; i < d.count; i++ {
|
||||
newBuf[i] = d.buf[(d.head+i)%len(d.buf)]
|
||||
}
|
||||
d.buf = newBuf
|
||||
d.head = 0
|
||||
d.tail = d.count
|
||||
}
|
||||
|
||||
func (d *LineDeque) PushBack(l Line) {
|
||||
if d.count == len(d.buf) {
|
||||
d.grow()
|
||||
}
|
||||
d.buf[d.tail] = l
|
||||
d.tail = (d.tail + 1) % len(d.buf)
|
||||
d.count++
|
||||
}
|
||||
|
||||
func (d *LineDeque) PushFront(l Line) {
|
||||
if d.count == len(d.buf) {
|
||||
d.grow()
|
||||
}
|
||||
d.head = (d.head - 1 + len(d.buf)) % len(d.buf)
|
||||
d.buf[d.head] = l
|
||||
d.count++
|
||||
}
|
||||
|
||||
func (d *LineDeque) PopFront() Line {
|
||||
l := d.buf[d.head]
|
||||
d.buf[d.head] = Line{} // clear for GC
|
||||
d.head = (d.head + 1) % len(d.buf)
|
||||
d.count--
|
||||
return l
|
||||
}
|
||||
|
||||
func (d *LineDeque) PopBack() Line {
|
||||
d.tail = (d.tail - 1 + len(d.buf)) % len(d.buf)
|
||||
l := d.buf[d.tail]
|
||||
d.buf[d.tail] = Line{} // clear for GC
|
||||
d.count--
|
||||
return l
|
||||
}
|
||||
|
||||
func (d *LineDeque) Front() Line { return d.buf[d.head] }
|
||||
|
||||
func (d *LineDeque) Back() Line {
|
||||
return d.buf[(d.tail-1+len(d.buf))%len(d.buf)]
|
||||
}
|
||||
|
||||
func (d *LineDeque) Get(i int) *Line {
|
||||
return &d.buf[(d.head+i)%len(d.buf)]
|
||||
}
|
||||
|
||||
func (d *LineDeque) Set(i int, l Line) {
|
||||
d.buf[(d.head+i)%len(d.buf)] = l
|
||||
}
|
||||
|
||||
func (d *LineDeque) Clear() {
|
||||
for i := 0; i < d.count; i++ {
|
||||
d.buf[(d.head+i)%len(d.buf)] = Line{}
|
||||
}
|
||||
d.head, d.tail, d.count = 0, 0, 0
|
||||
}
|
||||
|
||||
type ScrollerPage struct {
|
||||
Base
|
||||
|
||||
words []string
|
||||
wordIndex int
|
||||
lines *LineDeque
|
||||
lineHeight int32
|
||||
fontScale float32
|
||||
shader *shaderManager.Shader
|
||||
activePattern linePattern.LinePattern
|
||||
font *fontManager.FontEntry
|
||||
spaceWidth int32
|
||||
lineCtx *linePattern.LineRenderContext
|
||||
colorPattern *colorPattern.ColorPattern
|
||||
scrollOffset float32
|
||||
scrollY float32
|
||||
|
||||
increment float32
|
||||
velocity float32
|
||||
|
||||
// Time-based velocity control
|
||||
velocityFloor float32
|
||||
velocityCeiling float32
|
||||
velocityStep float32
|
||||
velocityDelay float32
|
||||
velocityDirection int
|
||||
velocityTimer float32
|
||||
velocityState int
|
||||
autoVelocity bool
|
||||
hold float32
|
||||
}
|
||||
|
||||
func (p *ScrollerPage) Init(app ApplicationInterface) error {
|
||||
p.increment = 100.0
|
||||
p.velocity = 800.0
|
||||
p.SetAutoVelocity(true, -4000.0, 4000.0, 100.0, 2.5, 6.0)
|
||||
|
||||
sm := shaderManager.Get()
|
||||
fm := fontManager.Get()
|
||||
|
||||
// Get font
|
||||
font, ok := fm.GetFont("Scrambled")
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to fetch font 'Scrambled'")
|
||||
}
|
||||
p.font = font
|
||||
|
||||
p.Base.Init(app)
|
||||
|
||||
// Get text shader
|
||||
textShader, ok := sm.Get("text")
|
||||
if !ok {
|
||||
return fmt.Errorf("text shader not found")
|
||||
}
|
||||
p.shader = textShader
|
||||
|
||||
// Load proclamation words from database
|
||||
if err := p.loadProclamations(); err != nil {
|
||||
// Don't stop execution, just show fallback words
|
||||
p.words = strings.Fields("No Proclamations Selected!")
|
||||
}
|
||||
p.wordIndex = 0
|
||||
|
||||
// Get line pattern from database
|
||||
linePatternName, err := db.GetConfigValue("line_pattern")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving line_pattern from db: %w", err)
|
||||
}
|
||||
|
||||
p.activePattern, err = linePattern.Get(linePatternName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load line pattern '%s': %w", linePatternName, err)
|
||||
}
|
||||
|
||||
// Initialize color pattern
|
||||
var baseColors = []sdl.Color{
|
||||
colors.Tan,
|
||||
colors.PeachPuff,
|
||||
colors.LightCyan,
|
||||
colors.LightSeaGreen,
|
||||
colors.MediumTurquoise,
|
||||
colors.DarkCyan,
|
||||
colors.MediumPurple,
|
||||
}
|
||||
p.colorPattern = colorPattern.NewColorPattern(baseColors)
|
||||
|
||||
// Get font sizes from database (optimize: load once, cache as int32)
|
||||
standardFontSizeStr, err := db.GetConfigValue("standard_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving standard_font_size from db: %w", err)
|
||||
}
|
||||
standardFontSize, err := strconv.Atoi(standardFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing standard_font_size: %w", err)
|
||||
}
|
||||
|
||||
baseFontSizeStr, err := db.GetConfigValue("init_font_size")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving init_font_size from db: %w", err)
|
||||
}
|
||||
baseFontSize, err := strconv.Atoi(baseFontSizeStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing init_font_size: %w", err)
|
||||
}
|
||||
|
||||
// Font setup (optimized: calculate once, reuse)
|
||||
p.fontScale = float32(standardFontSize) / float32(baseFontSize)
|
||||
nLines := int(p.ScreenHeight / int32(standardFontSize))
|
||||
p.lines = NewLineDeque(nLines * 2)
|
||||
|
||||
// Calculate space width once
|
||||
spaceGlyph, ok := fm.GetGlyph(p.font.Name, ' ')
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get space glyph")
|
||||
}
|
||||
p.spaceWidth = int32(float32(spaceGlyph.Advance) * p.fontScale)
|
||||
|
||||
// Get line height from a sample string
|
||||
tmpStrID, _, H, err := fm.NewCreateStringTexture(p.font.Name, "Mg", p.shader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sample texture: %w", err)
|
||||
}
|
||||
p.lineHeight = int32(float32(H) * p.fontScale)
|
||||
gl.DeleteTextures(1, &tmpStrID)
|
||||
|
||||
p.lineCtx = &linePattern.LineRenderContext{
|
||||
ScreenWidth: p.ScreenWidth,
|
||||
Font: p.font,
|
||||
FontScale: p.fontScale,
|
||||
SpaceWidth: p.spaceWidth,
|
||||
}
|
||||
|
||||
// Create initial lines
|
||||
for i := 0; i < nLines; i++ {
|
||||
if err := p.createLine(i, true); err != nil {
|
||||
return fmt.Errorf("failed to create initial line %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Audio setup
|
||||
audioDeviceName, err := db.GetConfigValue("audio_device_name")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving audio_device_name from db: %w", err)
|
||||
}
|
||||
|
||||
// Switch audio devices if needed
|
||||
if mixer.CurrentDevice() != audioDeviceName {
|
||||
if err := mixer.SwitchDevice(audioDeviceName); err != nil {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
// Start audio playback
|
||||
selectedAudio, err := db.GetConfigValue("selected_audio")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving selected_audio from db: %w", err)
|
||||
}
|
||||
|
||||
assetsDir, err := db.GetConfigValue("assets_dir")
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving assets_dir from db: %w", err)
|
||||
}
|
||||
|
||||
if selectedAudio == "" {
|
||||
} else {
|
||||
if err := mixer.Play(filepath.Join(assetsDir, selectedAudio), true); err != nil {
|
||||
}
|
||||
}
|
||||
|
||||
currentDevice := mixer.CurrentDevice()
|
||||
if currentDevice == "" {
|
||||
currentDevice = "default"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadProclamations loads selected proclamations from the database
|
||||
func (p *ScrollerPage) loadProclamations() error {
|
||||
p.words = []string{}
|
||||
|
||||
// Get all selected proclamations from database
|
||||
proclamations, err := db.GetProclamations()
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving proclamations from db: %w", err)
|
||||
}
|
||||
|
||||
// Build word list from selected, available proclamations
|
||||
for _, proclamation := range proclamations {
|
||||
if proclamation.Selected && proclamation.Available {
|
||||
// Split content into words and add to list
|
||||
words := strings.Fields(proclamation.Content)
|
||||
p.words = append(p.words, words...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.words) == 0 {
|
||||
p.words = strings.Fields("No Proclamations Selected!")
|
||||
return fmt.Errorf("no proclamations selected or available")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createLine is a helper to generate a new line at a specific index
|
||||
// isInitial determines if this is during initialization (positions from top)
|
||||
// or dynamic creation (positions relative to existing lines)
|
||||
func (p *ScrollerPage) createLine(index int, isInitial bool) error {
|
||||
words := p.activePattern.GetNextLine(p.words, &p.wordIndex, p.lineCtx)
|
||||
line := Line{words: []StringItem{}}
|
||||
|
||||
for _, word := range words {
|
||||
if word == "" {
|
||||
continue
|
||||
}
|
||||
item, err := NewStringItem(word, p.font, p.colorPattern.Color())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create string item for word '%s': %w", word, err)
|
||||
}
|
||||
item.W = int32(float32(item.W) * p.fontScale)
|
||||
item.H = int32(float32(item.H) * p.fontScale)
|
||||
|
||||
if len(line.words) > 0 {
|
||||
line.width += p.spaceWidth
|
||||
}
|
||||
line.words = append(line.words, item)
|
||||
line.width += item.W
|
||||
}
|
||||
|
||||
// Calculate horizontal position (centered)
|
||||
startX := p.ScreenCenterX - (line.width / 2)
|
||||
|
||||
// Calculate vertical position
|
||||
var baseY int32
|
||||
if isInitial {
|
||||
baseY = int32(p.scrollY) - int32(p.lineHeight)*int32(index)
|
||||
} else {
|
||||
baseY = 0 // Will be set by caller based on context
|
||||
}
|
||||
|
||||
// Position all words in the line
|
||||
for j := range line.words {
|
||||
if j == 0 {
|
||||
line.words[j].X = startX
|
||||
} else {
|
||||
line.words[j].X = line.words[j-1].X + line.words[j-1].W + p.spaceWidth
|
||||
}
|
||||
line.words[j].Y = baseY
|
||||
}
|
||||
|
||||
if isInitial {
|
||||
p.lines.PushBack(line)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ScrollerPage) 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.autoVelocity {
|
||||
p.velocity += p.increment
|
||||
}
|
||||
case sdl.K_DOWN:
|
||||
if !p.autoVelocity {
|
||||
p.velocity -= p.increment
|
||||
}
|
||||
case sdl.K_a:
|
||||
p.autoVelocity = !p.autoVelocity
|
||||
if p.autoVelocity {
|
||||
// Reset to initial state when enabling
|
||||
p.velocity = p.velocityFloor
|
||||
p.velocityDirection = 1
|
||||
p.velocityState = 0
|
||||
p.velocityTimer = 0.0
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ScrollerPage) Update(dt float32) error {
|
||||
p.updateAutoVelocity(dt)
|
||||
|
||||
p.scrollOffset += dt * p.velocity
|
||||
move := int32(p.scrollOffset)
|
||||
if move == 0 {
|
||||
return nil
|
||||
}
|
||||
p.scrollOffset -= float32(move)
|
||||
|
||||
// Move all lines vertically
|
||||
for i := 0; i < p.lines.Len(); i++ {
|
||||
line := p.lines.Get(i)
|
||||
for j := range line.words {
|
||||
line.words[j].Y += move
|
||||
}
|
||||
}
|
||||
|
||||
// Handle line replacement based on scroll direction
|
||||
if p.velocity > 0 {
|
||||
// Moving up: replace lines that go off top
|
||||
for p.lines.Len() > 0 && len(p.lines.Front().words) > 0 && p.lines.Front().words[0].Y > p.ScreenHeight {
|
||||
old := p.lines.PopFront()
|
||||
for _, word := range old.words {
|
||||
gl.DeleteTextures(1, &word.ID)
|
||||
}
|
||||
|
||||
if err := p.addLineAtBottom(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if p.velocity < 0 {
|
||||
// Moving down: replace lines that go off bottom
|
||||
for p.lines.Len() > 0 {
|
||||
back := p.lines.Back()
|
||||
if len(back.words) == 0 || back.words[0].Y+back.words[0].H >= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
old := p.lines.PopBack()
|
||||
for _, word := range old.words {
|
||||
gl.DeleteTextures(1, &word.ID)
|
||||
}
|
||||
|
||||
if err := p.addLineAtTop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addLineAtBottom creates and adds a new line at the bottom of the display
|
||||
func (p *ScrollerPage) addLineAtBottom() error {
|
||||
newWords := p.activePattern.GetNextLine(p.words, &p.wordIndex, p.lineCtx)
|
||||
line := Line{words: []StringItem{}}
|
||||
|
||||
for _, word := range newWords {
|
||||
if word == "" {
|
||||
continue
|
||||
}
|
||||
item, err := NewStringItem(word, p.font, p.colorPattern.Color())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create string item for word '%s': %w", word, err)
|
||||
}
|
||||
item.W = int32(float32(item.W) * p.fontScale)
|
||||
item.H = int32(float32(item.H) * p.fontScale)
|
||||
|
||||
if len(line.words) > 0 {
|
||||
line.width += p.spaceWidth
|
||||
}
|
||||
line.words = append(line.words, item)
|
||||
line.width += item.W
|
||||
}
|
||||
|
||||
startX := p.ScreenCenterX - (line.width / 2)
|
||||
|
||||
// Position relative to last existing line
|
||||
var baseY int32
|
||||
if p.lines.Len() > 0 && len(p.lines.Back().words) > 0 {
|
||||
baseY = p.lines.Back().words[0].Y - int32(p.lineHeight)
|
||||
} else {
|
||||
baseY = -int32(p.lineHeight)
|
||||
}
|
||||
|
||||
for j := range line.words {
|
||||
if j == 0 {
|
||||
line.words[j].X = startX
|
||||
} else {
|
||||
line.words[j].X = line.words[j-1].X + line.words[j-1].W + p.spaceWidth
|
||||
}
|
||||
line.words[j].Y = baseY
|
||||
}
|
||||
|
||||
p.lines.PushBack(line)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addLineAtTop creates and adds a new line at the top of the display
|
||||
func (p *ScrollerPage) addLineAtTop() error {
|
||||
newWords := p.activePattern.GetNextLine(p.words, &p.wordIndex, p.lineCtx)
|
||||
line := Line{words: []StringItem{}}
|
||||
|
||||
for _, word := range newWords {
|
||||
if word == "" {
|
||||
continue
|
||||
}
|
||||
item, err := NewStringItem(word, p.font, p.colorPattern.Color())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create string item for word '%s': %w", word, err)
|
||||
}
|
||||
item.W = int32(float32(item.W) * p.fontScale)
|
||||
item.H = int32(float32(item.H) * p.fontScale)
|
||||
|
||||
if len(line.words) > 0 {
|
||||
line.width += p.spaceWidth
|
||||
}
|
||||
line.words = append(line.words, item)
|
||||
line.width += item.W
|
||||
}
|
||||
|
||||
startX := p.ScreenCenterX - (line.width / 2)
|
||||
|
||||
// Position relative to first existing line
|
||||
var baseY int32
|
||||
if p.lines.Len() > 0 && len(p.lines.Front().words) > 0 {
|
||||
baseY = p.lines.Front().words[0].Y + int32(p.lineHeight)
|
||||
} else {
|
||||
baseY = p.ScreenHeight
|
||||
}
|
||||
|
||||
for j := range line.words {
|
||||
if j == 0 {
|
||||
line.words[j].X = startX
|
||||
} else {
|
||||
line.words[j].X = line.words[j-1].X + line.words[j-1].W + p.spaceWidth
|
||||
}
|
||||
line.words[j].Y = baseY
|
||||
}
|
||||
|
||||
p.lines.PushFront(line)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ScrollerPage) Render() error {
|
||||
p.BeginTextureRendering(p.shader)
|
||||
for i := 0; i < p.lines.Len(); i++ {
|
||||
line := p.lines.Get(i)
|
||||
for _, word := range line.words {
|
||||
if word.ID == 0 {
|
||||
continue
|
||||
}
|
||||
p.RenderTextureBatched(p.shader, word.ID, word.Position(), word.RawDimensions(), word.Color)
|
||||
}
|
||||
}
|
||||
p.EndTextureRendering()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ScrollerPage) Destroy() error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mixer.Stop(1000)
|
||||
|
||||
// GL cleanup — delete all text textures
|
||||
for i := 0; i < p.lines.Len(); i++ {
|
||||
line := p.lines.Get(i)
|
||||
for j := range line.words {
|
||||
if id := line.words[j].ID; id != 0 {
|
||||
gl.DeleteTextures(1, &line.words[j].ID)
|
||||
line.words[j].ID = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gl.Flush()
|
||||
|
||||
// Drop references for GC
|
||||
p.lines = nil
|
||||
p.words = nil
|
||||
p.lineCtx = nil
|
||||
p.font = nil
|
||||
p.shader = nil
|
||||
p.colorPattern = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAutoVelocity configures the automatic velocity system for the ScrollerPage.
|
||||
func (p *ScrollerPage) SetAutoVelocity(enabled bool, floor, ceiling, step, delay, hold float32) {
|
||||
p.autoVelocity = enabled
|
||||
p.velocityFloor = -float32(math.Abs(float64(floor)))
|
||||
p.velocityCeiling = float32(math.Abs(float64(ceiling)))
|
||||
p.velocityStep = float32(math.Abs(float64(step)))
|
||||
p.velocityDelay = delay
|
||||
p.hold = hold
|
||||
|
||||
// Initialize state
|
||||
p.velocityDirection = 1
|
||||
p.velocityState = 0
|
||||
p.velocityTimer = 0.0
|
||||
p.velocity = p.velocityFloor
|
||||
}
|
||||
|
||||
// updateAutoVelocity handles automatic velocity changes
|
||||
func (p *ScrollerPage) updateAutoVelocity(dt float32) {
|
||||
if !p.autoVelocity {
|
||||
return
|
||||
}
|
||||
|
||||
switch p.velocityState {
|
||||
case 0: // Changing velocity
|
||||
if p.velocityDirection == 1 {
|
||||
p.velocity += p.velocityStep * dt
|
||||
if p.velocity >= p.velocityCeiling {
|
||||
p.velocity = p.velocityCeiling
|
||||
p.velocityState = 1
|
||||
p.velocityTimer = 0.0
|
||||
}
|
||||
} else {
|
||||
p.velocity -= p.velocityStep * dt
|
||||
if p.velocity <= p.velocityFloor {
|
||||
p.velocity = p.velocityFloor
|
||||
p.velocityState = 2
|
||||
p.velocityTimer = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
case 1: // Waiting at ceiling
|
||||
p.velocityTimer += dt
|
||||
if p.velocityTimer >= p.velocityDelay {
|
||||
p.velocityDirection = -1
|
||||
p.velocityState = 0
|
||||
}
|
||||
|
||||
case 2: // Waiting at floor
|
||||
p.velocityTimer += dt
|
||||
if p.velocityTimer >= p.velocityDelay {
|
||||
p.velocityDirection = 1
|
||||
p.velocityState = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user