Example #1
0
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")
	}
}
Example #2
0
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
}