251 lines
5.9 KiB
Go
251 lines
5.9 KiB
Go
package shell
|
|
|
|
import (
|
|
pd "aego/editor/core/playdiff"
|
|
"slices"
|
|
"sort"
|
|
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/document"
|
|
"aego/editor/core/session"
|
|
"aego/editor/core/state"
|
|
"aego/platform"
|
|
"aego/platform/clip"
|
|
"aego/scene/sceneio"
|
|
)
|
|
|
|
const clipTTL = 0.5
|
|
|
|
type clipboard struct {
|
|
win platform.Window
|
|
now func() float64
|
|
mimes []string
|
|
at float64
|
|
fresh bool
|
|
}
|
|
|
|
func (c *clipboard) invalidate() {
|
|
c.fresh = false
|
|
}
|
|
|
|
func (c *clipboard) Text() string {
|
|
if cb, ok := c.win.(platform.Clipboard); ok {
|
|
return cb.ClipboardText()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c *clipboard) SetText(s string) {
|
|
c.invalidate()
|
|
if cb, ok := c.win.(platform.Clipboard); ok {
|
|
cb.SetClipboardText(s)
|
|
}
|
|
}
|
|
|
|
func (c *clipboard) SetData(mime string, data []byte, text string) bool {
|
|
c.invalidate()
|
|
if cb, ok := c.win.(platform.Clipboard); ok {
|
|
return cb.SetClipboardData(mime, data, text)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *clipboard) Data(mime string) ([]byte, bool) {
|
|
if cb, ok := c.win.(platform.Clipboard); ok {
|
|
return cb.ClipboardData(mime)
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (c *clipboard) SetItems(items []clip.Item) bool {
|
|
c.invalidate()
|
|
if cb, ok := c.win.(platform.ClipboardItems); ok {
|
|
return cb.SetClipboardItems(items)
|
|
}
|
|
if len(items) == 0 {
|
|
return false
|
|
}
|
|
text := ""
|
|
for _, it := range items {
|
|
if it.Mime == clip.MimeText {
|
|
text = string(it.Data)
|
|
}
|
|
}
|
|
return c.SetData(items[0].Mime, items[0].Data, text)
|
|
}
|
|
|
|
func (c *clipboard) Has(mime string) bool {
|
|
cb, ok := c.win.(platform.ClipboardItems)
|
|
if !ok {
|
|
data, has := c.Data(mime)
|
|
return has && len(data) > 0
|
|
}
|
|
if now := c.now(); !c.fresh || now-c.at > clipTTL {
|
|
c.mimes, c.at, c.fresh = cb.ClipboardMimes(), now, true
|
|
}
|
|
return slices.Contains(c.mimes, mime)
|
|
}
|
|
|
|
func (a *App) applyPlatform() {
|
|
win := a.eng.Window()
|
|
ui := a.ui
|
|
if cs, ok := win.(platform.Cursorer); ok {
|
|
cs.SetCursor(platform.CursorShape(ui.CursorShape()))
|
|
}
|
|
if mc, ok := win.(platform.MouseCapturer); ok {
|
|
if want := ui.Captured() != 0; want != a.captured {
|
|
mc.CaptureMouse(want)
|
|
a.captured = want
|
|
}
|
|
}
|
|
ti, ok := win.(platform.TextInputer)
|
|
if !ok {
|
|
return
|
|
}
|
|
wants, r, cursor := ui.WantsTextInput()
|
|
switch {
|
|
case wants && !a.textOn:
|
|
ti.StartTextInput()
|
|
a.textOn = true
|
|
case !wants && a.textOn:
|
|
ti.StopTextInput()
|
|
a.textOn = false
|
|
ui.SetComposition("", 0)
|
|
}
|
|
if wants {
|
|
ti.SetTextInputArea(r, cursor)
|
|
}
|
|
}
|
|
|
|
func (a *App) restoreState() {
|
|
root := a.opt.Project.Root
|
|
if a.opt.Supervised {
|
|
if h, ok := session.Take(root); ok {
|
|
a.applyState(h.State, h.Scenes)
|
|
a.ctx.Notify(context.LevelInfo, "Session restored after the native rebuild")
|
|
return
|
|
}
|
|
}
|
|
st, err := state.Load(root)
|
|
if err != nil {
|
|
a.log.Warn("editor state is unreadable", log.F("err", err.Error()))
|
|
}
|
|
a.applyState(st, nil)
|
|
}
|
|
|
|
func (a *App) applyState(st *state.Editor, scenes map[string][]byte) {
|
|
if st.Workspace != "" {
|
|
a.ws.Switch(st.Workspace)
|
|
}
|
|
a.diffMode = pd.ParseMode(st.PlayDiff)
|
|
set := &a.ctx.Settings
|
|
set.Tool = uint8(st.Tool)
|
|
set.Grid, set.Snap = st.Grid, st.Snap
|
|
set.SnapMove, set.SnapRotate, set.SnapScale = st.SnapMove, st.SnapRotate, st.SnapScale
|
|
if c := a.opt.Code; c != nil {
|
|
c.Autosave, c.Hints, c.Minimap, c.Rainbow, c.Block = st.Autosave, st.InlayHints, st.Minimap, st.RainbowBrackets, st.HighlightBlock
|
|
}
|
|
for _, k := range st.Collapsed {
|
|
a.ctx.Collapsed[k] = true
|
|
}
|
|
a.watchPanel.SetExprs(st.Watches)
|
|
var active *document.SceneDocument
|
|
var sel []ids.NodeID
|
|
for _, sd := range st.Docs {
|
|
var d *document.SceneDocument
|
|
var err error
|
|
if data, ok := scenes[sd.Path]; ok {
|
|
d, err = a.ctx.Docs.OpenData(sd.Path, data, true)
|
|
} else {
|
|
d, err = a.ctx.Docs.Open(sd.Path)
|
|
}
|
|
if err != nil {
|
|
a.log.Warn("cannot reopen scene", log.F("path", sd.Path), log.F("err", err.Error()))
|
|
continue
|
|
}
|
|
d.View.Camera = sd.Camera
|
|
if d.View.Camera.Zoom <= 0 {
|
|
d.View.Camera.Zoom = 64
|
|
}
|
|
d.View.Mode = document.ViewMode(sd.Mode)
|
|
d.View.Orbit = sd.Orbit
|
|
for _, id := range sd.Expanded {
|
|
d.View.Expanded[id] = true
|
|
}
|
|
if sd.Path == st.Active {
|
|
active, sel = d, sd.Selection
|
|
}
|
|
}
|
|
if active == nil {
|
|
return
|
|
}
|
|
a.ctx.Docs.SetActive(active.ID)
|
|
s := active.Scene()
|
|
alive := sel[:0]
|
|
for _, id := range sel {
|
|
if h := s.Handle(id); !h.IsZero() && s.IsAlive(h) {
|
|
alive = append(alive, id)
|
|
}
|
|
}
|
|
a.ctx.Select(alive...)
|
|
}
|
|
|
|
func (a *App) collect(all bool) *state.Editor {
|
|
set := a.ctx.Settings
|
|
st := state.Default()
|
|
st.Workspace = a.ws.Current().ID
|
|
st.PlayDiff = a.diffMode.String()
|
|
st.Tool = int(set.Tool)
|
|
st.Grid, st.Snap = set.Grid, set.Snap
|
|
st.SnapMove, st.SnapRotate, st.SnapScale = set.SnapMove, set.SnapRotate, set.SnapScale
|
|
if c := a.opt.Code; c != nil {
|
|
st.Autosave, st.InlayHints, st.Minimap, st.RainbowBrackets, st.HighlightBlock = c.Autosave, c.Hints, c.Minimap, c.Rainbow, c.Block
|
|
}
|
|
for k, closed := range a.ctx.Collapsed {
|
|
if closed {
|
|
st.Collapsed = append(st.Collapsed, k)
|
|
}
|
|
}
|
|
sort.Strings(st.Collapsed)
|
|
st.Watches = a.watchPanel.Exprs()
|
|
m := a.eng.Metrics()
|
|
st.WindowW, st.WindowH = m.LogicalW, m.LogicalH
|
|
active := a.ctx.Doc()
|
|
for _, d := range a.ctx.Docs.Docs() {
|
|
if !d.OnDisk && !all {
|
|
continue
|
|
}
|
|
sd := state.Doc{Path: d.Path, Camera: d.View.Camera, Mode: int(d.View.Mode), Orbit: d.View.Orbit}
|
|
for id, open := range d.View.Expanded {
|
|
if open {
|
|
sd.Expanded = append(sd.Expanded, id)
|
|
}
|
|
}
|
|
state.SortNodes(sd.Expanded)
|
|
if active != nil && d.ID == active.ID {
|
|
st.Active = d.Path
|
|
sd.Selection = a.ctx.Selection.Nodes(d.ID, nil)
|
|
}
|
|
st.Docs = append(st.Docs, sd)
|
|
}
|
|
return st
|
|
}
|
|
|
|
func (a *App) saveState() {
|
|
if err := state.Save(a.opt.Project.Root, a.collect(false)); err != nil {
|
|
a.ctx.Notify(context.LevelWarn, "cannot save the editor state: "+err.Error())
|
|
}
|
|
}
|
|
|
|
func (a *App) handoff() *session.Handoff {
|
|
h := &session.Handoff{State: a.collect(true), Scenes: map[string][]byte{}}
|
|
for _, d := range a.ctx.Docs.Docs() {
|
|
if d.Dirty() {
|
|
h.Scenes[d.Path] = sceneio.Save(d.Scene())
|
|
}
|
|
}
|
|
return h
|
|
}
|