Manage Embedded Files In Levels
In the Level Editor, the "Level->Attached files" menu opens the FileSystem Window, which shows a paginated list of attached files and a "Delete" button to remove them. - Custom doodads which also exist locally can be deleted from the level's filesystem at any time. - If a custom doodad does NOT exist locally, and one of them is still placed somewhere within the level, you can not delete it. - You can't delete the custom wallpaper image IF the level is still using it. Change to a default wallpaper and then you can delete the custom wallpaper image.
This commit is contained in:
parent
7093b102e3
commit
c5e3fc297c
|
@ -77,6 +77,13 @@ var (
|
||||||
Color: render.Black,
|
Color: render.Black,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Small font
|
||||||
|
SmallFont = render.Text{
|
||||||
|
Size: 10,
|
||||||
|
Padding: 4,
|
||||||
|
Color: render.Black,
|
||||||
|
}
|
||||||
|
|
||||||
// Color for draggable doodad.
|
// Color for draggable doodad.
|
||||||
DragColor = render.MustHexColor("#0099FF")
|
DragColor = render.MustHexColor("#0099FF")
|
||||||
|
|
||||||
|
@ -101,6 +108,7 @@ var (
|
||||||
|
|
||||||
// Button styles, customized in init().
|
// Button styles, customized in init().
|
||||||
ButtonPrimary = style.DefaultButton
|
ButtonPrimary = style.DefaultButton
|
||||||
|
ButtonDanger = style.DefaultButton
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -109,4 +117,9 @@ func init() {
|
||||||
ButtonPrimary.Foreground = render.RGBA(255, 255, 254, 255)
|
ButtonPrimary.Foreground = render.RGBA(255, 255, 254, 255)
|
||||||
ButtonPrimary.HoverBackground = render.RGBA(0, 153, 255, 255)
|
ButtonPrimary.HoverBackground = render.RGBA(0, 153, 255, 255)
|
||||||
ButtonPrimary.HoverForeground = ButtonPrimary.Foreground
|
ButtonPrimary.HoverForeground = ButtonPrimary.Foreground
|
||||||
|
|
||||||
|
ButtonDanger.Background = render.RGBA(153, 30, 30, 255)
|
||||||
|
ButtonDanger.Foreground = render.RGBA(255, 255, 254, 255)
|
||||||
|
ButtonDanger.HoverBackground = render.RGBA(255, 30, 30, 255)
|
||||||
|
ButtonDanger.HoverForeground = ButtonPrimary.Foreground
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,27 +50,18 @@ type EditorUI struct {
|
||||||
paletteEditor *ui.Window
|
paletteEditor *ui.Window
|
||||||
layersWindow *ui.Window
|
layersWindow *ui.Window
|
||||||
publishWindow *ui.Window
|
publishWindow *ui.Window
|
||||||
|
filesystemWindow *ui.Window
|
||||||
|
|
||||||
// Palette window.
|
// Palette window.
|
||||||
Palette *ui.Window
|
Palette *ui.Window
|
||||||
PaletteTab *ui.Frame
|
PaletteTab *ui.Frame
|
||||||
DoodadTab *ui.Frame
|
DoodadTab *ui.Frame
|
||||||
|
|
||||||
// Doodad Palette window variables.
|
|
||||||
doodadSkip int
|
|
||||||
doodadRows []*ui.Frame
|
|
||||||
doodadPager *ui.Frame
|
|
||||||
doodadButtonSize int
|
|
||||||
doodadScroller *ui.Frame
|
|
||||||
|
|
||||||
// ToolBar window.
|
// ToolBar window.
|
||||||
activeTool string
|
activeTool string
|
||||||
|
|
||||||
// Draggable Doodad canvas.
|
// Draggable Doodad canvas.
|
||||||
DraggableActor *DraggableActor
|
DraggableActor *DraggableActor
|
||||||
|
|
||||||
// Palette variables.
|
|
||||||
paletteTab string // selected tab, Palette or Doodads
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEditorUI initializes the Editor UI.
|
// NewEditorUI initializes the Editor UI.
|
||||||
|
@ -568,6 +559,10 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
u.SetupPopups(u.d)
|
u.SetupPopups(u.d)
|
||||||
u.levelSettingsWindow.Show()
|
u.levelSettingsWindow.Show()
|
||||||
})
|
})
|
||||||
|
levelMenu.AddItem("Attached files", func() {
|
||||||
|
log.Info("Opening the FileSystem window")
|
||||||
|
u.OpenFileSystemWindow()
|
||||||
|
})
|
||||||
levelMenu.AddItemAccel("Playtest", "P", func() {
|
levelMenu.AddItemAccel("Playtest", "P", func() {
|
||||||
u.Scene.Playtest()
|
u.Scene.Playtest()
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level/publishing"
|
"git.kirsle.net/apps/doodle/pkg/level/publishing"
|
||||||
|
@ -56,6 +57,14 @@ func (u *EditorUI) OpenPublishWindow() {
|
||||||
u.publishWindow.Show()
|
u.publishWindow.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenPublishWindow opens the FileSystem window.
|
||||||
|
func (u *EditorUI) OpenFileSystemWindow() {
|
||||||
|
u.filesystemWindow.Hide()
|
||||||
|
u.filesystemWindow = nil
|
||||||
|
u.SetupPopups(u.d)
|
||||||
|
u.filesystemWindow.Show()
|
||||||
|
}
|
||||||
|
|
||||||
// SetupPopups preloads popup windows like the DoodadDropper.
|
// SetupPopups preloads popup windows like the DoodadDropper.
|
||||||
func (u *EditorUI) SetupPopups(d *Doodle) {
|
func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
// Common window configure function.
|
// Common window configure function.
|
||||||
|
@ -147,6 +156,54 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
configure(u.publishWindow)
|
configure(u.publishWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Level FileSystem Viewer.
|
||||||
|
if u.filesystemWindow == nil {
|
||||||
|
scene, _ := d.Scene.(*EditorScene)
|
||||||
|
|
||||||
|
u.filesystemWindow = windows.NewFileSystemWindow(windows.FileSystem{
|
||||||
|
Supervisor: u.Supervisor,
|
||||||
|
Engine: d.Engine,
|
||||||
|
Level: scene.Level,
|
||||||
|
|
||||||
|
OnDelete: func(filename string) bool {
|
||||||
|
// Check if it is an embedded doodad.
|
||||||
|
if strings.HasPrefix(filename, balance.EmbeddedDoodadsBasePath) {
|
||||||
|
// Check if we have the doodad installed locally.
|
||||||
|
if _, err := doodads.LoadFile(filepath.Base(filename)); err != nil {
|
||||||
|
modal.Alert(
|
||||||
|
"Cannot remove %s:\n\n"+
|
||||||
|
"This doodad is still in use by the level and does not\n"+
|
||||||
|
"exist on your local device, so can not be deleted.",
|
||||||
|
filepath.Base(filename),
|
||||||
|
).WithTitle("Cannot Remove Custom Doodad")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't delete the current wallpaper.
|
||||||
|
if filepath.Base(filename) == scene.Level.Wallpaper {
|
||||||
|
modal.Alert(
|
||||||
|
"This wallpaper is still in use as the level background, so can\n" +
|
||||||
|
"not be deleted. Change the wallpaper in the Page Settings window\n" +
|
||||||
|
"to one of the defaults and then you may remove this file from the level.",
|
||||||
|
).WithTitle("Cannot Remove Current Wallpaper")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok := scene.Level.DeleteFile(filename); !ok {
|
||||||
|
modal.Alert("Failed to remove file from level data!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
OnCancel: func() {
|
||||||
|
u.filesystemWindow.Hide()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
configure(u.filesystemWindow)
|
||||||
|
}
|
||||||
|
|
||||||
// Palette Editor.
|
// Palette Editor.
|
||||||
if u.paletteEditor == nil {
|
if u.paletteEditor == nil {
|
||||||
scene, _ := d.Scene.(*EditorScene)
|
scene, _ := d.Scene.(*EditorScene)
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (l *Level) DeleteFile(filename string) bool {
|
||||||
func (l *Level) ListFiles() []string {
|
func (l *Level) ListFiles() []string {
|
||||||
var files []string
|
var files []string
|
||||||
|
|
||||||
if l.Files == nil {
|
if l == nil || l.Files == nil {
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
260
pkg/windows/filesystem_window.go
Normal file
260
pkg/windows/filesystem_window.go
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
"git.kirsle.net/go/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileSystem window shows the file attachments to the current level.
|
||||||
|
type FileSystem struct {
|
||||||
|
// Settings passed in by doodle
|
||||||
|
Supervisor *ui.Supervisor
|
||||||
|
Engine render.Engine
|
||||||
|
Level *level.Level
|
||||||
|
|
||||||
|
OnDelete func(filename string) bool
|
||||||
|
OnCancel func()
|
||||||
|
|
||||||
|
// Private vars.
|
||||||
|
includeBuiltins bool // show built-in doodads in checkbox-list.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileSystemWindow initializes the window.
|
||||||
|
func NewFileSystemWindow(cfg FileSystem) *ui.Window {
|
||||||
|
var (
|
||||||
|
windowColor = render.RGBA(255, 255, 200, 255)
|
||||||
|
windowTitleColor = render.RGBA(255, 153, 0, 255)
|
||||||
|
windowWidth = 380
|
||||||
|
windowHeight = 360
|
||||||
|
page = 1
|
||||||
|
perPage = 6
|
||||||
|
pages = 1
|
||||||
|
maxPageButtons = 8
|
||||||
|
|
||||||
|
// columns and sizes to draw the doodad list
|
||||||
|
btnHeight = 14
|
||||||
|
)
|
||||||
|
|
||||||
|
window := ui.NewWindow("Attached Files")
|
||||||
|
window.SetButtons(ui.CloseButton)
|
||||||
|
window.ActiveTitleBackground = windowTitleColor // TODO: not working?
|
||||||
|
window.InactiveTitleBackground = windowTitleColor.Darken(60)
|
||||||
|
window.InactiveTitleForeground = render.Grey
|
||||||
|
window.Configure(ui.Config{
|
||||||
|
Width: windowWidth,
|
||||||
|
Height: windowHeight,
|
||||||
|
Background: windowColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Intro text
|
||||||
|
|
||||||
|
introFrame := ui.NewFrame("Intro Frame")
|
||||||
|
window.Pack(introFrame, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
lines := []struct {
|
||||||
|
Text string
|
||||||
|
Font render.Text
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Text: "About",
|
||||||
|
Font: balance.LabelFont,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "These are the files embedded inside your level data. When\n" +
|
||||||
|
"a level is Published, it can attach all of its custom doodads,\n" +
|
||||||
|
"wallpapers or other custom asset so that it easily plays on\n" +
|
||||||
|
"a different computer.",
|
||||||
|
Font: balance.UIFont,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "File Attachments",
|
||||||
|
Font: balance.LabelFont,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for n, row := range lines {
|
||||||
|
frame := ui.NewFrame(fmt.Sprintf("Intro Line %d", n))
|
||||||
|
introFrame.Pack(frame, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: row.Text,
|
||||||
|
Font: row.Font,
|
||||||
|
})
|
||||||
|
frame.Pack(label, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Attached files table.
|
||||||
|
fsFrame := ui.NewFrame("Doodads Frame")
|
||||||
|
fsFrame.Resize(render.Rect{
|
||||||
|
W: windowWidth,
|
||||||
|
H: btnHeight*perPage + 140,
|
||||||
|
})
|
||||||
|
window.Pack(fsFrame, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
var fileRows = []*ui.Frame{}
|
||||||
|
|
||||||
|
// Get the file attachments.
|
||||||
|
files := cfg.Level.ListFiles()
|
||||||
|
for _, file := range files {
|
||||||
|
file := file
|
||||||
|
row := ui.NewFrame("Row: " + file)
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: file,
|
||||||
|
Font: balance.UIFont,
|
||||||
|
})
|
||||||
|
row.Pack(label, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
Padding: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
delBtn := ui.NewButton("Delete: "+file, ui.NewLabel(ui.Label{
|
||||||
|
Text: "Delete",
|
||||||
|
Font: balance.SmallFont,
|
||||||
|
}))
|
||||||
|
delBtn.SetStyle(&balance.ButtonDanger)
|
||||||
|
delBtn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||||
|
if cfg.OnDelete != nil {
|
||||||
|
if cfg.OnDelete(file) {
|
||||||
|
row.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
cfg.Supervisor.Add(delBtn)
|
||||||
|
row.Place(delBtn, ui.Place{
|
||||||
|
Right: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
fileRows = append(fileRows, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, row := range fileRows {
|
||||||
|
fsFrame.Pack(row, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
PadY: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hide if too long for 1st page.
|
||||||
|
if i >= perPage {
|
||||||
|
row.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Buttons at bottom of window
|
||||||
|
|
||||||
|
bottomFrame := ui.NewFrame("Button Frame")
|
||||||
|
window.Pack(bottomFrame, ui.Pack{
|
||||||
|
Side: ui.S,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pager for the doodads.
|
||||||
|
pages = int(
|
||||||
|
math.Ceil(
|
||||||
|
float64(len(fileRows)) / float64(perPage),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
pagerOnChange := func(newPage, perPage int) {
|
||||||
|
page = newPage
|
||||||
|
log.Info("Page: %d, %d", page, perPage)
|
||||||
|
|
||||||
|
// Re-evaluate which rows are shown/hidden for the page we're on.
|
||||||
|
var (
|
||||||
|
minRow = (page - 1) * perPage
|
||||||
|
visible = 0
|
||||||
|
)
|
||||||
|
for i, row := range fileRows {
|
||||||
|
if visible >= perPage {
|
||||||
|
row.Hide()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < minRow {
|
||||||
|
row.Hide()
|
||||||
|
} else {
|
||||||
|
row.Show()
|
||||||
|
visible++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pager := ui.NewPager(ui.Pager{
|
||||||
|
Name: "Files List Pager",
|
||||||
|
Page: page,
|
||||||
|
Pages: pages,
|
||||||
|
PerPage: perPage,
|
||||||
|
MaxPageButtons: maxPageButtons,
|
||||||
|
Font: balance.MenuFont,
|
||||||
|
OnChange: pagerOnChange,
|
||||||
|
})
|
||||||
|
pager.Compute(cfg.Engine)
|
||||||
|
pager.Supervise(cfg.Supervisor)
|
||||||
|
bottomFrame.Place(pager, ui.Place{
|
||||||
|
Top: 20,
|
||||||
|
Left: 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
frame := ui.NewFrame("Button frame")
|
||||||
|
buttons := []struct {
|
||||||
|
label string
|
||||||
|
primary bool
|
||||||
|
f func()
|
||||||
|
}{
|
||||||
|
{"Close", false, func() {
|
||||||
|
if cfg.OnCancel != nil {
|
||||||
|
cfg.OnCancel()
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, button := range buttons {
|
||||||
|
button := button
|
||||||
|
|
||||||
|
btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{
|
||||||
|
Text: button.label,
|
||||||
|
Font: balance.MenuFont,
|
||||||
|
}))
|
||||||
|
if button.primary {
|
||||||
|
btn.SetStyle(&balance.ButtonPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||||
|
button.f()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
btn.Compute(cfg.Engine)
|
||||||
|
cfg.Supervisor.Add(btn)
|
||||||
|
|
||||||
|
frame.Pack(btn, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
PadX: 4,
|
||||||
|
Expand: true,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bottomFrame.Pack(frame, ui.Pack{
|
||||||
|
Side: ui.E,
|
||||||
|
Padding: 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
return window
|
||||||
|
}
|
|
@ -123,39 +123,7 @@ func NewPublishWindow(cfg Publish) *ui.Window {
|
||||||
PadX: 2,
|
PadX: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
// // Collect all the doodad names in use in this level.
|
// Collect the doodads named in this level.
|
||||||
// unique := map[string]interface{}{}
|
|
||||||
// names := []string{}
|
|
||||||
// if cfg.Level != nil {
|
|
||||||
// for _, actor := range cfg.Level.Actors {
|
|
||||||
// if _, ok := unique[actor.Filename]; ok {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// unique[actor.Filename] = nil
|
|
||||||
// names = append(names, actor.Filename)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// sort.Strings(names)
|
|
||||||
|
|
||||||
// // Identify which of the doodads are built-ins.
|
|
||||||
// usedBuiltins := []string{}
|
|
||||||
// builtinMap := map[string]interface{}{}
|
|
||||||
// usedCustom := []string{}
|
|
||||||
// if builtins, err := doodads.ListBuiltin(); err == nil {
|
|
||||||
// for _, filename := range builtins {
|
|
||||||
// if _, ok := unique[filename]; ok {
|
|
||||||
// usedBuiltins = append(usedBuiltins, filename)
|
|
||||||
// builtinMap[filename] = nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// for _, name := range names {
|
|
||||||
// if _, ok := builtinMap[name]; ok {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// usedCustom = append(usedCustom, name)
|
|
||||||
// }
|
|
||||||
usedBuiltins, usedCustom := publishing.GetUsedDoodadNames(cfg.Level)
|
usedBuiltins, usedCustom := publishing.GetUsedDoodadNames(cfg.Level)
|
||||||
|
|
||||||
// Helper function to draw the button rows for a set of doodads.
|
// Helper function to draw the button rows for a set of doodads.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user