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,
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
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,
|
||||
})
|
||||
|
||||
// // 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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user