Files
MrixsCraft-launcher/internal/ui/screens/screens.go
Vladimir Zagainov 1487360215 feat: add UI layer (theme, components, screens, main window)
- theme: MinecraftTheme — dark palette with green accents
- components: ServerCard, ProgressBar, PlayButton, SettingsButton, LogoutButton, AvatarImage
- screens: MainScreen (BorderLayout: sidebar + center + bottom bar), LoginScreen (modal form), SettingsScreen (RAM slider + JVM args)
- ui.Launch: wires theme, session, settings into Fyne app
- main.go: delegates to ui.Launch, loads config
- fix: nil-session guard in MainScreen username display

Co-Authored-By: OWL <noreply@anthropic.com>
2026-05-26 11:22:41 +03:00

194 lines
4.6 KiB
Go

// package screens implements application screens (login, main menu, settings).
package screens
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"gitea.mrixs.me/Mrixs/MrixsCraft-launcher/internal/auth"
"gitea.mrixs.me/Mrixs/MrixsCraft-launcher/internal/ui/components"
)
// Modpack represents a single server/modpack entry from the backend.
type Modpack struct {
Slug string
Name string
}
// MainScreen builds the primary launcher window content.
func MainScreen(
w fyne.Window,
client *auth.Client,
session *auth.Session,
serverList []Modpack,
onPlay func(),
onSettings func(),
) fyne.CanvasObject {
// ── Sidebar (Left) ──────────────────────────────────────
serverLabels := make([]*widget.Label, len(serverList))
serverContainer := container.NewVBox()
for i, sp := range serverList {
label := widget.NewLabel(sp.Name)
serverLabels[i] = label
index := i
card := components.ServerCard(sp.Name, index == 0, func() {
for j, l := range serverLabels {
l.TextStyle.Bold = (j == index)
l.Refresh()
}
})
serverContainer.Add(card)
}
sidebar := container.NewVScroll(serverContainer)
// ── Center (background + info) ──────────────────────────
bg := widget.NewRichTextFromMarkdown(
"# Welcome to MrixsCraft\n\n" +
"Select a server from the left panel and press **PLAY**.\n\n" +
"---\n\n" +
"*Version: dev*",
)
center := container.NewMax(widget.NewCard("", "", bg))
// ── Bottom bar ──────────────────────────────────────────
avatar := components.AvatarImage()
displayName := "Not logged in"
if session != nil && session.Username != "" {
displayName = session.Username
}
usernameLabel := widget.NewLabel(displayName)
logoutBtn := components.LogoutButton(func() {
dialog.ShowConfirm(
"Logout",
"Are you sure you want to log out?",
func(confirmed bool) {
if confirmed {
_ = auth.Delete()
w.Close()
}
},
w,
)
})
bottomLeft := container.NewHBox(
avatar,
usernameLabel,
layout.NewSpacer(),
logoutBtn,
)
progress := components.NewProgressBar()
playBtn := components.PlayButton(onPlay)
settingsBtn := components.SettingsButton(onSettings)
bottomRight := container.NewHBox(
settingsBtn,
widget.NewSeparator(),
playBtn,
)
bottomCenter := container.NewMax(progress)
bottom := container.NewBorder(
nil, nil,
bottomLeft,
bottomRight,
bottomCenter,
)
// ── Assemble ────────────────────────────────────────────
return container.NewBorder(
nil,
bottom,
sidebar,
nil,
center,
)
}
// LoginScreen builds a modal login form.
func LoginScreen(w fyne.Window, client *auth.Client, onLogin func(*auth.Session)) {
username := widget.NewEntry()
username.SetPlaceHolder("Username or email")
password := widget.NewPasswordEntry()
password.SetPlaceHolder("Password")
form := widget.NewForm(
widget.NewFormItem("Username", username),
widget.NewFormItem("Password", password),
)
dialog.ShowCustomConfirm(
"Login — MrixsCraft",
"Login",
"Cancel",
form,
func(confirmed bool) {
if !confirmed {
return
}
sess, err := client.Authenticate(username.Text, password.Text)
if err != nil {
dialog.ShowError(err, w)
return
}
if err := sess.Save(); err != nil {
dialog.ShowError(err, w)
return
}
onLogin(sess)
},
w,
)
}
// SettingsScreen builds the settings modal.
func SettingsScreen(w fyne.Window, onSave func(memoryMB int, extraArgs string)) {
memorySlider := widget.NewSlider(1024, 16384)
memorySlider.Step = 512
memorySlider.Value = 4096
memLabel := widget.NewLabel("4096 MB")
memorySlider.OnChanged = func(v float64) {
memLabel.SetText(fmt.Sprintf("%.0f MB", v))
}
extraArgs := widget.NewMultiLineEntry()
extraArgs.SetPlaceHolder("e.g. -XX:+UseG1GC -XX:MaxGCPauseMillis=50")
content := container.NewVBox(
widget.NewLabelWithStyle("Memory (RAM)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
memLabel,
memorySlider,
widget.NewSeparator(),
widget.NewLabelWithStyle("Extra JVM arguments", fyne.TextAlignLeading, fyne.TextStyle{}),
extraArgs,
)
dialog.ShowCustomConfirm(
"Settings",
"Save",
"Cancel",
content,
func(confirmed bool) {
if confirmed {
onSave(int(memorySlider.Value), extraArgs.Text)
}
},
w,
)
}