package main import ( "bufio" "encoding/json" "fmt" "io" "os" "os/exec" "path/filepath" "runtime" "strings" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) type Project struct { EnginePath string `json:"enginePath"` ProjectPath string `json:"projectPath"` ProjectName string `json:"projectName"` Version string `json:"engineVersion"` } const projectsFilePath = "projects.json" var projectList *fyne.Container var logText *widget.Entry var scrollContainer *container.Scroll var logBuffer []string var logMaxLines = 7 type hoverableButton struct { widget.Button onMouseIn func() onMouseOut func() } func (h *hoverableButton) MouseIn() { if h.onMouseIn != nil { h.onMouseIn() } } func (h *hoverableButton) MouseOut() { if h.onMouseOut != nil { h.onMouseOut() } } func loadProjects() ([]Project, error) { file, err := os.ReadFile(projectsFilePath) if err != nil { return nil, err } var projects []Project if err := json.Unmarshal(file, &projects); err != nil { return nil, err } return projects, nil } func removeDir(path string) { err := os.RemoveAll(path) if err != nil { log("Can't remove content at " + path) } else { log("Removed " + path) } } func cleanUnrealProject(project Project) { dirs := []string { "Binaries", "DerivedDataCache", "Intermediate", "Saved", "Script", } for _, dir := range dirs { fullPath := filepath.Join(project.ProjectPath, dir) removeDir(fullPath) } pluginsPath := filepath.Join(project.ProjectPath, "Plugins") entries, err := os.ReadDir(pluginsPath) if err != nil { return } for _, entry := range entries { if !entry.IsDir() { continue } pluginDir := filepath.Join(pluginsPath, entry.Name()) removeDir(filepath.Join(pluginDir, "Binaries")) removeDir(filepath.Join(pluginDir, "Intermediate")) } } func generateUnrealSolution(project Project) { var buildCmd string switch runtime.GOOS { case "linux": buildCmd = project.EnginePath + "/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh"; case "darwin": buildCmd = project.EnginePath + "/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" default: fmt.Println("Generate Project is not yet supported on Windows") return } projectCmd := project.ProjectPath + "/" + project.ProjectName + ".uproject" cmd := exec.Command(buildCmd, projectCmd, "-game") logToOutput(cmd) } func buildUnrealSolution(project Project) { switch runtime.GOOS { case "linux": cmd := exec.Command("make", "-C", project.ProjectPath, project.ProjectName) logToOutput(cmd) default: fmt.Println("Build Project is not yet supported on Windows") return } } func runUnrealProject(project Project) { var buildCmd string switch runtime.GOOS { case "linux": buildCmd = project.EnginePath + "/Engine/Binaries/Linux/UnrealEditor" case "darwin": buildCmd = project.EnginePath + "/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor" default: buildCmd = project.EnginePath + "/Engine/Binaries/Win64/UnrealEditor.exe" } projectCmd := project.ProjectPath + "/" + project.ProjectName + ".uproject" cmd := exec.Command(buildCmd, projectCmd) logToOutput(cmd) } func logToOutput(cmd *exec.Cmd) { stdout, err := cmd.StdoutPipe() if err != nil { fmt.Println("Error during pipe stdout creation") } stderr, err := cmd.StderrPipe() if err != nil { fmt.Println("Error during pipe stderr creation") } if err := cmd.Start(); err != nil { fmt.Println("Error while loading command") } readPipe := func(reader io.ReadCloser) { scanner := bufio.NewScanner(reader) for scanner.Scan() { line := scanner.Text() log(line) } } go readPipe(stdout) go readPipe(stderr) if err := cmd.Wait(); err != nil { fmt.Println("Error") } } func log(line string) { logBuffer = append(logBuffer, line) if len(logBuffer) > logMaxLines { logBuffer = logBuffer[1:] } logText.SetText(strings.Join(logBuffer, "\n")) } func newProjectRow(project Project) fyne.CanvasObject { projectName := project.ProjectName iconPath := filepath.Join(project.ProjectPath, projectName+".png") var projectIcon fyne.Resource if _, err := os.Stat(iconPath); err == nil { icon, err := fyne.LoadResourceFromPath(iconPath) if err == nil { projectIcon = icon } } if projectIcon == nil { projectIcon = theme.FileIcon() } icon := canvas.NewImageFromResource(projectIcon) icon.SetMinSize(fyne.NewSize(48, 48)) nameLabel := widget.NewLabel(projectName + " / Unreal Engine " + project.Version) nameLabel.Alignment = fyne.TextAlignLeading // Crée les boutons masqués par défaut clearButton := widget.NewButton("Clean", func() { cleanUnrealProject(project) }) generateBtn := widget.NewButton("Generate", func() { generateUnrealSolution(project) }) buildBtn := widget.NewButton("Build", func() { buildUnrealSolution(project) }) launchBtn := widget.NewButton("Run", func() { runUnrealProject(project) }) buttons := container.NewHBox(clearButton, generateBtn, buildBtn, launchBtn) // Conteneur principal row := container.NewHBox( icon, nameLabel, layout.NewSpacer(), buttons, ) return row } func main() { myApp := app.New() myWindow := myApp.NewWindow("Unreal Project Launcher") myWindow.Resize(fyne.NewSize(730, 460)) projects, err := loadProjects() if err != nil { fmt.Println("Error while loading projects") } logText = widget.NewMultiLineEntry() logText.SetMinRowsVisible(logMaxLines); reloadUI := func() { projectList.RemoveAll() for _, project := range projects { row := newProjectRow(project) projectList.Add(row) } } projectList = container.NewVBox() reloadUI() scrollContainer = container.NewVScroll(projectList) mainContainer := container.NewBorder(nil, logText, nil, nil, scrollContainer) myWindow.SetContent(mainContainer) myWindow.ShowAndRun() }