func TestFunctions(t *testing.T) { inputBytes, _ := hex.DecodeString(inputModel) inputBuf := bytes.NewBuffer(inputBytes) inputRoot, err := bin.DeserializeModel(inputBuf, nil) if err != nil { t.Fatalf("rbxfile/bin failed to decode file: %s", err) } if !rbxclip.Set(nil) { t.Error("clear clipboard failed") } if rbxclip.Has() { t.Error("Has: expected no instances in clipboard") } if rbxclip.Get() != nil { t.Error("Get: expected no root") } if !rbxclip.Set(inputRoot) { t.Error("set clipboard failed") } if !rbxclip.Has() { t.Error("Has: expected instances in clipboard") } root := rbxclip.Get() if root == nil { t.Fatal("Get: expected root") } var buf bytes.Buffer if err := bin.SerializeModel(&buf, nil, root); err != nil { t.Fatal("failed to serialize output") } if !bytes.Equal(inputBytes, buf.Bytes()) { t.Fatal("output bytes do not equal input bytes") } }
func (c *EditorContext) Entering(ctxc *ContextController) ([]gxui.Control, bool) { c.ctxc = ctxc theme := ctxc.Theme() bubble := theme.CreateBubbleOverlay() tooltips := gxui.CreateToolTipController(bubble, ctxc.Driver()) //// Menu menu := theme.CreateLinearLayout() menu.SetDirection(gxui.LeftToRight) actionButton := func(name string, f func()) gxui.Button { button := CreateButton(theme, name) button.OnClick(func(e gxui.MouseEvent) { if e.Button != gxui.MouseButtonLeft { return } ctxc.Driver().Call(f) }) menu.AddChild(button) return button } saveAs := func(f func()) { exportCtx := &ExportContext{ File: c.session.File, Format: c.session.Format, Minified: c.session.Minified, } exportCtx.Finished = func(ok bool) { if ok { c.session.File = exportCtx.File c.session.Format = exportCtx.Format c.session.Minified = exportCtx.Minified c.updateWindowTitle(ctxc.Window()) if err := c.session.EncodeFile(); err != nil { ctxc.EnterContext(&AlertContext{ Title: "Error", Text: "Failed to save file:\n" + err.Error(), Buttons: ButtonsOKCancel, Finished: func(ok, _ bool) { if ok && f != nil { f() } }, }) return } if f != nil { f() } } } ctxc.EnterContext(exportCtx) } saveOrSaveAs := func(f func()) { if c.session == nil { return } if c.session.File == "" { saveAs(f) return } if err := c.session.EncodeFile(); err != nil { ctxc.EnterContext(&AlertContext{ Title: "Error", Text: "Failed to save file: " + err.Error(), Buttons: ButtonsOKCancel, Finished: func(ok, _ bool) { if ok && f != nil { f() } }, }) return } if f != nil { f() } } promptSaveCond := func(title string, cond bool, f func()) { if !cond { f() return } var text string if c.session.File == "" { text = "Would you like to save?" } else { text = "Would you like to save " + filepath.Base(c.session.File) + "?" } ctxc.EnterContext(&AlertContext{ Title: title, Text: text, Buttons: ButtonsYesNoCancel, Finished: func(ok, cancel bool) { if cancel { return } if ok { saveOrSaveAs(f) return } if f != nil { f() } }, }) } actionButton("New", func() { if c.session == nil { c.ChangeSession(NewSession("")) return } if Settings.Get("spawn_processes").(bool) { if err := SpawnProcess("--new"); err != nil { log.Printf("failed to spawn process: %s\n", err) } return } promptSaveCond("New File", c.session.Unsaved, func() { c.ChangeSession(NewSession("")) }, ) }) actionButton("Open", func() { promptSaveCond("Open File", c.session != nil && c.session.Unsaved && !Settings.Get("spawn_processes").(bool), func() { selectCtx := &FileSelectContext{ SelectedFile: "", Type: FileOpen, } selectCtx.Finished = func() { if selectCtx.SelectedFile == "" { return } if c.session != nil && c.session.Unsaved && Settings.Get("spawn_processes").(bool) { if err := SpawnProcess(selectCtx.SelectedFile); err != nil { log.Printf("failed to spawn process: %s\n", err) } return } c.ChangeSession(NewSession(selectCtx.SelectedFile)) } ctxc.EnterContext(selectCtx) }, ) }) actionButton("Settings", func() { ctxc.EnterContext(&SettingsContext{}) }) actionSave := actionButton("Save", func() { if c.session == nil { return } saveOrSaveAs(nil) }) actionSaveAs := actionButton("Save As", func() { if c.session == nil { return } saveAs(nil) }) actionClose := actionButton("Close", func() { if c.session == nil { return } promptSaveCond("Close File", c.session.Unsaved, func() { c.ChangeSession(nil, nil) }, ) }) //// Editor var updateSelection func(gxui.AdapterItem) c.tree = theme.CreateTree() c.tree.SetAdapter(&rootAdapter{ tooltips: tooltips, ctx: c, }) c.tree.OnKeyPress(func(e gxui.KeyboardEvent) { if !c.tree.HasFocus() { return } if e.Modifier == gxui.ModControl { switch e.Key { case gxui.KeyC: inst, _ := c.tree.Selected().(*rbxfile.Instance) if inst != nil { rbxclip.Set(&rbxfile.Root{Instances: []*rbxfile.Instance{inst}}) } case gxui.KeyV: if rbxclip.Has() { if r := rbxclip.Get(); r != nil { parent, _ := c.tree.Selected().(*rbxfile.Instance) var first *rbxfile.Instance ag := make(action.Group, len(r.Instances)) if parent == nil { for i, inst := range r.Instances { if first == nil { first = inst } ag[i] = cmd.AddRootInstance(c.session.Root, inst) } } else { for i, inst := range r.Instances { if first == nil { first = inst } ag[i] = cmd.SetParent(inst, parent) } } if err := c.session.Action.Do(ag); err != nil { ctxc.EnterContext(&AlertContext{ Title: "Error", Text: "Failed to add objects:\n" + err.Error(), Buttons: ButtonsOK, }) return } if first != nil { c.tree.Show(first) } } } case gxui.KeyX: fmt.Println("TODO: Set tree selection to clipboard and remove selection") } } }) propsLayout := theme.CreateLinearLayout() propsLayout.SetDirection(gxui.TopToBottom) propsLayout.SetHorizontalAlignment(gxui.AlignLeft) propsLayout.SetVerticalAlignment(gxui.AlignTop) propsButtons := theme.CreateLinearLayout() propsButtons.SetDirection(gxui.LeftToRight) propsButtons.SetHorizontalAlignment(gxui.AlignLeft) propsButtons.SetVerticalAlignment(gxui.AlignMiddle) propsLayout.AddChild(propsButtons) addChildButton := CreateButton(theme, "Add Child") addChildButton.SetVisible(false) addChildButton.OnClick(func(gxui.MouseEvent) { inst, _ := c.tree.Selected().(*rbxfile.Instance) if inst == nil { return } ctxc.EnterContext(&InstanceContext{ Finished: func(child *rbxfile.Instance, ok bool) { if !ok { return } if err := c.session.Action.Do(cmd.SetParent(child, inst)); err != nil { ctxc.EnterContext(&AlertContext{ Title: "Error", Text: "Failed to add instance:\n" + err.Error(), Buttons: ButtonsOK, }) return } if c.tree.Select(child) { c.tree.Show(child) } }, }) }) propsButtons.AddChild(addChildButton) addModelButton := CreateButton(theme, "Add Model") addModelButton.SetVisible(false) addModelButton.OnClick(func(gxui.MouseEvent) { inst, _ := c.tree.Selected().(*rbxfile.Instance) if inst == nil { return } loadModel(ctxc, func(children []*rbxfile.Instance) { ag := make(action.Group, len(children)) for i, child := range children { ag[i] = cmd.SetParent(child, inst) } if err := c.session.Action.Do(ag); err != nil { ctxc.EnterContext(&AlertContext{ Title: "Error", Text: "Failed to add objects:\n" + err.Error(), Buttons: ButtonsOK, }) return } }) }) propsButtons.AddChild(addModelButton) propPanel := property.CreatePanel(theme) propsLayout.AddChild(propPanel.Control()) splitter := theme.CreateSplitterLayout() splitter.SetOrientation(gxui.Horizontal) splitter.AddChild(c.tree) splitter.AddChild(propsLayout) //// Layout layout := theme.CreateLinearLayout() layout.SetDirection(gxui.TopToBottom) layout.AddChild(menu) layout.AddChild(splitter) if c.changeListener != nil { c.changeListener.Unlisten() } c.changeListener = c.OnChangeSession(func(err error) { if c.actionListener != nil { c.actionListener.Disconnect() c.actionListener = nil } if err != nil { ctxc.EnterContext(&AlertContext{ Title: "Error", Text: "Failed to open file: " + err.Error(), Buttons: ButtonsOK, }) return } actionSave.SetVisible(c.session != nil) actionSaveAs.SetVisible(c.session != nil) actionClose.SetVisible(c.session != nil) c.updateWindowTitle(ctxc.Window()) if updateSelection != nil { updateSelection(nil) } c.tree.Select(nil) var root *rbxfile.Root if c.session != nil { c.actionListener = c.session.Action.OnUpdate(func(...interface{}) { c.tree.Adapter().(*rootAdapter).DataChanged(false) }) propPanel.SetActionController(c.session.Action) root = c.session.Root } c.tree.SetAdapter(&rootAdapter{ Root: root, tooltips: tooltips, ctx: c, }) }) updateSelection = func(item gxui.AdapterItem) { inst, _ := item.(*rbxfile.Instance) addChildButton.SetVisible(inst != nil) addModelButton.SetVisible(inst != nil) propPanel.SetInstance(inst) } c.tree.OnSelectionChanged(updateSelection) c.ChangeSession(nil, nil) return []gxui.Control{ layout, bubble, }, true }