Installer/main.go
partisan b403befe74
Some checks failed
/ test-on-windows (push) Failing after 9s
/ test-on-alpine (push) Successful in 1m14s
fixed windows bugs
2025-01-21 23:31:53 +01:00

427 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 circles 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)
// }