added installation progress tab
This commit is contained in:
parent
9e5457c2ec
commit
ad826b3b43
3 changed files with 254 additions and 76 deletions
73
circle_animator.go
Normal file
73
circle_animator.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CircleAnimator manages both the position/radius alpha (0..1)
|
||||||
|
// and the displayed progress which eases toward the actual SPM progress.
|
||||||
|
type CircleAnimator struct {
|
||||||
|
PosAlpha float32 // 0 => top-right, 1 => center
|
||||||
|
DisplayProgress float32 // The "smooth" progress shown on screen
|
||||||
|
targetStep int // The step controlling circle's final position (0..2 => top-right, 3 => center)
|
||||||
|
|
||||||
|
// For smoothing the circle position alpha with "easeInOutCubic"
|
||||||
|
accumSec float32
|
||||||
|
duration float32
|
||||||
|
startAlpha float32
|
||||||
|
endAlpha float32
|
||||||
|
|
||||||
|
// For smoothing the displayed progress
|
||||||
|
// We'll do a simple exponential approach each frame.
|
||||||
|
// Alternatively, you could do a time-based tween.
|
||||||
|
smoothingFactor float32 // e.g. 0.1 => about 10% approach per frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCircleAnimator sets initial defaults
|
||||||
|
func NewCircleAnimator() *CircleAnimator {
|
||||||
|
return &CircleAnimator{
|
||||||
|
PosAlpha: 0.0, // start at top-right
|
||||||
|
DisplayProgress: 0.0,
|
||||||
|
duration: 1.0, // 1 second total for position tween
|
||||||
|
smoothingFactor: 0.07, // how fast progress approaches real
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStep sets whether we want circle in top-right (step<3 => alpha=0) or center (step=3 => alpha=1).
|
||||||
|
// This starts a new positional tween from current alpha to the new alpha.
|
||||||
|
func (c *CircleAnimator) SetStep(step int) {
|
||||||
|
c.targetStep = step
|
||||||
|
var newEnd float32 = 0.0
|
||||||
|
if step == 3 {
|
||||||
|
newEnd = 1.0
|
||||||
|
}
|
||||||
|
// Start new tween from current alpha to newEnd
|
||||||
|
c.startAlpha = c.PosAlpha
|
||||||
|
c.endAlpha = newEnd
|
||||||
|
c.accumSec = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates both the position alpha (with easeInOutCubic) and the displayed progress.
|
||||||
|
func (c *CircleAnimator) Update(realProgress int) {
|
||||||
|
dt := rl.GetFrameTime() // seconds
|
||||||
|
|
||||||
|
// 1) Update position alpha
|
||||||
|
if c.accumSec < c.duration {
|
||||||
|
c.accumSec += dt
|
||||||
|
if c.accumSec > c.duration {
|
||||||
|
c.accumSec = c.duration
|
||||||
|
}
|
||||||
|
p := c.accumSec / c.duration
|
||||||
|
eased := easeInOutCubic(p)
|
||||||
|
// Lerp from startAlpha to endAlpha with eased fraction
|
||||||
|
c.PosAlpha = c.startAlpha + (c.endAlpha-c.startAlpha)*eased
|
||||||
|
} else {
|
||||||
|
// no active tween, just keep the final alpha
|
||||||
|
c.PosAlpha = c.endAlpha
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Smoothly approach the real progress
|
||||||
|
// (exponential approach: new = old + factor*(target - old))
|
||||||
|
target := float32(realProgress)
|
||||||
|
c.DisplayProgress += c.smoothingFactor * (target - c.DisplayProgress)
|
||||||
|
}
|
39
installer.go
39
installer.go
|
@ -18,6 +18,7 @@ type Installer struct {
|
||||||
DoneDownload bool
|
DoneDownload bool
|
||||||
DoneInstall bool
|
DoneInstall bool
|
||||||
LastError error
|
LastError error
|
||||||
|
PendingInstall bool
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
DownloadDir string
|
DownloadDir string
|
||||||
|
@ -36,13 +37,18 @@ func (inst *Installer) StartDownloadDecompress() {
|
||||||
defer func() {
|
defer func() {
|
||||||
inst.IsDownloading = false
|
inst.IsDownloading = false
|
||||||
inst.DoneDownload = (inst.LastError == nil)
|
inst.DoneDownload = (inst.LastError == nil)
|
||||||
|
|
||||||
|
// If user requested install while we were downloading (PendingInstall),
|
||||||
|
// automatically do the install now that we're done decompressing.
|
||||||
|
if inst.PendingInstall && inst.DoneDownload && !inst.IsInstalling && !inst.DoneInstall {
|
||||||
|
inst.doFinalInstall()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Prepare download directory
|
|
||||||
spm.UpdateProgress(0, "Preparing to download...")
|
spm.UpdateProgress(0, "Preparing to download...")
|
||||||
inst.DownloadDir = spm.GetTempDownloadDir()
|
inst.DownloadDir = spm.GetTempDownloadDir()
|
||||||
|
|
||||||
// Download APPINDEX
|
// 1) Download APPINDEX
|
||||||
appIndexPath := filepath.Join(inst.DownloadDir, "APPINDEX")
|
appIndexPath := filepath.Join(inst.DownloadDir, "APPINDEX")
|
||||||
spm.UpdateProgress(0, "Downloading APPINDEX")
|
spm.UpdateProgress(0, "Downloading APPINDEX")
|
||||||
if err := spm.DownloadAppIndex(appIndexPath); err != nil {
|
if err := spm.DownloadAppIndex(appIndexPath); err != nil {
|
||||||
|
@ -50,7 +56,7 @@ func (inst *Installer) StartDownloadDecompress() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download package
|
// 2) Download package
|
||||||
packageName := "spitfire-browser"
|
packageName := "spitfire-browser"
|
||||||
release := "nightly"
|
release := "nightly"
|
||||||
spm.UpdateProgress(0, "Downloading package...")
|
spm.UpdateProgress(0, "Downloading package...")
|
||||||
|
@ -59,7 +65,7 @@ func (inst *Installer) StartDownloadDecompress() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompress
|
// 3) Decompress
|
||||||
spm.UpdateProgress(0, "Decompressing...")
|
spm.UpdateProgress(0, "Decompressing...")
|
||||||
packagePath := filepath.Join(inst.DownloadDir, "browser-amd64-nightly-linux.tar.gz")
|
packagePath := filepath.Join(inst.DownloadDir, "browser-amd64-nightly-linux.tar.gz")
|
||||||
tempDir, err := spm.DecompressToTemp(packagePath)
|
tempDir, err := spm.DecompressToTemp(packagePath)
|
||||||
|
@ -67,19 +73,34 @@ func (inst *Installer) StartDownloadDecompress() {
|
||||||
inst.LastError = err
|
inst.LastError = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.TempDir = tempDir
|
inst.TempDir = tempDir
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinalInstall moves files to the final install directory in a background goroutine.
|
// FinalInstall is called by the UI to request installation.
|
||||||
|
// If download is done, it runs immediately, otherwise sets PendingInstall=true.
|
||||||
func (inst *Installer) FinalInstall() {
|
func (inst *Installer) FinalInstall() {
|
||||||
if !inst.DoneDownload {
|
// Already installed or installing => ignore repeated calls
|
||||||
inst.LastError = fmt.Errorf("Cannot install: download and decompression are not complete")
|
if inst.IsInstalling || inst.DoneInstall {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If not done downloading, just mark that we want to install once finished
|
||||||
|
if !inst.DoneDownload {
|
||||||
|
fmt.Println("Cannot install now: download and decompression not complete -> pending install.")
|
||||||
|
inst.PendingInstall = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, go ahead and install now
|
||||||
|
inst.doFinalInstall()
|
||||||
|
}
|
||||||
|
|
||||||
|
// doFinalInstall does the actual file move and sets states
|
||||||
|
func (inst *Installer) doFinalInstall() {
|
||||||
inst.IsInstalling = true
|
inst.IsInstalling = true
|
||||||
|
inst.PendingInstall = false // we are fulfilling the install now
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
inst.IsInstalling = false
|
inst.IsInstalling = false
|
||||||
|
@ -93,6 +114,8 @@ func (inst *Installer) FinalInstall() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move files
|
||||||
|
spm.UpdateProgress(0, "Installing...")
|
||||||
if err := spm.MoveFilesToInstallDir(inst.TempDir, installDir); err != nil {
|
if err := spm.MoveFilesToInstallDir(inst.TempDir, installDir); err != nil {
|
||||||
inst.LastError = err
|
inst.LastError = err
|
||||||
return
|
return
|
||||||
|
|
200
main.go
200
main.go
|
@ -2,24 +2,32 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Keep your global variables for steps and transitions
|
|
||||||
var (
|
var (
|
||||||
transition = NewTransitionManager()
|
transition = NewTransitionManager()
|
||||||
currentStep = 0
|
currentStep = 0
|
||||||
targetStep = 0
|
targetStep = 0
|
||||||
useDefault = false
|
useDefault = false
|
||||||
|
|
||||||
step1DefaultRect rl.Rectangle
|
step1DefaultRect rl.Rectangle
|
||||||
step1CustomRect rl.Rectangle
|
step1CustomRect rl.Rectangle
|
||||||
|
|
||||||
// Our global Installer from installer.go
|
|
||||||
installer = NewInstaller()
|
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() {
|
func main() {
|
||||||
monitor := rl.GetCurrentMonitor()
|
monitor := rl.GetCurrentMonitor()
|
||||||
screenW := rl.GetMonitorWidth(monitor)
|
screenW := rl.GetMonitorWidth(monitor)
|
||||||
|
@ -31,16 +39,17 @@ func main() {
|
||||||
|
|
||||||
refreshRate := rl.GetMonitorRefreshRate(monitor)
|
refreshRate := rl.GetMonitorRefreshRate(monitor)
|
||||||
if refreshRate <= 0 {
|
if refreshRate <= 0 {
|
||||||
refreshRate = 60 // Fallback to 60 if detection fails
|
refreshRate = 60
|
||||||
}
|
}
|
||||||
rl.SetTargetFPS(int32(refreshRate))
|
rl.SetTargetFPS(int32(refreshRate))
|
||||||
|
|
||||||
InitBackground(rl.GetScreenWidth(), rl.GetScreenHeight())
|
InitBackground(rl.GetScreenWidth(), rl.GetScreenHeight())
|
||||||
|
|
||||||
// Start the download+decompress in background immediately:
|
// Start the download+decompress in background immediately
|
||||||
installer.StartDownloadDecompress()
|
installer.StartDownloadDecompress()
|
||||||
|
|
||||||
targetStep = 0
|
// Initially step 0 => circle is top-right => circleAnimator.PosAlpha=0
|
||||||
|
circleAnimator.SetStep(0)
|
||||||
|
|
||||||
for !rl.WindowShouldClose() {
|
for !rl.WindowShouldClose() {
|
||||||
screenW = rl.GetScreenWidth()
|
screenW = rl.GetScreenWidth()
|
||||||
|
@ -54,24 +63,50 @@ func main() {
|
||||||
nextX := int32(screenW - 150)
|
nextX := int32(screenW - 150)
|
||||||
nextY := prevY
|
nextY := prevY
|
||||||
|
|
||||||
// Update transition
|
// Transition update
|
||||||
oldAlpha, oldScale, oldOffsetX, newAlpha, newScale, newOffsetX := transition.Update()
|
oldAlpha, oldScale, oldOffsetX, newAlpha, newScale, newOffsetX := transition.Update()
|
||||||
|
|
||||||
if !transition.IsActive() && currentStep != targetStep {
|
if !transition.IsActive() && currentStep != targetStep {
|
||||||
currentStep = targetStep
|
currentStep = targetStep
|
||||||
|
// Update circleAnimator step so it moves (0 => top-right, 3 => center)
|
||||||
|
circleAnimator.SetStep(currentStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll SPM progress for the GUI
|
// Poll SPM progress
|
||||||
installer.PollProgress()
|
installer.PollProgress()
|
||||||
|
|
||||||
// If an unrecoverable error occurred, you could handle it here:
|
// If error
|
||||||
// (For now, we just print it in the console.)
|
|
||||||
if installer.LastError != nil {
|
if installer.LastError != nil {
|
||||||
fmt.Println("SPM Error:", installer.LastError)
|
fmt.Println("SPM Error:", installer.LastError)
|
||||||
// You might choose to show a popup or do something else
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GUI input
|
// 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) {
|
if !transition.IsActive() && rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||||||
handleInput(mousePos, screenW, screenH, buttonW, buttonH, prevX, prevY, nextX, nextY)
|
handleInput(mousePos, screenW, screenH, buttonW, buttonH, prevX, prevY, nextX, nextY)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +124,6 @@ func main() {
|
||||||
oldStep = transition.oldStep
|
oldStep = transition.oldStep
|
||||||
newStep = transition.newStep
|
newStep = transition.newStep
|
||||||
}
|
}
|
||||||
|
|
||||||
phase := transition.GetPhase()
|
phase := transition.GetPhase()
|
||||||
if transition.IsActive() {
|
if transition.IsActive() {
|
||||||
if phase == TransitionOutFadeScale {
|
if phase == TransitionOutFadeScale {
|
||||||
|
@ -99,10 +133,11 @@ func main() {
|
||||||
drawStep(newStep, screenW, screenH, mousePos, newAlpha, newScale, newOffsetX)
|
drawStep(newStep, screenW, screenH, mousePos, newAlpha, newScale, newOffsetX)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
drawStep(currentStep, screenW, screenH, mousePos, 1.0, 1.0, 0.0)
|
drawStep(currentStep, screenW, screenH, mousePos, 1, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !transition.IsActive() {
|
// Nav buttons if not final step
|
||||||
|
if !transition.IsActive() && currentStep < finalStep {
|
||||||
if currentStep > 0 && currentStep < 3 {
|
if currentStep > 0 && currentStep < 3 {
|
||||||
drawButton("Previous", prevX, prevY, int32(buttonW), int32(buttonH), mousePos)
|
drawButton("Previous", prevX, prevY, int32(buttonW), int32(buttonH), mousePos)
|
||||||
}
|
}
|
||||||
|
@ -113,33 +148,29 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the semi-transparent loading circle if user is downloading or installing
|
// Draw the circle
|
||||||
if installer.IsDownloading || installer.IsInstalling {
|
drawInstallCircle(screenW, screenH, circleAnimator.PosAlpha, circleAnimator.DisplayProgress, installer.DoneInstall, textHoverFade)
|
||||||
drawInstallProgress(screenW, installer.Progress, installer.Task)
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.EndDrawing()
|
rl.EndDrawing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleInput acts on mouse clicks in each step
|
func handleInput(mousePos rl.Vector2, screenW, screenH, buttonW, buttonH int, prevX, prevY, nextX, nextY int32) {
|
||||||
func handleInput(mousePos rl.Vector2, screenW, screenH, buttonW, buttonH int,
|
switch currentStep {
|
||||||
prevX, prevY, nextX, nextY int32) {
|
case 0:
|
||||||
|
|
||||||
if currentStep == 0 {
|
|
||||||
if overRect(mousePos, step1DefaultRect) {
|
if overRect(mousePos, step1DefaultRect) {
|
||||||
// user clicked "Default" => do final install if not already installing
|
|
||||||
useDefault = true
|
useDefault = true
|
||||||
if !installer.IsInstalling && !installer.DoneInstall {
|
if !installer.IsInstalling && !installer.DoneInstall {
|
||||||
installer.FinalInstall()
|
installer.FinalInstall()
|
||||||
}
|
}
|
||||||
fmt.Println("Installation started with default settings.")
|
startTransition(currentStep, finalStep)
|
||||||
}
|
}
|
||||||
if overRect(mousePos, step1CustomRect) {
|
if overRect(mousePos, step1CustomRect) {
|
||||||
useDefault = false
|
useDefault = false
|
||||||
startTransition(currentStep, 1)
|
startTransition(currentStep, 1)
|
||||||
}
|
}
|
||||||
} else if currentStep == 1 {
|
|
||||||
|
case 1:
|
||||||
if overButton(mousePos, nextX, nextY, int32(buttonW), int32(buttonH)) {
|
if overButton(mousePos, nextX, nextY, int32(buttonW), int32(buttonH)) {
|
||||||
startTransition(currentStep, 2)
|
startTransition(currentStep, 2)
|
||||||
}
|
}
|
||||||
|
@ -149,14 +180,14 @@ func handleInput(mousePos rl.Vector2, screenW, screenH, buttonW, buttonH int,
|
||||||
selectColor(mousePos)
|
selectColor(mousePos)
|
||||||
selectContrastIcon(mousePos)
|
selectContrastIcon(mousePos)
|
||||||
selectTheme(mousePos)
|
selectTheme(mousePos)
|
||||||
} else if currentStep == 2 {
|
|
||||||
|
case 2:
|
||||||
if overButton(mousePos, nextX, nextY, int32(buttonW), int32(buttonH)) {
|
if overButton(mousePos, nextX, nextY, int32(buttonW), int32(buttonH)) {
|
||||||
// user clicked "Finish" => final install if not already installing
|
|
||||||
if !installer.IsInstalling && !installer.DoneInstall {
|
if !installer.IsInstalling && !installer.DoneInstall {
|
||||||
installer.FinalInstall()
|
installer.FinalInstall()
|
||||||
}
|
}
|
||||||
fmt.Printf("Installation started:\nDefault: %v\nColor: %s\nTheme: %s\nLayout: %s\n",
|
fmt.Printf("Installation started:\nDefault: %v\nColor: %s\nTheme: %s\nLayout: %s\n", useDefault, selectedColor, selectedTheme, selectedLayout)
|
||||||
useDefault, selectedColor, selectedTheme, selectedLayout)
|
startTransition(currentStep, finalStep)
|
||||||
}
|
}
|
||||||
if overButton(mousePos, int32(prevX), int32(prevY), int32(buttonW), int32(buttonH)) {
|
if overButton(mousePos, int32(prevX), int32(prevY), int32(buttonW), int32(buttonH)) {
|
||||||
startTransition(currentStep, 1)
|
startTransition(currentStep, 1)
|
||||||
|
@ -165,7 +196,6 @@ func handleInput(mousePos rl.Vector2, screenW, screenH, buttonW, buttonH int,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawHeader is your original function
|
|
||||||
func drawHeader(screenW int) {
|
func drawHeader(screenW int) {
|
||||||
title := "Spitfire Browser Installer"
|
title := "Spitfire Browser Installer"
|
||||||
titleFontSize := int32(30)
|
titleFontSize := int32(30)
|
||||||
|
@ -174,37 +204,89 @@ func drawHeader(screenW int) {
|
||||||
rl.DrawLine(50, 60, int32(screenW)-50, 60, rl.Fade(rl.White, 0.5))
|
rl.DrawLine(50, 60, int32(screenW)-50, 60, rl.Fade(rl.White, 0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawInstallProgress displays a simple white circle with partial alpha,
|
// Now we pass "displayProgress" (0..100) as a float, "posAlpha" in [0..1],
|
||||||
// plus the current task text below it, in the top-right area.
|
// plus "doneInstall" to show the "Run App" button
|
||||||
func drawInstallProgress(screenW int, progress int, task string) {
|
func drawInstallCircle(screenW, screenH int, posAlpha, displayProgress float32, doneInstall bool, hoverAlpha float32) {
|
||||||
circleX := float32(screenW - 80)
|
// Lerp position/radius
|
||||||
circleY := float32(100)
|
topRight := rl.Vector2{X: float32(screenW - 80), Y: 100}
|
||||||
radius := float32(30)
|
center := rl.Vector2{X: float32(screenW) / 2, Y: float32(screenH)/2 - 50}
|
||||||
|
x := lerp(topRight.X, center.X, posAlpha)
|
||||||
// Colors for the circle
|
y := lerp(topRight.Y, center.Y, posAlpha)
|
||||||
bgColor := rl.Color{R: 255, G: 255, B: 255, A: 80}
|
r := lerp(30, 60, posAlpha)
|
||||||
fillColor := rl.Color{R: 255, G: 255, B: 255, A: 200}
|
|
||||||
|
|
||||||
// Draw background circle
|
// Draw background circle
|
||||||
rl.DrawCircle(int32(circleX), int32(circleY), radius, bgColor)
|
bgColor := rl.Color{255, 255, 255, 80}
|
||||||
|
fillColor := rl.Color{255, 255, 255, 200}
|
||||||
|
|
||||||
// Draw progress arc
|
rl.DrawCircle(int32(x), int32(y), r, bgColor)
|
||||||
angle := float32(progress) / 100.0 * 360.0
|
angle := displayProgress / 100.0 * 360.0
|
||||||
rl.DrawCircleSector(
|
rl.DrawCircleSector(rl.Vector2{X: x, Y: y}, r, 0, angle, 60, fillColor)
|
||||||
rl.Vector2{X: circleX, Y: circleY},
|
|
||||||
radius,
|
|
||||||
0,
|
|
||||||
angle,
|
|
||||||
40,
|
|
||||||
fillColor,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Print numeric progress
|
// Draw numeric progress
|
||||||
txt := fmt.Sprintf("%3d%%", progress)
|
txt := fmt.Sprintf("%3.0f%%", displayProgress)
|
||||||
rl.DrawText(txt, int32(circleX)-rl.MeasureText(txt, 18)/2, int32(circleY)-10, 18, rl.White)
|
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)
|
||||||
|
|
||||||
// Draw wrapped task text below the circle
|
if currentStep < finalStep {
|
||||||
drawTextWrapped(task, int32(circleX)-100, int32(circleY)+40, 200, 18, rl.White)
|
// 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
|
// Custom function to draw text with wrapping
|
||||||
|
|
Loading…
Add table
Reference in a new issue