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:
Noah 2021-06-13 16:03:32 -07:00
parent 7093b102e3
commit c5e3fc297c
6 changed files with 337 additions and 44 deletions

View File

@ -77,6 +77,13 @@ var (
Color: render.Black,
}
// Small font
SmallFont = render.Text{
Size: 10,
Padding: 4,
Color: render.Black,
}
// Color for draggable doodad.
DragColor = render.MustHexColor("#0099FF")
@ -101,6 +108,7 @@ var (
// Button styles, customized in init().
ButtonPrimary = style.DefaultButton
ButtonDanger = style.DefaultButton
)
func init() {
@ -109,4 +117,9 @@ func init() {
ButtonPrimary.Foreground = render.RGBA(255, 255, 254, 255)
ButtonPrimary.HoverBackground = render.RGBA(0, 153, 255, 255)
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
}

View File

@ -50,27 +50,18 @@ type EditorUI struct {
paletteEditor *ui.Window
layersWindow *ui.Window
publishWindow *ui.Window
filesystemWindow *ui.Window
// Palette window.
Palette *ui.Window
PaletteTab *ui.Frame
DoodadTab *ui.Frame
// Doodad Palette window variables.
doodadSkip int
doodadRows []*ui.Frame
doodadPager *ui.Frame
doodadButtonSize int
doodadScroller *ui.Frame
// ToolBar window.
activeTool string
// Draggable Doodad canvas.
DraggableActor *DraggableActor
// Palette variables.
paletteTab string // selected tab, Palette or Doodads
}
// NewEditorUI initializes the Editor UI.
@ -568,6 +559,10 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
u.SetupPopups(u.d)
u.levelSettingsWindow.Show()
})
levelMenu.AddItem("Attached files", func() {
log.Info("Opening the FileSystem window")
u.OpenFileSystemWindow()
})
levelMenu.AddItemAccel("Playtest", "P", func() {
u.Scene.Playtest()
})

View File

@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/level/publishing"
@ -56,6 +57,14 @@ func (u *EditorUI) OpenPublishWindow() {
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.
func (u *EditorUI) SetupPopups(d *Doodle) {
// Common window configure function.
@ -147,6 +156,54 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
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.
if u.paletteEditor == nil {
scene, _ := d.Scene.(*EditorScene)

View File

@ -54,7 +54,7 @@ func (l *Level) DeleteFile(filename string) bool {
func (l *Level) ListFiles() []string {
var files []string
if l.Files == nil {
if l == nil || l.Files == nil {
return files
}

View 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
}

View File

@ -123,39 +123,7 @@ func NewPublishWindow(cfg Publish) *ui.Window {
PadX: 2,
})
// // Collect all the doodad names in use 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)
// }
// Collect the doodads named in this level.
usedBuiltins, usedCustom := publishing.GetUsedDoodadNames(cfg.Level)
// Helper function to draw the button rows for a set of doodads.