Files

262 lines
6.2 KiB
Go

package shell
import (
"fmt"
"strconv"
"strings"
"aego/core/mathx"
"aego/editor/core/context"
"aego/editor/core/document"
"aego/platform/input"
"aego/ui/core"
"aego/ui/widgets"
)
type modalKind uint8
const (
modalRecovery modalKind = iota
modalConflict
modalSaveAs
modalQuit
modalClose
modalLock
modalAsk
)
type modal struct {
kind modalKind
doc document.ID
path string
removed bool
rec document.Recovery
lock document.LockInfo
input string
ask context.Question
vals []string
focus bool
}
func (a *App) push(m modal) {
if m.kind != modalAsk {
for _, o := range a.modals {
if o.kind == m.kind && o.doc == m.doc && o.rec.File == m.rec.File {
return
}
}
}
a.modals = append(a.modals, m)
}
func (a *App) pop() {
if len(a.modals) > 0 {
a.modals = a.modals[1:]
}
if len(a.modals) == 0 {
a.ui.CloseModal()
}
}
func mineSuggestion(p string) string {
return strings.TrimSuffix(p, document.SceneExt) + "-mine" + document.SceneExt
}
func (a *App) drawModals() {
if len(a.modals) == 0 {
return
}
ui := a.ui
docs := a.ctx.Docs
id := core.IDOf("editor.modal")
ui.OpenModal(id)
md := a.modals[0]
switch md.kind {
case modalRecovery:
msg := "An autosave of " + md.rec.Path + " from " + md.rec.Time.Format("2006-01-02 15:04") + " was found."
switch widgets.Confirm(ui, id, "Recover unsaved changes", msg, "Restore", "Discard", "Later") {
case 0:
if _, err := docs.RestoreRecovery(md.rec); err != nil {
a.ctx.Notify(context.LevelError, err.Error())
}
a.pop()
case 1:
docs.DiscardRecovery(md.rec)
a.pop()
case 2:
a.pop()
}
case modalConflict:
msg := md.path + " was changed on disk while it has unsaved edits."
first := "Reload"
if md.removed {
msg = md.path + " was deleted on disk."
first = "Close"
}
switch widgets.Confirm(ui, id, "Scene changed outside the editor", msg, first, "Keep mine", "Save As…") {
case 0:
if md.removed {
a.ctx.Selection.DropDoc(md.doc)
docs.Close(md.doc, true)
} else if err := docs.Reload(md.doc); err != nil {
a.ctx.Notify(context.LevelError, err.Error())
} else {
a.ctx.PruneSelectionOf(md.doc)
}
a.pop()
case 1:
docs.KeepMine(md.doc)
a.pop()
case 2:
a.modals[0] = modal{kind: modalSaveAs, doc: md.doc, input: mineSuggestion(md.path)}
}
case modalSaveAs:
a.drawSaveAs(id)
case modalQuit:
msg := strconv.Itoa(docs.DirtyCount()) + " scene(s) have unsaved changes."
switch widgets.Confirm(ui, id, "Quit Aego Editor", msg, "Save All", "Discard", "Cancel") {
case 0:
if err := docs.SaveAll(); err != nil {
a.ctx.Notify(context.LevelError, err.Error())
a.pop()
return
}
a.pop()
a.quitting = true
a.eng.Quit()
case 1:
a.discard = true
a.pop()
a.quitting = true
a.eng.Quit()
case 2:
a.pop()
}
case modalClose:
switch widgets.Confirm(ui, id, "Close scene", md.path+" has unsaved changes.", "Save", "Discard", "Cancel") {
case 0:
if err := docs.Save(md.doc); err != nil {
a.ctx.Notify(context.LevelError, err.Error())
} else {
a.ctx.Selection.DropDoc(md.doc)
docs.Close(md.doc, false)
}
a.pop()
case 1:
a.ctx.Selection.DropDoc(md.doc)
docs.Close(md.doc, true)
a.pop()
case 2:
a.pop()
}
case modalLock:
msg := fmt.Sprintf("editor.lock belongs to pid %d on %s (%s). Another editor may be running, or the previous one crashed.",
md.lock.PID, md.lock.Host, md.lock.Time.Format("2006-01-02 15:04"))
if widgets.Confirm(ui, id, "Project may be open elsewhere", msg, "Continue") == 0 {
a.pop()
}
case modalAsk:
if len(md.ask.Fields) > 0 {
a.drawPrompt(id)
return
}
if c := widgets.Confirm(ui, id, md.ask.Title, md.ask.Message, md.ask.Buttons...); c >= 0 {
a.pop()
if md.ask.Then != nil {
md.ask.Then(c)
}
}
}
}
func (a *App) drawPrompt(id core.ID) {
ui := a.ui
th := &ui.Theme
md := &a.modals[0]
q := md.ask
for len(md.vals) < len(q.Fields) {
md.vals = append(md.vals, "")
}
rows := 2 + 2*len(q.Fields)
if q.Message != "" {
rows++
}
sz := ui.Size()
w := min(float32(460), sz.X-40)
r := mathx.Rect{X: (sz.X - w) / 2, Y: sz.Y / 3, W: w, H: float32(rows)*(th.RowHeight+th.Spacing) + th.Padding*4}
if !ui.BeginModal(id, r) {
return
}
widgets.Label(ui, q.Title)
if q.Message != "" {
widgets.LabelDim(ui, q.Message)
}
base := ui.ID("prompt")
submit, escape := false, false
for i, f := range q.Fields {
widgets.LabelDim(ui, f)
fid := core.IDSeq(base, i)
if md.focus && i == 0 {
ui.SetFocus(fid)
ui.EditBuffer(fid, md.vals[i]).SelectAll()
md.focus = false
}
if ui.Focus() == fid {
submit = submit || ui.KeyPressed(input.KeyEnter) || ui.KeyPressed(input.KeyKPEnter)
escape = escape || ui.KeyPressed(input.KeyEscape)
}
widgets.TextFieldRect(ui, fid, ui.Cursor().Next(0), &md.vals[i], true)
}
row := ui.Cursor().Next(0)
okLabel, cancelLabel := "OK", "Cancel"
if len(q.Buttons) > 0 {
okLabel = q.Buttons[0]
}
if len(q.Buttons) > 1 {
cancelLabel = q.Buttons[1]
}
ok := widgets.ButtonRect(ui, ui.ID("prompt.ok"), mathx.Rect{X: row.X + row.W - 184, Y: row.Y, W: 88, H: row.H}, okLabel)
cancel := widgets.ButtonRect(ui, ui.ID("prompt.cancel"), mathx.Rect{X: row.X + row.W - 90, Y: row.Y, W: 88, H: row.H}, cancelLabel)
ui.EndModal()
switch {
case ok || submit:
vals := md.vals
a.pop()
if q.Submit != nil {
q.Submit(vals)
}
case cancel || escape:
a.pop()
}
}
func (a *App) drawSaveAs(id core.ID) {
ui := a.ui
th := &ui.Theme
sz := ui.Size()
w := min(float32(460), sz.X-40)
r := mathx.Rect{X: (sz.X - w) / 2, Y: sz.Y / 3, W: w, H: th.RowHeight*4 + th.Padding*4}
if !ui.BeginModal(id, r) {
return
}
widgets.Label(ui, "Save scene as")
in := a.modals[0].input
widgets.TextField(ui, ui.ID("saveas.path"), &in)
a.modals[0].input = in
row := ui.Cursor().Next(0)
ok := widgets.ButtonRect(ui, ui.ID("saveas.ok"), mathx.Rect{X: row.X + row.W - 184, Y: row.Y, W: 88, H: row.H}, "Save")
cancel := widgets.ButtonRect(ui, ui.ID("saveas.cancel"), mathx.Rect{X: row.X + row.W - 90, Y: row.Y, W: 88, H: row.H}, "Cancel")
ui.EndModal()
doc := a.modals[0].doc
switch {
case ok:
if err := a.ctx.Docs.SaveAs(doc, in); err != nil {
a.ctx.Notify(context.LevelError, err.Error())
return
}
a.pop()
case cancel:
a.pop()
}
}