427 lines
12 KiB
Go
427 lines
12 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"os"
|
||
|
||
rl "github.com/gen2brain/raylib-go/raylib"
|
||
)
|
||
|
||
var (
|
||
transition = NewTransitionManager()
|
||
currentStep = 0
|
||
targetStep = 0
|
||
useDefault = false
|
||
step1DefaultRect rl.Rectangle
|
||
step1CustomRect rl.Rectangle
|
||
|
||
installer = NewInstaller()
|
||
|
||
// We no longer store circleToCenter or "display progress" here.
|
||
// Instead, we use the new CircleAnimator:
|
||
circleAnimator = NewCircleAnimator()
|
||
|
||
// textHoverFade => for the small circle’s info text in steps 0..2
|
||
textHoverFade float32
|
||
)
|
||
|
||
const finalStep = 3
|
||
|
||
func main() {
|
||
monitor := rl.GetCurrentMonitor()
|
||
if monitor < 0 {
|
||
monitor = 0 // Fallback to the primary monitor
|
||
}
|
||
screenW := rl.GetMonitorWidth(monitor)
|
||
screenH := rl.GetMonitorHeight(monitor)
|
||
|
||
rl.InitWindow(int32(screenW), int32(screenH), "Spitfire Browser Installer")
|
||
rl.SetWindowState(rl.FlagFullscreenMode)
|
||
defer rl.CloseWindow()
|
||
|
||
refreshRate := rl.GetMonitorRefreshRate(monitor)
|
||
if refreshRate <= 0 {
|
||
refreshRate = 60
|
||
}
|
||
rl.SetTargetFPS(int32(refreshRate))
|
||
|
||
InitBackground(rl.GetScreenWidth(), rl.GetScreenHeight())
|
||
|
||
// Start the download+decompress in background immediately
|
||
installer.StartDownloadDecompress()
|
||
|
||
// Initially step 0 => circle is top-right => circleAnimator.PosAlpha=0
|
||
circleAnimator.SetStep(0)
|
||
|
||
for !rl.WindowShouldClose() {
|
||
screenW = rl.GetScreenWidth()
|
||
screenH = rl.GetScreenHeight()
|
||
mousePos := rl.GetMousePosition()
|
||
|
||
buttonW := 100
|
||
buttonH := 30
|
||
prevX := int32(50)
|
||
prevY := int32(screenH - 50)
|
||
nextX := int32(screenW - 150)
|
||
nextY := prevY
|
||
|
||
// Transition update
|
||
oldAlpha, oldScale, oldOffsetX, newAlpha, newScale, newOffsetX := transition.Update()
|
||
if !transition.IsActive() && currentStep != targetStep {
|
||
currentStep = targetStep
|
||
// Update circleAnimator step so it moves (0 => top-right, 3 => center)
|
||
circleAnimator.SetStep(currentStep)
|
||
}
|
||
|
||
// Poll SPM progress
|
||
installer.PollProgress()
|
||
|
||
// If error
|
||
if installer.LastError != nil {
|
||
fmt.Println("SPM Error:", installer.LastError)
|
||
}
|
||
|
||
// Update circle animator every frame
|
||
// This will ease the actual SPM progress => "DisplayProgress"
|
||
circleAnimator.Update(installer.Progress)
|
||
|
||
// textHoverFade for step<3
|
||
if currentStep < finalStep {
|
||
topRightX := float32(screenW - 80)
|
||
topRightY := float32(100)
|
||
radius := float32(30)
|
||
dx := mousePos.X - topRightX
|
||
dy := mousePos.Y - topRightY
|
||
dist := float32(math.Sqrt(float64(dx*dx + dy*dy)))
|
||
if dist < radius+30 {
|
||
textHoverFade += 0.1
|
||
if textHoverFade > 1 {
|
||
textHoverFade = 1
|
||
}
|
||
} else {
|
||
textHoverFade -= 0.1
|
||
if textHoverFade < 0 {
|
||
textHoverFade = 0
|
||
}
|
||
}
|
||
} else {
|
||
textHoverFade = 0
|
||
}
|
||
|
||
// Mouse input
|
||
if !transition.IsActive() && rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||
handleInput(mousePos, screenW, screenH, buttonW, buttonH, prevX, prevY, nextX, nextY)
|
||
}
|
||
|
||
UpdateBackground(screenW, screenH)
|
||
|
||
rl.BeginDrawing()
|
||
DrawBackground(screenW, screenH)
|
||
|
||
drawHeader(screenW)
|
||
|
||
oldStep := currentStep
|
||
newStep := currentStep
|
||
if transition.IsActive() {
|
||
oldStep = transition.oldStep
|
||
newStep = transition.newStep
|
||
}
|
||
phase := transition.GetPhase()
|
||
if transition.IsActive() {
|
||
if phase == TransitionOutFadeScale {
|
||
drawStep(oldStep, screenW, screenH, mousePos, oldAlpha, oldScale, oldOffsetX)
|
||
} else {
|
||
drawStep(oldStep, screenW, screenH, mousePos, oldAlpha, oldScale, oldOffsetX)
|
||
drawStep(newStep, screenW, screenH, mousePos, newAlpha, newScale, newOffsetX)
|
||
}
|
||
} else {
|
||
drawStep(currentStep, screenW, screenH, mousePos, 1, 1, 0)
|
||
}
|
||
|
||
// Nav buttons if not final step
|
||
if !transition.IsActive() && currentStep < finalStep {
|
||
if currentStep > 0 && currentStep < 3 {
|
||
drawButton("Previous", prevX, prevY, int32(buttonW), int32(buttonH), mousePos)
|
||
}
|
||
if currentStep == 1 {
|
||
drawButton("Next", nextX, nextY, int32(buttonW), int32(buttonH), mousePos)
|
||
} else if currentStep == 2 {
|
||
drawButton("Finish", nextX, nextY, int32(buttonW), int32(buttonH), mousePos)
|
||
}
|
||
}
|
||
|
||
// Draw the circle
|
||
drawInstallCircle(screenW, screenH, circleAnimator.PosAlpha, circleAnimator.DisplayProgress, installer.DoneInstall, textHoverFade)
|
||
|
||
rl.EndDrawing()
|
||
}
|
||
}
|
||
|
||
func handleInput(mousePos rl.Vector2, screenW, screenH, buttonW, buttonH int, prevX, prevY, nextX, nextY int32) {
|
||
switch currentStep {
|
||
case 0:
|
||
if overRect(mousePos, step1DefaultRect) {
|
||
useDefault = true
|
||
if !installer.IsInstalling && !installer.DoneInstall {
|
||
installer.FinalInstall()
|
||
}
|
||
startTransition(currentStep, finalStep)
|
||
}
|
||
if overRect(mousePos, step1CustomRect) {
|
||
useDefault = false
|
||
startTransition(currentStep, 1)
|
||
}
|
||
|
||
case 1:
|
||
if overButton(mousePos, nextX, nextY, int32(buttonW), int32(buttonH)) {
|
||
startTransition(currentStep, 2)
|
||
}
|
||
if overButton(mousePos, int32(prevX), int32(prevY), int32(buttonW), int32(buttonH)) {
|
||
startTransition(currentStep, 0)
|
||
}
|
||
selectColor(mousePos)
|
||
selectContrastIcon(mousePos)
|
||
selectTheme(mousePos)
|
||
|
||
case 2:
|
||
if overButton(mousePos, nextX, nextY, int32(buttonW), int32(buttonH)) {
|
||
if !installer.IsInstalling && !installer.DoneInstall {
|
||
installer.FinalInstall()
|
||
}
|
||
fmt.Printf("Installation started:\nDefault: %v\nColor: %s\nTheme: %s\nLayout: %s\n", useDefault, selectedColor, selectedTheme, selectedLayout)
|
||
startTransition(currentStep, finalStep)
|
||
}
|
||
if overButton(mousePos, int32(prevX), int32(prevY), int32(buttonW), int32(buttonH)) {
|
||
startTransition(currentStep, 1)
|
||
}
|
||
selectLayoutOption(mousePos)
|
||
}
|
||
}
|
||
|
||
func drawHeader(screenW int) {
|
||
title := "Spitfire Browser Installer"
|
||
titleFontSize := int32(30)
|
||
titleWidth := rl.MeasureText(title, titleFontSize)
|
||
rl.DrawText(title, (int32(screenW)-titleWidth)/2, 20, titleFontSize, rl.RayWhite)
|
||
rl.DrawLine(50, 60, int32(screenW)-50, 60, rl.Fade(rl.White, 0.5))
|
||
}
|
||
|
||
// Now we pass "displayProgress" (0..100) as a float, "posAlpha" in [0..1],
|
||
// plus "doneInstall" to show the "Run App" button
|
||
func drawInstallCircle(screenW, screenH int, posAlpha, displayProgress float32, doneInstall bool, hoverAlpha float32) {
|
||
// Lerp position/radius
|
||
topRight := rl.Vector2{X: float32(screenW - 80), Y: 100}
|
||
center := rl.Vector2{X: float32(screenW) / 2, Y: float32(screenH)/2 - 50}
|
||
x := lerp(topRight.X, center.X, posAlpha)
|
||
y := lerp(topRight.Y, center.Y, posAlpha)
|
||
r := lerp(30, 60, posAlpha)
|
||
|
||
// Draw background circle
|
||
bgColor := rl.Color{255, 255, 255, 80}
|
||
fillColor := rl.Color{255, 255, 255, 200}
|
||
|
||
rl.DrawCircle(int32(x), int32(y), r, bgColor)
|
||
angle := displayProgress / 100.0 * 360.0
|
||
rl.DrawCircleSector(rl.Vector2{X: x, Y: y}, r, 0, angle, 60, fillColor)
|
||
|
||
// Draw numeric progress
|
||
txt := fmt.Sprintf("%3.0f%%", displayProgress)
|
||
var fontSize int32 = 18
|
||
if posAlpha > 0.5 {
|
||
fontSize = 24 // bigger in final steps
|
||
}
|
||
txtW := rl.MeasureText(txt, fontSize)
|
||
rl.DrawText(txt, int32(x)-txtW/2, int32(y)-(fontSize/2), fontSize, rl.White)
|
||
|
||
if currentStep < finalStep {
|
||
// show the task text with "hoverAlpha"
|
||
if hoverAlpha > 0 {
|
||
drawTextWrappedAlpha(installer.Task, int32(x)-100, int32(y)+35, 200, 18, rl.White, hoverAlpha)
|
||
}
|
||
} else {
|
||
// final step => always show text, lower so bigger circle won't overlap
|
||
drawTextWrapped(installer.Task, int32(x)-100, int32(y+r+10), 200, 18, rl.White)
|
||
if doneInstall {
|
||
drawRunAppButton(x, y+r+80)
|
||
}
|
||
}
|
||
}
|
||
|
||
// "Run App" button logic is the same
|
||
func drawRunAppButton(cx, cy float32) {
|
||
w := float32(180)
|
||
h := float32(50)
|
||
rect := rl.Rectangle{X: cx - w/2, Y: cy, Width: w, Height: h}
|
||
hovered := overRect(rl.GetMousePosition(), rect)
|
||
drawRoundedRectButton(rect, "Start Spitfire", 1.0, hovered)
|
||
if hovered && rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||
fmt.Println("Launching the app (placeholder)...")
|
||
os.Exit(0)
|
||
}
|
||
}
|
||
|
||
// linear interpolation
|
||
func lerp(a, b, t float32) float32 {
|
||
return a + (b-a)*t
|
||
}
|
||
|
||
// Helper for text wrapping with alpha
|
||
func drawTextWrappedAlpha(text string, x, y, maxWidth, fontSize int32, color rl.Color, alpha float32) {
|
||
words := splitIntoWords(text)
|
||
line := ""
|
||
offsetY := int32(0)
|
||
|
||
for _, word := range words {
|
||
testLine := line + word + " "
|
||
if rl.MeasureText(testLine, fontSize) > int32(maxWidth) {
|
||
drawColoredText(line, x, y+offsetY, fontSize, color)
|
||
line = word + " "
|
||
offsetY += fontSize + 2
|
||
} else {
|
||
line = testLine
|
||
}
|
||
}
|
||
if line != "" {
|
||
drawColoredText(line, x, y+offsetY, fontSize, color)
|
||
}
|
||
}
|
||
|
||
// Same as drawTextWrapped, just using a color param
|
||
func drawColoredText(text string, x, y, fontSize int32, color rl.Color) {
|
||
rl.DrawText(text, x, y, fontSize, color)
|
||
}
|
||
|
||
// Custom function to draw text with wrapping
|
||
func drawTextWrapped(text string, x, y int32, maxWidth, fontSize int32, color rl.Color) {
|
||
words := splitIntoWords(text)
|
||
line := ""
|
||
offsetY := int32(0)
|
||
|
||
for _, word := range words {
|
||
testLine := line + word + " "
|
||
if rl.MeasureText(testLine, fontSize) > int32(maxWidth) {
|
||
rl.DrawText(line, x, y+offsetY, fontSize, color)
|
||
line = word + " "
|
||
offsetY += fontSize + 2
|
||
} else {
|
||
line = testLine
|
||
}
|
||
}
|
||
|
||
// Draw the last line
|
||
if line != "" {
|
||
rl.DrawText(line, x, y+offsetY, fontSize, color)
|
||
}
|
||
}
|
||
|
||
// Helper function to split text into words
|
||
func splitIntoWords(text string) []string {
|
||
words := []string{}
|
||
word := ""
|
||
for _, char := range text {
|
||
if char == ' ' || char == '\n' {
|
||
if word != "" {
|
||
words = append(words, word)
|
||
word = ""
|
||
}
|
||
if char == '\n' {
|
||
words = append(words, "\n")
|
||
}
|
||
} else {
|
||
word += string(char)
|
||
}
|
||
}
|
||
if word != "" {
|
||
words = append(words, word)
|
||
}
|
||
return words
|
||
}
|
||
|
||
// startTransition is your existing function
|
||
func startTransition(from, to int) {
|
||
targetStep = to
|
||
transition.Start(from, to)
|
||
}
|
||
|
||
// SPM example
|
||
|
||
// package main
|
||
|
||
// import (
|
||
// "fmt"
|
||
// "os"
|
||
// "path/filepath"
|
||
// "spitfire-installer/spm"
|
||
// "time"
|
||
// )
|
||
|
||
// func main() {
|
||
// // Start a goroutine to display progress updates
|
||
// done := make(chan bool)
|
||
// go func() {
|
||
// for {
|
||
// select {
|
||
// case <-done:
|
||
// return
|
||
// default:
|
||
// percentage, task := spm.GetProgress()
|
||
// fmt.Printf("\r[%3d%%] %s", percentage, task)
|
||
// time.Sleep(500 * time.Millisecond)
|
||
// }
|
||
// }
|
||
// }()
|
||
|
||
// // Set up the download directory
|
||
// downloadDir := spm.GetTempDownloadDir()
|
||
// fmt.Println("\nTemporary download directory:", downloadDir)
|
||
|
||
// // Download the APPINDEX
|
||
// appIndexPath := filepath.Join(downloadDir, "APPINDEX")
|
||
// spm.UpdateProgress(0, "Starting APPINDEX download")
|
||
// if err := spm.DownloadAppIndex(appIndexPath); err != nil {
|
||
// fmt.Println("\nError downloading APPINDEX:", err)
|
||
// done <- true
|
||
// os.Exit(1)
|
||
// }
|
||
|
||
// // Download the desired package version (e.g., nightly)
|
||
// packageName := "spitfire-browser"
|
||
// release := "nightly"
|
||
|
||
// spm.UpdateProgress(0, "Starting package download")
|
||
// if err := spm.DownloadPackageFromAppIndex(appIndexPath, packageName, release, downloadDir); err != nil {
|
||
// fmt.Println("\nError downloading package:", err)
|
||
// done <- true
|
||
// os.Exit(1)
|
||
// }
|
||
|
||
// // Decompress and install
|
||
// packagePath := filepath.Join(downloadDir, "browser-amd64-nightly-linux.tar.gz")
|
||
// spm.UpdateProgress(0, "Starting decompression")
|
||
// tempDir, err := spm.DecompressToTemp(packagePath)
|
||
// if err != nil {
|
||
// fmt.Println("\nError decompressing package:", err)
|
||
// done <- true
|
||
// os.Exit(1)
|
||
// }
|
||
// fmt.Println("\nDecompressed package to:", tempDir)
|
||
|
||
// // Generate default install directory
|
||
// installDir, err := spm.GetDefaultInstallDir()
|
||
// if err != nil {
|
||
// inst.LastError = fmt.Errorf("failed to determine default install directory: %w", err)
|
||
// return
|
||
// }
|
||
|
||
// spm.UpdateProgress(0, "Starting installation")
|
||
// if err := spm.MoveFilesToInstallDir(tempDir, installDir); err != nil {
|
||
// fmt.Println("\nError installing package:", err)
|
||
// done <- true
|
||
// os.Exit(1)
|
||
// }
|
||
|
||
// // Notify progress display to stop and finalize
|
||
// done <- true
|
||
// fmt.Printf("\nSuccessfully installed %s (%s) to %s\n", packageName, release, installDir)
|
||
// }
|