diff --git a/background.go b/background.go index 9ae17e2..0754395 100644 --- a/background.go +++ b/background.go @@ -18,20 +18,23 @@ var topColor = rl.Color{R: 0x20, G: 0x0F, B: 0x3C, A: 0xFF} // #200F3C top var bottomColor = rl.Color{R: 0x3B, G: 0x0B, B: 0x42, A: 0xFF} // #3B0B42 bottom var particleColor = rl.Color{R: 0xD4, G: 0xB0, B: 0xB5, A: 0x80} // D4B0B5 with some transparency +var rng = rand.New(rand.NewSource(time.Now().UnixNano())) // Local RNG + func InitBackground(width, height int) { - rand.Seed(time.Now().UnixNano()) particles = make([]Particle, 100) for i := range particles { - particles[i].Pos = rl.Vector2{X: float32(rand.Intn(width)), Y: float32(rand.Intn(height))} - particles[i].Vel = rl.Vector2{X: (rand.Float32() - 0.5) * 0.2, Y: (rand.Float32() - 0.5) * 0.2} - particles[i].Size = rand.Float32()*1.5 + 0.5 // Particles size ~0.5-2.0 + particles[i].Pos = rl.Vector2{X: float32(rng.Intn(width)), Y: float32(rng.Intn(height))} + particles[i].Vel = rl.Vector2{X: (rng.Float32() - 0.5) * 0.2, Y: (rng.Float32() - 0.5) * 0.2} + particles[i].Size = rng.Float32()*1.5 + 0.5 // Particles size ~0.5-2.0 } } func UpdateBackground(screenWidth, screenHeight int) { + deltaTime := rl.GetFrameTime() // Time in seconds since the last frame for i := range particles { - particles[i].Pos.X += particles[i].Vel.X - particles[i].Pos.Y += particles[i].Vel.Y + particles[i].Pos.X += particles[i].Vel.X * deltaTime * 60 // Adjust for frame rate + particles[i].Pos.Y += particles[i].Vel.Y * deltaTime * 60 + // Wrap around screen if particles[i].Pos.X < 0 { particles[i].Pos.X += float32(screenWidth) diff --git a/main.go b/main.go index c9a25a2..ba43caa 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,11 @@ func main() { rl.SetWindowState(rl.FlagFullscreenMode) defer rl.CloseWindow() - rl.SetTargetFPS(60) + refreshRate := rl.GetMonitorRefreshRate(monitor) + if refreshRate <= 0 { + refreshRate = 60 // Fallback to 60 if detection fails + } + rl.SetTargetFPS(int32(refreshRate)) InitBackground(rl.GetScreenWidth(), rl.GetScreenHeight()) diff --git a/transition.go b/transition.go index 805283b..954688e 100644 --- a/transition.go +++ b/transition.go @@ -1,8 +1,6 @@ package main import ( - "time" - rl "github.com/gen2brain/raylib-go/raylib" ) @@ -24,36 +22,54 @@ const ( type TransitionManager struct { active bool - startTime time.Time - phase TransitionPhase direction TransitionDirection - oldStep int - newStep int - outFadeScaleDuration time.Duration - outSlideDuration time.Duration - inFadeScaleDuration time.Duration + oldStep int + newStep int + + // Single total transition duration (seconds) + totalSec float32 + + // Sub-phase fractions (must sum to 1.0) + fadeFrac float32 + slideFrac float32 + inFrac float32 + + // Where we store the linear boundary of the sub-phase in [0..1] + fadeEnd float32 + slideEnd float32 + + // Accumulated time in seconds + accumSec float32 } +// NewTransitionManager with one total duration, e.g. 0.5 seconds total +// Sub-phase fractions: fade=0.3, slide=0.4, fadeIn=0.3 (they sum to 1.0) func NewTransitionManager() *TransitionManager { return &TransitionManager{ - outFadeScaleDuration: 150 * time.Millisecond, - outSlideDuration: 200 * time.Millisecond, - inFadeScaleDuration: 150 * time.Millisecond, + totalSec: 0.6, // e.g., entire transition 600ms + fadeFrac: 0.3, // 30% of total time for fade out scale + slideFrac: 0.4, // 40% of total time for slide + inFrac: 0.3, // 30% of total time for fade in scale } } func (t *TransitionManager) Start(oldStep, newStep int) { t.active = true - t.startTime = time.Now() - t.phase = TransitionOutFadeScale + t.accumSec = 0 t.oldStep = oldStep t.newStep = newStep + if newStep > oldStep { t.direction = DirectionForward } else { t.direction = DirectionBackward } + + // Precompute sub-phase boundaries in [0..1] + t.fadeEnd = t.fadeFrac + t.slideEnd = t.fadeFrac + t.slideFrac + // Final inFrac ends at 1.0 } func (t *TransitionManager) IsActive() bool { @@ -65,80 +81,103 @@ func (t *TransitionManager) GetPhase() TransitionPhase { if !t.active { return TransitionNone } - elapsed := time.Since(t.startTime) - total := t.outFadeScaleDuration + t.outSlideDuration + t.inFadeScaleDuration - if elapsed >= total { - return TransitionNone - } else if elapsed < t.outFadeScaleDuration { + // Determine the linear progress + p := t.accumSec / t.totalSec + if p < t.fadeEnd { return TransitionOutFadeScale - } else if elapsed < t.outFadeScaleDuration+t.outSlideDuration { + } else if p < t.slideEnd { return TransitionOutSlide } else { return TransitionInFadeScale } } -func (t *TransitionManager) Update() (oldAlpha float32, oldScale float32, oldOffsetX float32, - newAlpha float32, newScale float32, newOffsetX float32) { +// Easing function: "easeInOutCubic" => pronounced slow start, fast middle, slow end +func easeInOutCubic(p float32) float32 { + if p < 0.5 { + return 4 * p * p * p + } + f := (2 * p) - 2 + return 0.5*f*f*f + 1 +} + +// Update returns alpha/scale/offset for old/new steps +func (t *TransitionManager) Update() ( + oldAlpha, oldScale, oldOffsetX float32, + newAlpha, newScale, newOffsetX float32, +) { if !t.active { - return 1.0, 1.0, 0.0, 1.0, 1.0, 0.0 + return 1, 1, 0, 1, 1, 0 } - elapsed := time.Since(t.startTime) - totalDuration := t.outFadeScaleDuration + t.outSlideDuration + t.inFadeScaleDuration - if elapsed >= totalDuration { - // Transition done + // Accumulate variable time + dt := rl.GetFrameTime() // in seconds + t.accumSec += dt + if t.accumSec >= t.totalSec { t.active = false - return 1.0, 1.0, 0.0, 1.0, 1.0, 0.0 + return 1, 1, 0, 1, 1, 0 } - oldAlpha = 1.0 - oldScale = 1.0 - oldOffsetX = 0.0 - newAlpha = 1.0 - newScale = 1.0 - newOffsetX = 0.0 + oldAlpha, oldScale, oldOffsetX = 1, 1, 0 + newAlpha, newScale, newOffsetX = 1, 1, 0 - slideDirection := float32(-1) + slideDir := float32(-1) if t.direction == DirectionBackward { - slideDirection = 1 + slideDir = 1 } - if elapsed < t.outFadeScaleDuration { - // Phase 1: Out Fade/Scale - progress := float32(elapsed.Milliseconds()) / float32(t.outFadeScaleDuration.Milliseconds()) - oldAlpha = 1.0 - 0.2*progress - oldScale = 1.0 - 0.1*progress + p := t.accumSec / t.totalSec + if p > 1 { + p = 1 + } - // New tab not visible at all yet - newAlpha = 0.0 + globalP := easeInOutCubic(p) + + if p < t.fadeEnd { + gEnd := easeInOutCubic(t.fadeEnd) + subE := float32(0) + if gEnd > 0 { + subE = globalP / gEnd + } + + oldAlpha = 1 - 0.2*subE + oldScale = 1 - 0.1*subE + newAlpha = 0 newScale = 0.9 - newOffsetX = float32(rl.GetScreenWidth()) * slideDirection - } else if elapsed < t.outFadeScaleDuration+t.outSlideDuration { - // Phase 2: Slide out old, slide in new - phaseElapsed := elapsed - t.outFadeScaleDuration - progress := float32(phaseElapsed.Milliseconds()) / float32(t.outSlideDuration.Milliseconds()) + newOffsetX = float32(rl.GetScreenWidth()) * slideDir + + } else if p < t.slideEnd { + gStart := easeInOutCubic(t.fadeEnd) + gEnd := easeInOutCubic(t.slideEnd) + gRange := gEnd - gStart + subE := float32(0) + if gRange > 0 { + subE = (globalP - gStart) / gRange + } oldAlpha = 0.8 oldScale = 0.9 - oldOffsetX = -float32(rl.GetScreenWidth()) * progress * slideDirection - + oldOffsetX = -float32(rl.GetScreenWidth()) * subE * slideDir newAlpha = 0.8 newScale = 0.9 - newOffsetX = float32(rl.GetScreenWidth()) * (1.0 - progress) * slideDirection + newOffsetX = float32(rl.GetScreenWidth()) * (1 - subE) * slideDir + } else { - // Phase 3: In Fade/Scale - phaseElapsed := elapsed - t.outFadeScaleDuration - t.outSlideDuration - progress := float32(phaseElapsed.Milliseconds()) / float32(t.inFadeScaleDuration.Milliseconds()) + gStart := easeInOutCubic(t.slideEnd) + gEnd := easeInOutCubic(1) + gRange := gEnd - gStart + subE := float32(0) + if gRange > 0 { + subE = (globalP - gStart) / gRange + } - oldAlpha = 0.0 + oldAlpha = 0 oldScale = 0.9 - oldOffsetX = -float32(rl.GetScreenWidth()) * slideDirection - - newAlpha = 0.8 + 0.2*progress - newScale = 0.9 + 0.1*progress - newOffsetX = 0.0 + oldOffsetX = -float32(rl.GetScreenWidth()) * slideDir + newAlpha = 0.8 + 0.2*subE + newScale = 0.9 + 0.1*subE + newOffsetX = 0 } return oldAlpha, oldScale, oldOffsetX, newAlpha, newScale, newOffsetX