package KFEditor import ( "log" "os" "path/filepath" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/widget" "g.r-io.lu/shynd/kemoforge/pkg/KFData/KFConfig" "g.r-io.lu/shynd/kemoforge/pkg/KFData/KFError" "g.r-io.lu/shynd/kemoforge/pkg/KFData/KFLog" ) // CreateMainContent updates the loading variable as the KemoForge content is created func CreateMainContent(window fyne.Window) { // runs "go version" to check if Go is installed // loading.SetProgress(10, "Checking dependencies") CheckAndInstallDependencies(window) // creates a main menu to hold the buttons below // loading.SetProgress(30, "Creating main menu") CreateMainMenu(window) // create a grid layout for the four main buttons // loading.SetProgress(50, "Creating main content") grid := CreateMainGrid(window) // fetch the buttons for the grid to allow for disabling them openRecentButton := grid.(*fyne.Container).Objects[2].(*widget.Button) continueLastButton := grid.(*fyne.Container).Objects[3].(*widget.Button) // loading.SetProgress(70, "Initializing buttons") err := InitButtons(window, continueLastButton, openRecentButton) if err != nil { // show an error dialog dialog.ShowError(err, window) return } // loading.SetProgress(100, "Setting content") window.SetContent(grid) // loading.Complete() } func InitButtons(window fyne.Window, continueLastButton *widget.Button, openRecentButton *widget.Button) error { projects, err := ReadProjectInfo() if err != nil { //Show an error dialog dialog.ShowError(err, window) return err } var project KFInfo if len(projects) == 0 { continueLastButton.Disable() openRecentButton.Disable() } else { //Get the most recently opened project project = projects[0] for i := 0; i < len(projects); i++ { if projects[i].OpenDate.After(project.OpenDate) { project = projects[i] } } //Os stat the project path to see if it still exists _, err = os.Stat(project.Path) if err != nil { //If the project does not exist, disable the continue last button continueLastButton.Disable() } } continueLastButton.OnTapped = func() { err = OpenFromInfo(project, window) if err != nil { //Show an error dialog dialog.ShowError(err, window) } } return nil } // CreateMainGrid creates the main grid layout for the main window func CreateMainGrid(window fyne.Window) fyne.CanvasObject { grid := container.New(layout.NewGridLayout(2)) // create a 'New Project' button in the top left newProjectButton := widget.NewButton("New Project", func() { NewProjectDialog(window) }) // create a 'Open Project' button in the top right openProjectButton := widget.NewButton("Open Project", func() { OpenProjectDialog(window) }) // create a 'Open Recent' button in the bottom left var openRecentButton *widget.Button openRecentButton = widget.NewButton("Open Recent", func() { err := OpenRecentDialog(window) if err != nil { // if there are no projects, or another issue occurs, disable the button openRecentButton.Disable() } }) // create a 'Continue' button in the bottom right continueLastButton := widget.NewButton("Continue Last", func() {}) // TODO // add the buttons to the grid grid.Add(newProjectButton) grid.Add(openProjectButton) grid.Add(openRecentButton) grid.Add(continueLastButton) return grid } type OpenPage int // Enum for the different pages of the editor to allow changing the menu bar based on the page const ( HomePage OpenPage = iota EditorPage ) func CreateMainMenu(window fyne.Window) { openRecentMenu := fyne.NewMenuItem("Open Recent", func() { _ = OpenRecentDialog(window) }) projectMenu := fyne.NewMenu("Project") projects, err := ReadProjectInfo() if err != nil { //Show an error dialog dialog.ShowError(err, window) } if len(projects) == 0 { openRecentMenu.Disabled = true } else { openRecentMenu.Disabled = false //Create a list of all the projects as menu items of the child menu for i := 0; i < len(projects); i++ { notFound := false _, err := os.Stat(projects[i].Path) if err != nil { notFound = true } var newMenuItem *fyne.MenuItem if notFound { newMenuItem = fyne.NewMenuItem(projects[i].Name+" (Not Found)", func() { err = UpdateProjectInfo(projects[i]) if err != nil { //Show an error dialog dialog.ShowError(err, window) } CreateMainMenu(window) }) } else { newMenuItem = fyne.NewMenuItem(projects[i].Name, func() { err = OpenFromInfo(projects[i], window) if err != nil { dialog.ShowError(err, window) } }) } projectMenu.Items = append(projectMenu.Items, newMenuItem) } } openRecentMenu.ChildMenu = projectMenu mainMenu := fyne.NewMainMenu( fyne.NewMenu("File", fyne.NewMenuItem("Home", func() { grid := CreateMainGrid(window) continueButton := grid.(*fyne.Container).Objects[3].(*widget.Button) openRecentButton := grid.(*fyne.Container).Objects[2].(*widget.Button) err = InitButtons(window, continueButton, openRecentButton) if err != nil { dialog.ShowError(err, window) } else { CreateMainMenu(window) window.SetContent(grid) } }), fyne.NewMenuItem("New Project", func() { NewProjectDialog(window) }), fyne.NewMenuItem("Open Project", func() { OpenProjectDialog(window) }), openRecentMenu, fyne.NewMenuItem("Quit", func() { os.Exit(0) }), ), fyne.NewMenu("View", fyne.NewMenuItem("Full Screen", func() { window.SetFullScreen(!window.FullScreen()) }), fyne.NewMenuItem("Terminal", func() { KFLog.ShowDialog(window) }), ), fyne.NewMenu("Help", fyne.NewMenuItem("Documentation", func() { //TODO: Open the documentation page }), fyne.NewMenuItem("About", func() { //TODO: Open a dialog that shows the about information }), ), ) window.SetMainMenu(mainMenu) } func OpenRecentDialog(window fyne.Window) error { box := container.NewVBox() newDialog := dialog.NewCustom("Open Recent", "Close", box, window) projects, err := ReadProjectInfo() if err != nil { //Show an error dialog dialog.ShowError(err, window) return err } if len(projects) == 0 { //Show an error dialog dialog.ShowError(KFError.ErrNoProjects, window) return err } //Create a scrollable list of all the projects list := widget.NewList( func() int { return len(projects) + 1 }, func() fyne.CanvasObject { nameLabel := widget.NewLabel("Name") lastOpenedLabel := widget.NewLabel("Last Opened") //Set the style of the labels to bold nameLabel.TextStyle = fyne.TextStyle{Bold: true} lastOpenedLabel.TextStyle = fyne.TextStyle{Bold: true} // Align the labels to the center nameLabel.Alignment = fyne.TextAlignCenter lastOpenedLabel.Alignment = fyne.TextAlignCenter return container.NewHBox(widget.NewLabel("Name"), layout.NewSpacer(), widget.NewLabel("Last Opened")) }, func(id widget.ListItemID, item fyne.CanvasObject) { if id == 0 { return } //Set the first label to the project name and the second to the last opened date project := projects[id-1] _, err := os.Stat(project.Path) if err != nil { item.(*fyne.Container).Objects[0].(*widget.Label).SetText(project.Name + " (Not Found, click to hide)") } else { item.(*fyne.Container).Objects[0].(*widget.Label).SetText(project.Name) } item.(*fyne.Container).Objects[2].(*widget.Label).SetText(project.OpenDate.Format("01/02/2006 13:04")) }) list.OnSelected = func(id widget.ListItemID) { if id == 0 { return } project := projects[id-1] _, err := os.Stat(project.Path) if err != nil { err := UpdateProjectInfo(project) if err != nil { dialog.ShowError(err, window) return } projects, err = ReadProjectInfo() if err != nil { dialog.ShowError(err, window) return } list.Refresh() return } err = OpenFromInfo(projects[id-1], window) if err != nil { //Show an error dialog errDialog := dialog.NewError(err, window) errDialog.SetOnClosed(func() { newDialog.Hide() }) errDialog.Show() } else { newDialog.Hide() } } scrollBox := container.NewVScroll(list) scrollBox.SetMinSize(fyne.NewSize(400, 300)) box.Add(scrollBox) newDialog.Resize(fyne.NewSize(800, 600)) newDialog.Show() return nil } func OpenProjectDialog(window fyne.Window) { // Create a custom file dialog fileDialog := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil { log.Println("Error opening file") // Show an error dialog dialog.ShowError(err, window) return } //If the reader is nil, then the user canceled the dialog and nothing should happen if reader == nil { return } // Read the file file, err := os.ReadFile(reader.URI().Path()) if err != nil { log.Println("Error reading file") // Show an error dialog dialog.ShowError(err, window) return } // Deserialize the project project, err := Deserialize(file) if err != nil { log.Println("Error deserializing project") // Show an error dialog dialog.ShowError(err, window) return } err = project.Load(window) if err != nil { // Show an error dialog log.Println("Error loading project") dialog.ShowError(err, window) return } }, window) // Set filter for .KFProject files fileDialog.SetFilter(storage.NewExtensionFileFilter([]string{".KFProject"})) // Set the starting directory to the Projects directory projectURI, err := storage.ListerForURI(storage.NewFileURI("projects")) if err == nil { fileDialog.SetLocation(projectURI) } //Set the size of the dialog to 800x600 fileDialog.Resize(fyne.NewSize(800, 600)) // Show the dialog fileDialog.Show() } func NewProjectDialog(window fyne.Window) { //Create a dialog that allows the user to create a new project var err error box := container.NewVBox() projectDialog := dialog.NewCustom("New Project", "Cancel", container.NewVBox(layout.NewSpacer(), box, layout.NewSpacer()), window) projectInfo := KFInfo{} projectName := "" nameEntry := widget.NewEntry() nameValidationLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Italic: true}) nameEntry.SetPlaceHolder("Project Name") nameConfirmButton := widget.NewButton("Confirm", func() {}) authorEntry := widget.NewEntry() authorEntry.SetPlaceHolder("Author") authorConfirmButton := widget.NewButton("Create Project", func() {}) authorBackButton := widget.NewButton("Back", func() {}) //Add everything to the box box.Add(nameValidationLabel) box.Add(nameEntry) box.Add(nameConfirmButton) box.Add(authorEntry) box.Add(authorConfirmButton) box.Add(authorBackButton) authorConfirmButton.Hide() authorBackButton.Hide() authorEntry.Hide() nameEntry.Validator = func(s string) error { _, err = SanitizeProjectName(s) if err != nil { nameValidationLabel.SetText(err.Error()) } else { nameValidationLabel.SetText("") } return err } nameEntry.SetOnValidationChanged(func(e error) { if e != nil { nameConfirmButton.Disable() } else { nameConfirmButton.Enable() } }) nameConfirmButton.OnTapped = func() { _, err = SanitizeProjectName(nameEntry.Text) if err != nil { dialog.ShowError(err, window) nameEntry.SetValidationError(err) return } projectName = nameEntry.Text nameEntry.Hide() nameConfirmButton.Hide() nameValidationLabel.Hide() authorEntry.Show() authorConfirmButton.Show() authorBackButton.Show() } authorConfirmButton.OnTapped = func() { config := KFConfig.NewConfig(projectName, "0.0.1", authorEntry.Text, "Created with KemoForge") homeDir, err := os.UserHomeDir() if err != nil { log.Println("Error getting home directory") dialog.ShowError(err, window) return } kemoForgeDir := homeDir + "/Documents/KemoForge" projectInfo.Name = projectName projectsDir := fyne.CurrentApp().Preferences().StringWithFallback("projectDir", kemoForgeDir+"/projects") projectPath := projectsDir + "/" + projectName + "/" + projectName + ".KFProject" projectPath = filepath.Clean(projectPath) projectInfo.Path = projectPath projectInfo.OpenDate = time.Now() newProject := KFProject{ Info: projectInfo, Config: config, } err = newProject.Create(window) if err != nil { dialog.ShowError(err, window) return } log.Printf("Created project %s", newProject.Info.Name) projectDialog.Hide() err = newProject.Info.Load(window) if err != nil { return } } authorBackButton.OnTapped = func() { authorConfirmButton.Hide() authorBackButton.Hide() authorEntry.Hide() nameEntry.Show() nameConfirmButton.Show() nameValidationLabel.Show() } projectDialog.Resize(fyne.NewSize(400, 300)) projectDialog.Show() }