diff --git a/assets/icons/1024.png b/assets/icons/1024.png new file mode 100644 index 0000000..7d0ea83 Binary files /dev/null and b/assets/icons/1024.png differ diff --git a/assets/icons/128.png b/assets/icons/128.png new file mode 100644 index 0000000..8a6718f Binary files /dev/null and b/assets/icons/128.png differ diff --git a/assets/icons/16.png b/assets/icons/16.png new file mode 100644 index 0000000..46428d2 Binary files /dev/null and b/assets/icons/16.png differ diff --git a/assets/icons/256.png b/assets/icons/256.png new file mode 100644 index 0000000..0b8e7b3 Binary files /dev/null and b/assets/icons/256.png differ diff --git a/assets/icons/32.png b/assets/icons/32.png new file mode 100644 index 0000000..2899bfc Binary files /dev/null and b/assets/icons/32.png differ diff --git a/assets/icons/512.png b/assets/icons/512.png new file mode 100644 index 0000000..ab086e3 Binary files /dev/null and b/assets/icons/512.png differ diff --git a/assets/icons/64.png b/assets/icons/64.png new file mode 100644 index 0000000..b98f729 Binary files /dev/null and b/assets/icons/64.png differ diff --git a/assets/icons/96.png b/assets/icons/96.png new file mode 100644 index 0000000..db9e054 Binary files /dev/null and b/assets/icons/96.png differ diff --git a/cmd/doodle/main.go b/cmd/doodle/main.go index 0e9d625..aa4d5b2 100644 --- a/cmd/doodle/main.go +++ b/cmd/doodle/main.go @@ -19,6 +19,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/apps/doodle/pkg/sound" + "git.kirsle.net/apps/doodle/pkg/sprites" "git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/go/render" "git.kirsle.net/go/render/sdl" @@ -166,6 +167,16 @@ func main() { game := doodle.New(c.Bool("debug"), engine) game.SetupEngine() + + // Set the app window icon. + if engine, ok := game.Engine.(*sdl.Renderer); ok { + if icon, err := sprites.LoadImage(game.Engine, balance.WindowIcon); err == nil { + engine.SetWindowIcon(icon.Image) + } else { + log.Error("Couldn't load WindowIcon (%s): %s", balance.WindowIcon, err) + } + } + if c.Bool("guitest") { game.Goto(&doodle.GUITestScene{}) } else if filename != "" { diff --git a/pkg/balance/theme.go b/pkg/balance/theme.go index 116587e..4468bf0 100644 --- a/pkg/balance/theme.go +++ b/pkg/balance/theme.go @@ -8,6 +8,8 @@ import ( // Theme and appearance variables. var ( + WindowIcon = "assets/icons/96.png" + // Title Screen Font TitleScreenFont = render.Text{ Size: 46, diff --git a/pkg/editor_ui_menubar.go b/pkg/editor_ui_menubar.go index 15d69ad..a38a8f7 100644 --- a/pkg/editor_ui_menubar.go +++ b/pkg/editor_ui_menubar.go @@ -78,7 +78,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { fileMenu.AddItemAccel("Open...", "Ctrl-O", u.Scene.MenuOpen) fileMenu.AddSeparator() - fileMenu.AddItem("Close "+drawingType, func() { + fileMenu.AddItem("Quit to menu", func() { u.Scene.ConfirmUnload(func() { d.Goto(&MainScene{}) }) diff --git a/pkg/enum/enum.go b/pkg/enum/enum.go index d754d9b..6b304c7 100644 --- a/pkg/enum/enum.go +++ b/pkg/enum/enum.go @@ -17,3 +17,11 @@ const ( DoodadExt = ".doodad" LevelPackExt = ".levelpack" ) + +// Responsive breakpoints for mobile friendly UIs. +const ( + ScreenWidthXSmall = 400 + ScreenWidthSmall = 600 + ScreenWidthMedium = 800 + ScreenWidthLarge = 1000 +) diff --git a/pkg/main_scene.go b/pkg/main_scene.go index b6c6f0b..87344c8 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -36,7 +36,6 @@ type MainScene struct { labelVersion *ui.Label labelHint *ui.Label frame *ui.Frame // Main button frame - btnRegister *ui.Button winRegister *ui.Window winSettings *ui.Window winLevelPacks *ui.Window @@ -125,47 +124,13 @@ func (s *MainScene) Setup(d *Doodle) error { s.updateButton.Hide() s.Supervisor.Add(s.updateButton) - // Register button. - s.btnRegister = ui.NewButton("Register", ui.NewLabel(ui.Label{ - Text: "Register Game", - Font: balance.LabelFont, - })) - s.btnRegister.SetStyle(&balance.ButtonPrimary) - s.btnRegister.Handle(ui.Click, func(ed ui.EventData) error { - if s.winRegister == nil { - cfg := windows.License{ - Supervisor: s.Supervisor, - Engine: d.Engine, - OnCancel: func() { - s.winRegister.Hide() - }, - } - cfg.OnLicensed = func() { - // License status has changed, reload the window! - if s.winRegister != nil { - s.winRegister.Hide() - } - s.winRegister = windows.MakeLicenseWindow(d.width, d.height, cfg) - } - - cfg.OnLicensed() - } - s.winRegister.Show() - return nil - }) - s.btnRegister.Compute(d.Engine) - s.Supervisor.Add(s.btnRegister) - - if license.IsRegistered() { - s.btnRegister.Hide() - } - // Main UI button frame. frame := ui.NewFrame("frame") s.frame = frame var buttons = []struct { Name string + If func() bool Func func() Style *style.Button }{ @@ -182,6 +147,9 @@ func (s *MainScene) Setup(d *Doodle) error { shmem.FlashError(err.Error()) } }, + OnCloseWindow: func() { + s.winLevelPacks.Hide() + }, }) } s.winLevelPacks.MoveTo(render.Point{ @@ -198,21 +166,14 @@ func (s *MainScene) Setup(d *Doodle) error { Style: &balance.ButtonBabyBlue, }, { - Name: "Create a Level", + Name: "New Drawing", Func: d.GotoNewMenu, Style: &balance.ButtonPink, }, { - Name: "Create a Doodad", - Func: func() { - d.NewDoodad(0) - }, - Style: &balance.ButtonPink, - }, - { - Name: "Edit a Drawing", + Name: "Edit Drawing", Func: d.GotoLoadMenu, - Style: &balance.ButtonPrimary, + Style: &balance.ButtonPink, }, { Name: "Settings", @@ -223,6 +184,34 @@ func (s *MainScene) Setup(d *Doodle) error { s.winSettings.Show() }, }, + { + Name: "Register", + If: func() bool { + return !license.IsRegistered() + }, + Func: func() { + if s.winRegister == nil { + cfg := windows.License{ + Supervisor: s.Supervisor, + Engine: d.Engine, + OnCancel: func() { + s.winRegister.Hide() + }, + } + cfg.OnLicensed = func() { + // License status has changed, reload the window! + if s.winRegister != nil { + s.winRegister.Hide() + } + s.winRegister = windows.MakeLicenseWindow(d.width, d.height, cfg) + } + + cfg.OnLicensed() + } + s.winRegister.Show() + }, + Style: &balance.ButtonPrimary, + }, } for _, button := range buttons { button := button @@ -406,12 +395,6 @@ func (s *MainScene) positionMenuPortrait(d *Doodle) { X: (d.width / 2) - (s.frame.Size().W / 2), Y: 260, }) - - // Register button. - s.btnRegister.MoveTo(render.Point{ - X: d.width - s.btnRegister.Size().W - 24, - Y: d.height - s.btnRegister.Size().H - 24, - }) } func (s *MainScene) positionMenuLandscape(d *Doodle) { @@ -447,13 +430,6 @@ func (s *MainScene) positionMenuLandscape(d *Doodle) { X: (col2.X+col2.W)/2 - (s.frame.Size().W / 2), Y: (d.height / 2) - (s.frame.Size().H / 2), }) - - // Register button to the top left. - // TODO: not ideal, move into main button list? - s.btnRegister.MoveTo(render.Point{ - X: 20, - Y: 20, - }) } // LoopLazyScroll gently scrolls the title screen demo level, called each Loop. @@ -560,9 +536,6 @@ func (s *MainScene) Draw(d *Doodle) error { s.frame.Compute(d.Engine) s.frame.Present(d.Engine, s.frame.Point()) - // Register button. - s.btnRegister.Present(d.Engine, s.btnRegister.Point()) - // Present supervised windows. s.Supervisor.Present(d.Engine) diff --git a/pkg/menu_scene.go b/pkg/menu_scene.go index cfba6b2..5706698 100644 --- a/pkg/menu_scene.go +++ b/pkg/menu_scene.go @@ -17,6 +17,9 @@ MenuScene holds the main dialog menu UIs for: * New Level * Open Level * Settings + +DEPRECATED: migrate these prompts into popup windows to appear +on the MainScene or elsewhere as wanted. */ type MenuScene struct { // Configuration. @@ -159,6 +162,9 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error { Level: lvl, }) }, + OnCreateNewDoodad: func(size int) { + d.NewDoodad(size) + }, OnCancel: func() { d.Goto(&MainScene{}) }, diff --git a/pkg/modal/modal.go b/pkg/modal/modal.go index 3188c7a..7bf8331 100644 --- a/pkg/modal/modal.go +++ b/pkg/modal/modal.go @@ -5,6 +5,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" + "git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" "git.kirsle.net/go/ui" @@ -87,10 +88,13 @@ func Draw() { // Center the window on screen. func center(win *ui.Window) { - var modSize = win.Size() + var ( + modSize = win.Size() + width, height = shmem.CurrentRenderEngine.WindowSize() + ) var moveTo = render.Point{ - X: (window.W / 2) - (modSize.W / 2), - Y: (window.H / 4) - (modSize.H / 2), + X: (width / 2) - (modSize.W / 2), + Y: (height / 4) - (modSize.H / 2), } win.MoveTo(moveTo) diff --git a/pkg/windows/add_edit_level.go b/pkg/windows/add_edit_level.go index 14f78e6..bbb7fbd 100644 --- a/pkg/windows/add_edit_level.go +++ b/pkg/windows/add_edit_level.go @@ -24,19 +24,60 @@ type AddEditLevel struct { // Callback functions. OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string) OnCreateNewLevel func(*level.Level) + OnCreateNewDoodad func(size int) OnReload func() OnCancel func() } // NewAddEditLevel initializes the window. func NewAddEditLevel(config AddEditLevel) *ui.Window { + // Default options. + var ( + title = "New Drawing" + ) + + // Given a level to edit? + if config.EditLevel != nil { + title = "Level Properties" + } + + window := ui.NewWindow(title) + window.SetButtons(ui.CloseButton) + window.Configure(ui.Config{ + Width: 400, + Height: 280, + Background: render.Grey, + }) + + // Tabbed UI for New Level or New Doodad. + tabframe := ui.NewTabFrame("Level Tabs") + if config.EditLevel != nil { + tabframe.SetTabsHidden(true) + } + window.Pack(tabframe, ui.Pack{ + Side: ui.N, + Fill: true, + Expand: true, + }) + + // Add the tabs. + config.setupLevelFrame(tabframe) + config.setupDoodadFrame(tabframe) + + tabframe.Supervise(config.Supervisor) + + window.Hide() + return window +} + +// Creates the Create/Edit Level tab ("index"). +func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) { // Default options. var ( newPageType = level.Bounded.String() newWallpaper = "notebook.png" paletteName = level.DefaultPaletteNames[0] isNewLevel = config.EditLevel == nil - title = "New Drawing" // Default text for the Palette drop-down for already-existing levels. // (needs --experimental feature flag to enable the UI). @@ -53,472 +94,582 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window { newPageType = config.EditLevel.PageType.String() newWallpaper = config.EditLevel.Wallpaper paletteName = textCurrentPalette - title = "Level Properties" } - window := ui.NewWindow(title) - window.SetButtons(ui.CloseButton) - window.Configure(ui.Config{ - Width: 400, - Height: 280, - Background: render.Grey, + frame := tf.AddTab("index", ui.NewLabel(ui.Label{ + Text: "New Level", + Font: balance.TabFont, + })) + + /****************** + * Frame for selecting Page Type + ******************/ + + typeFrame := ui.NewFrame("Page Type Options Frame") + frame.Pack(typeFrame, ui.Pack{ + Side: ui.N, + FillX: true, }) - { - frame := ui.NewFrame("New Level Frame") - window.Pack(frame, ui.Pack{ - Side: ui.N, - Fill: true, - Expand: true, - }) + label1 := ui.NewLabel(ui.Label{ + Text: "Page Type:", + Font: balance.LabelFont, + }) + typeFrame.Pack(label1, ui.Pack{ + Side: ui.W, + }) - /****************** - * Frame for selecting Page Type - ******************/ + type typeObj struct { + Name string + Value level.PageType + } + var types = []typeObj{ + {"Bounded", level.Bounded}, + {"Unbounded", level.Unbounded}, + {"No Negative Space", level.NoNegativeSpace}, + // {"Bordered (TODO)", level.Bordered}, + } - typeFrame := ui.NewFrame("Page Type Options Frame") - frame.Pack(typeFrame, ui.Pack{ - Side: ui.N, - FillX: true, - }) + typeBtn := ui.NewSelectBox("Type Select", ui.Label{ + Font: ui.MenuFont, + }) + typeFrame.Pack(typeBtn, ui.Pack{ + Side: ui.W, + Expand: true, + }) - label1 := ui.NewLabel(ui.Label{ - Text: "Page Type:", - Font: balance.LabelFont, - }) - typeFrame.Pack(label1, ui.Pack{ - Side: ui.W, - }) + for _, t := range types { + // TODO: Hide some options for the free version of the game. + // - At launch only Bounded and Bordered will be available + // in the shareware version. + // - For now, only hide Bordered as it's not yet implemented. + // -------- + // if balance.FreeVersion { + // if t.Value == level.Bordered { + // continue + // } + // } + typeBtn.AddItem(t.Name, t.Value, func() {}) + } - type typeObj struct { - Name string - Value level.PageType - } - var types = []typeObj{ - {"Bounded", level.Bounded}, - {"Unbounded", level.Unbounded}, - {"No Negative Space", level.NoNegativeSpace}, - // {"Bordered (TODO)", level.Bordered}, - } + // If editing an existing level, pre-select the right page type. + if config.EditLevel != nil { + typeBtn.SetValue(config.EditLevel.PageType) + } - typeBtn := ui.NewSelectBox("Type Select", ui.Label{ - Font: ui.MenuFont, - }) - typeFrame.Pack(typeBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) - - for _, t := range types { - // TODO: Hide some options for the free version of the game. - // - At launch only Bounded and Bordered will be available - // in the shareware version. - // - For now, only hide Bordered as it's not yet implemented. - // -------- - // if balance.FreeVersion { - // if t.Value == level.Bordered { - // continue - // } - // } - typeBtn.AddItem(t.Name, t.Value, func() {}) - } - - // If editing an existing level, pre-select the right page type. - if config.EditLevel != nil { - typeBtn.SetValue(config.EditLevel.PageType) - } - - typeBtn.Handle(ui.Change, func(ed ui.EventData) error { - if selection, ok := typeBtn.GetValue(); ok { - if pageType, ok := selection.Value.(level.PageType); ok { - newPageType = pageType.String() - config.OnChangePageTypeAndWallpaper(pageType, newWallpaper) - } - } - return nil - }) - - typeBtn.Supervise(config.Supervisor) - config.Supervisor.Add(typeBtn) - - /****************** - * Frame for selecting Bounded Level Limits. - ******************/ - - if config.EditLevel != nil { - boundsFrame := ui.NewFrame("Bounds Frame") - frame.Pack(boundsFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, - }) - - label := ui.NewLabel(ui.Label{ - Text: "Bounded limits:", - Font: balance.LabelFont, - }) - boundsFrame.Pack(label, ui.Pack{ - Side: ui.W, - PadY: 2, - }) - - var forms = []struct { - label string - number *int64 - }{ - { - label: "Width:", - number: &config.EditLevel.MaxWidth, - }, - { - label: "Height:", - number: &config.EditLevel.MaxHeight, - }, - } - for _, form := range forms { - form := form - label := ui.NewLabel(ui.Label{ - Text: form.label, - Font: ui.MenuFont, - }) - - var intvar = int(*form.number) - button := ui.NewButton(form.label, ui.NewLabel(ui.Label{ - IntVariable: &intvar, - Font: ui.MenuFont, - })) - button.Handle(ui.Click, func(ed ui.EventData) error { - shmem.Prompt("Enter new "+form.label+" ", func(answer string) { - if answer == "" { - return - } - - if i, err := strconv.Atoi(answer); err == nil { - *form.number = int64(i) - intvar = i - } - }) - return nil - }) - - config.Supervisor.Add(button) - - boundsFrame.Pack(label, ui.Pack{ - Side: ui.W, - PadX: 1, - }) - boundsFrame.Pack(button, ui.Pack{ - Side: ui.W, - PadX: 1, - }) + typeBtn.Handle(ui.Change, func(ed ui.EventData) error { + if selection, ok := typeBtn.GetValue(); ok { + if pageType, ok := selection.Value.(level.PageType); ok { + newPageType = pageType.String() + config.OnChangePageTypeAndWallpaper(pageType, newWallpaper) } } + return nil + }) - /****************** - * Frame for selecting Level Wallpaper - ******************/ + typeBtn.Supervise(config.Supervisor) + config.Supervisor.Add(typeBtn) - wpFrame := ui.NewFrame("Wallpaper Frame") - frame.Pack(wpFrame, ui.Pack{ + /****************** + * Frame for selecting Bounded Level Limits. + ******************/ + + if config.EditLevel != nil { + boundsFrame := ui.NewFrame("Bounds Frame") + frame.Pack(boundsFrame, ui.Pack{ Side: ui.N, FillX: true, PadY: 2, }) - label2 := ui.NewLabel(ui.Label{ - Text: "Wallpaper:", + label := ui.NewLabel(ui.Label{ + Text: "Bounded limits:", Font: balance.LabelFont, }) - wpFrame.Pack(label2, ui.Pack{ + boundsFrame.Pack(label, ui.Pack{ Side: ui.W, PadY: 2, }) - type wallpaperObj struct { - Name string - Value string - } - var wallpapers = []wallpaperObj{ - {"Notebook", "notebook.png"}, - {"Legal Pad", "legal.png"}, - {"Graph paper", "graph.png"}, - {"Dotted paper", "dots.png"}, - {"Blueprint", "blueprint.png"}, - {"Pure White", "white.png"}, - // {"Placemat", "placemat.png"}, - } - - wallBtn := ui.NewSelectBox("Wallpaper Select", ui.Label{ - Font: balance.MenuFont, - }) - wallBtn.AlwaysChange = true - wpFrame.Pack(wallBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) - - for _, t := range wallpapers { - wallBtn.AddItem(t.Name, t.Value, func() {}) - } - - // Add custom wallpaper options. - if balance.Feature.CustomWallpaper { - wallBtn.AddSeparator() - wallBtn.AddItem("Custom wallpaper...", balance.CustomWallpaperFilename, func() {}) - } - - // If editing a level, select the current wallpaper. - if config.EditLevel != nil { - wallBtn.SetValue(config.EditLevel.Wallpaper) - } - - wallBtn.Handle(ui.Change, func(ed ui.EventData) error { - if selection, ok := wallBtn.GetValue(); ok { - if filename, ok := selection.Value.(string); ok { - // Picking the Custom option? - if filename == balance.CustomWallpaperFilename { - filename, err := native.OpenFile("Choose a custom wallpaper:", "*.png *.jpg *.gif") - if err == nil { - b64data, err := wallpaper.FileToB64(filename) - if err != nil { - shmem.Flash("Error loading wallpaper: %s", err) - return nil - } - - // If editing a level, apply the update straight away. - if config.EditLevel != nil { - config.EditLevel.SetFile(balance.CustomWallpaperEmbedPath, []byte(b64data)) - newWallpaper = balance.CustomWallpaperFilename - - // Trigger the page type change to the caller. - if pageType, ok := level.PageTypeFromString(newPageType); ok { - config.OnChangePageTypeAndWallpaper(pageType, balance.CustomWallpaperFilename) - } - } else { - // Hold onto the new wallpaper until the level is created. - newWallpaper = balance.CustomWallpaperFilename - newWallpaperB64 = b64data - } - } - return nil - } - - if pageType, ok := level.PageTypeFromString(newPageType); ok { - config.OnChangePageTypeAndWallpaper(pageType, filename) - newWallpaper = filename - } - } - } - return nil - }) - - wallBtn.Supervise(config.Supervisor) - config.Supervisor.Add(wallBtn) - - /****************** - * Frame for picking a default color palette. - ******************/ - - // For new level or --experimental only. - if config.EditLevel == nil || balance.Feature.ChangePalette { - palFrame := ui.NewFrame("Palette Frame") - frame.Pack(palFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 4, - }) - - label3 := ui.NewLabel(ui.Label{ - Text: "Palette: ", - Font: balance.LabelFont, - }) - palFrame.Pack(label3, ui.Pack{ - Side: ui.W, - }) - - palBtn := ui.NewSelectBox("Palette Select", ui.Label{ - Font: balance.MenuFont, - }) - palBtn.AlwaysChange = true - - palFrame.Pack(palBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) - - if config.EditLevel != nil { - palBtn.AddItem(paletteName, paletteName, func() {}) - palBtn.AddSeparator() - } - - for _, palName := range level.DefaultPaletteNames { - palName := palName - palBtn.AddItem(palName, palName, func() {}) - } - - palBtn.Handle(ui.Change, func(ed ui.EventData) error { - if val, ok := palBtn.GetValue(); ok { - val2, _ := val.Value.(string) - paletteName = val2 - } - return nil - }) - - config.Supervisor.Add(palBtn) - palBtn.Supervise(config.Supervisor) - } - - /****************** - * Frame for giving the level a title. - ******************/ - - if config.EditLevel != nil { - label3 := ui.NewLabel(ui.Label{ - Text: "Metadata", - Font: balance.LabelFont, - }) - frame.Pack(label3, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - type metadataObj struct { - Label string - Binding *string - Update func(string) - } - var metaRows = []metadataObj{ - {"Title:", &config.EditLevel.Title, func(v string) { config.EditLevel.Title = v }}, - {"Author:", &config.EditLevel.Author, func(v string) { config.EditLevel.Author = v }}, - } - - for _, mr := range metaRows { - mr := mr - mrFrame := ui.NewFrame("Metadata " + mr.Label + "Frame") - frame.Pack(mrFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, - }) - - // The label. - mrLabel := ui.NewLabel(ui.Label{ - Text: mr.Label, - Font: balance.MenuFont, - }) - mrLabel.Configure(ui.Config{ - Width: 75, - }) - mrFrame.Pack(mrLabel, ui.Pack{ - Side: ui.W, - }) - - // The button. - mrButton := ui.NewButton(mr.Label, ui.NewLabel(ui.Label{ - TextVariable: mr.Binding, - Font: balance.MenuFont, - })) - mrButton.Handle(ui.Click, func(ed ui.EventData) error { - shmem.Prompt("Enter a new "+mr.Label, func(answer string) { - if answer != "" { - mr.Update(answer) - } - }) - return nil - }) - config.Supervisor.Add(mrButton) - mrFrame.Pack(mrButton, ui.Pack{ - Side: ui.W, - Expand: true, - PadX: 2, - }) - } - } - - /****************** - * Confirm/cancel buttons. - ******************/ - - bottomFrame := ui.NewFrame("Button Frame") - frame.Pack(bottomFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 8, - }) - - var buttons = []struct { - Label string - F func(ui.EventData) error + var forms = []struct { + label string + number *int64 }{ - {"Continue", func(ed ui.EventData) error { - shmem.Flash("Create new map with %s page type and %s wallpaper", newPageType, newWallpaper) - pageType, ok := level.PageTypeFromString(newPageType) - if !ok { - shmem.Flash("Invalid Page Type '%s'", newPageType) - return nil - } - - lvl := level.New() - lvl.Palette = level.DefaultPalettes[paletteName] - lvl.Wallpaper = newWallpaper - lvl.PageType = pageType - - // Was a custom wallpaper selected for our NEW level? - if lvl.Wallpaper == balance.CustomWallpaperFilename && len(newWallpaperB64) > 0 { - lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64)) - } - - config.OnCreateNewLevel(lvl) - return nil - }}, - - {"Cancel", func(ed ui.EventData) error { - config.OnCancel() - return nil - }}, - - // OK button is for editing an existing level. - {"OK", func(ed ui.EventData) error { - // If we're editing a level, did we select a new palette? - if paletteName != textCurrentPalette { - modal.Confirm( - "Are you sure you want to change the level palette?\n" + - "Existing pixels drawn on your level may change, and\n" + - "if the new palette is smaller, some pixels may be\n" + - "lost from your level. OK to continue?", - ).WithTitle("Change Level Palette").Then(func() { - // Install the new level palette. - config.EditLevel.ReplacePalette(level.DefaultPalettes[paletteName]) - if config.OnReload != nil { - config.OnReload() - } - }) - return nil - } - - config.OnCancel() - return nil - }}, + { + label: "Width:", + number: &config.EditLevel.MaxWidth, + }, + { + label: "Height:", + number: &config.EditLevel.MaxHeight, + }, } - for _, t := range buttons { - // If we're editing settings on an existing level, skip the Continue. - if (isNewLevel && t.Label == "OK") || (!isNewLevel && t.Label != "OK") { - continue - } - btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ - Text: t.Label, - Font: balance.MenuFont, + for _, form := range forms { + form := form + label := ui.NewLabel(ui.Label{ + Text: form.label, + Font: ui.MenuFont, + }) + + var intvar = int(*form.number) + button := ui.NewButton(form.label, ui.NewLabel(ui.Label{ + IntVariable: &intvar, + Font: ui.MenuFont, })) - btn.Handle(ui.Click, t.F) - config.Supervisor.Add(btn) - bottomFrame.Pack(btn, ui.Pack{ + button.Handle(ui.Click, func(ed ui.EventData) error { + shmem.Prompt("Enter new "+form.label+" ", func(answer string) { + if answer == "" { + return + } + + if i, err := strconv.Atoi(answer); err == nil { + *form.number = int64(i) + intvar = i + } + }) + return nil + }) + + config.Supervisor.Add(button) + + boundsFrame.Pack(label, ui.Pack{ Side: ui.W, - PadX: 4, - PadY: 8, + PadX: 1, + }) + boundsFrame.Pack(button, ui.Pack{ + Side: ui.W, + PadX: 1, }) } } - window.Hide() - return window + /****************** + * Frame for selecting Level Wallpaper + ******************/ + + wpFrame := ui.NewFrame("Wallpaper Frame") + frame.Pack(wpFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 2, + }) + + label2 := ui.NewLabel(ui.Label{ + Text: "Wallpaper:", + Font: balance.LabelFont, + }) + wpFrame.Pack(label2, ui.Pack{ + Side: ui.W, + PadY: 2, + }) + + type wallpaperObj struct { + Name string + Value string + } + var wallpapers = []wallpaperObj{ + {"Notebook", "notebook.png"}, + {"Legal Pad", "legal.png"}, + {"Graph paper", "graph.png"}, + {"Dotted paper", "dots.png"}, + {"Blueprint", "blueprint.png"}, + {"Pure White", "white.png"}, + // {"Placemat", "placemat.png"}, + } + + wallBtn := ui.NewSelectBox("Wallpaper Select", ui.Label{ + Font: balance.MenuFont, + }) + wallBtn.AlwaysChange = true + wpFrame.Pack(wallBtn, ui.Pack{ + Side: ui.W, + Expand: true, + }) + + for _, t := range wallpapers { + wallBtn.AddItem(t.Name, t.Value, func() {}) + } + + // Add custom wallpaper options. + if balance.Feature.CustomWallpaper { + wallBtn.AddSeparator() + wallBtn.AddItem("Custom wallpaper...", balance.CustomWallpaperFilename, func() {}) + } + + // If editing a level, select the current wallpaper. + if config.EditLevel != nil { + wallBtn.SetValue(config.EditLevel.Wallpaper) + } + + wallBtn.Handle(ui.Change, func(ed ui.EventData) error { + if selection, ok := wallBtn.GetValue(); ok { + if filename, ok := selection.Value.(string); ok { + // Picking the Custom option? + if filename == balance.CustomWallpaperFilename { + filename, err := native.OpenFile("Choose a custom wallpaper:", "*.png *.jpg *.gif") + if err == nil { + b64data, err := wallpaper.FileToB64(filename) + if err != nil { + shmem.Flash("Error loading wallpaper: %s", err) + return nil + } + + // If editing a level, apply the update straight away. + if config.EditLevel != nil { + config.EditLevel.SetFile(balance.CustomWallpaperEmbedPath, []byte(b64data)) + newWallpaper = balance.CustomWallpaperFilename + + // Trigger the page type change to the caller. + if pageType, ok := level.PageTypeFromString(newPageType); ok { + config.OnChangePageTypeAndWallpaper(pageType, balance.CustomWallpaperFilename) + } + } else { + // Hold onto the new wallpaper until the level is created. + newWallpaper = balance.CustomWallpaperFilename + newWallpaperB64 = b64data + } + } + return nil + } + + if pageType, ok := level.PageTypeFromString(newPageType); ok { + config.OnChangePageTypeAndWallpaper(pageType, filename) + newWallpaper = filename + } + } + } + return nil + }) + + wallBtn.Supervise(config.Supervisor) + config.Supervisor.Add(wallBtn) + + /****************** + * Frame for picking a default color palette. + ******************/ + + // For new level or --experimental only. + if config.EditLevel == nil || balance.Feature.ChangePalette { + palFrame := ui.NewFrame("Palette Frame") + frame.Pack(palFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 4, + }) + + label3 := ui.NewLabel(ui.Label{ + Text: "Palette: ", + Font: balance.LabelFont, + }) + palFrame.Pack(label3, ui.Pack{ + Side: ui.W, + }) + + palBtn := ui.NewSelectBox("Palette Select", ui.Label{ + Font: balance.MenuFont, + }) + palBtn.AlwaysChange = true + + palFrame.Pack(palBtn, ui.Pack{ + Side: ui.W, + Expand: true, + }) + + if config.EditLevel != nil { + palBtn.AddItem(paletteName, paletteName, func() {}) + palBtn.AddSeparator() + } + + for _, palName := range level.DefaultPaletteNames { + palName := palName + palBtn.AddItem(palName, palName, func() {}) + } + + palBtn.Handle(ui.Change, func(ed ui.EventData) error { + if val, ok := palBtn.GetValue(); ok { + val2, _ := val.Value.(string) + paletteName = val2 + } + return nil + }) + + config.Supervisor.Add(palBtn) + palBtn.Supervise(config.Supervisor) + } + + /****************** + * Frame for giving the level a title. + ******************/ + + if config.EditLevel != nil { + label3 := ui.NewLabel(ui.Label{ + Text: "Metadata", + Font: balance.LabelFont, + }) + frame.Pack(label3, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + type metadataObj struct { + Label string + Binding *string + Update func(string) + } + var metaRows = []metadataObj{ + {"Title:", &config.EditLevel.Title, func(v string) { config.EditLevel.Title = v }}, + {"Author:", &config.EditLevel.Author, func(v string) { config.EditLevel.Author = v }}, + } + + for _, mr := range metaRows { + mr := mr + mrFrame := ui.NewFrame("Metadata " + mr.Label + "Frame") + frame.Pack(mrFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 2, + }) + + // The label. + mrLabel := ui.NewLabel(ui.Label{ + Text: mr.Label, + Font: balance.MenuFont, + }) + mrLabel.Configure(ui.Config{ + Width: 75, + }) + mrFrame.Pack(mrLabel, ui.Pack{ + Side: ui.W, + }) + + // The button. + mrButton := ui.NewButton(mr.Label, ui.NewLabel(ui.Label{ + TextVariable: mr.Binding, + Font: balance.MenuFont, + })) + mrButton.Handle(ui.Click, func(ed ui.EventData) error { + shmem.Prompt("Enter a new "+mr.Label, func(answer string) { + if answer != "" { + mr.Update(answer) + } + }) + return nil + }) + config.Supervisor.Add(mrButton) + mrFrame.Pack(mrButton, ui.Pack{ + Side: ui.W, + Expand: true, + PadX: 2, + }) + } + } + + /****************** + * Confirm/cancel buttons. + ******************/ + + bottomFrame := ui.NewFrame("Button Frame") + frame.Pack(bottomFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 8, + }) + + var buttons = []struct { + Label string + F func(ui.EventData) error + }{ + {"Continue", func(ed ui.EventData) error { + shmem.Flash("Create new map with %s page type and %s wallpaper", newPageType, newWallpaper) + pageType, ok := level.PageTypeFromString(newPageType) + if !ok { + shmem.Flash("Invalid Page Type '%s'", newPageType) + return nil + } + + lvl := level.New() + lvl.Palette = level.DefaultPalettes[paletteName] + lvl.Wallpaper = newWallpaper + lvl.PageType = pageType + + // Was a custom wallpaper selected for our NEW level? + if lvl.Wallpaper == balance.CustomWallpaperFilename && len(newWallpaperB64) > 0 { + lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64)) + } + + if config.OnCreateNewLevel != nil { + config.OnCreateNewLevel(lvl) + } else { + shmem.FlashError("OnCreateNewLevel not attached") + } + return nil + }}, + + {"Cancel", func(ed ui.EventData) error { + config.OnCancel() + return nil + }}, + + // OK button is for editing an existing level. + {"OK", func(ed ui.EventData) error { + // If we're editing a level, did we select a new palette? + if paletteName != textCurrentPalette { + modal.Confirm( + "Are you sure you want to change the level palette?\n" + + "Existing pixels drawn on your level may change, and\n" + + "if the new palette is smaller, some pixels may be\n" + + "lost from your level. OK to continue?", + ).WithTitle("Change Level Palette").Then(func() { + // Install the new level palette. + config.EditLevel.ReplacePalette(level.DefaultPalettes[paletteName]) + if config.OnReload != nil { + config.OnReload() + } + }) + return nil + } + + config.OnCancel() + return nil + }}, + } + for _, t := range buttons { + // If we're editing settings on an existing level, skip the Continue. + if (isNewLevel && t.Label == "OK") || (!isNewLevel && t.Label != "OK") { + continue + } + btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ + Text: t.Label, + Font: balance.MenuFont, + })) + btn.Handle(ui.Click, t.F) + config.Supervisor.Add(btn) + bottomFrame.Pack(btn, ui.Pack{ + Side: ui.W, + PadX: 4, + PadY: 8, + }) + } +} + +// Creates the "New Doodad" frame. +func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) { + // Default options. + var ( + doodadSize = 64 + ) + + frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{ + Text: "New Doodad", + Font: balance.TabFont, + })) + + /****************** + * Frame for selecting Page Type + ******************/ + + typeFrame := ui.NewFrame("Doodad Options Frame") + frame.Pack(typeFrame, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + label1 := ui.NewLabel(ui.Label{ + Text: "Doodad sprite size (square):", + Font: balance.LabelFont, + }) + typeFrame.Pack(label1, ui.Pack{ + Side: ui.W, + }) + + // A selectbox to suggest some sizes or let the user enter a custom. + sizeBtn := ui.NewSelectBox("Size Select", ui.Label{ + Font: ui.MenuFont, + }) + typeFrame.Pack(sizeBtn, ui.Pack{ + Side: ui.W, + Expand: true, + }) + + for _, row := range []struct { + Name string + Value int + }{ + {"32", 32}, + {"64", 64}, + {"96", 96}, + {"128", 128}, + {"200", 200}, + {"256", 256}, + {"Custom...", 0}, + } { + row := row + sizeBtn.AddItem(row.Name, row.Value, func() {}) + } + + sizeBtn.SetValue(doodadSize) + sizeBtn.Handle(ui.Change, func(ed ui.EventData) error { + if selection, ok := sizeBtn.GetValue(); ok { + if size, ok := selection.Value.(int); ok { + if size == 0 { + shmem.Prompt("Enter a custom size for the doodad width and height: ", func(answer string) { + if a, err := strconv.Atoi(answer); err == nil && a > 0 { + doodadSize = a + } else { + shmem.FlashError("Doodad size should be a number greater than zero.") + } + }) + } else { + doodadSize = size + } + } + } + return nil + }) + + sizeBtn.Supervise(config.Supervisor) + config.Supervisor.Add(sizeBtn) + + /****************** + * Confirm/cancel buttons. + ******************/ + + bottomFrame := ui.NewFrame("Button Frame") + frame.Pack(bottomFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 8, + }) + + var buttons = []struct { + Label string + F func(ui.EventData) error + }{ + {"Continue", func(ed ui.EventData) error { + if config.OnCreateNewDoodad != nil { + config.OnCreateNewDoodad(doodadSize) + } else { + shmem.FlashError("OnCreateNewDoodad not attached") + } + return nil + }}, + + {"Cancel", func(ed ui.EventData) error { + config.OnCancel() + return nil + }}, + } + for _, t := range buttons { + btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ + Text: t.Label, + Font: balance.MenuFont, + })) + btn.Handle(ui.Click, t.F) + config.Supervisor.Add(btn) + bottomFrame.Pack(btn, ui.Pack{ + Side: ui.W, + PadX: 4, + PadY: 8, + }) + } } diff --git a/pkg/windows/levelpack_open.go b/pkg/windows/levelpack_open.go index d0853ae..3480646 100644 --- a/pkg/windows/levelpack_open.go +++ b/pkg/windows/levelpack_open.go @@ -17,7 +17,8 @@ type LevelPack struct { Engine render.Engine // Callback functions. - OnPlayLevel func(pack levelpack.LevelPack, level levelpack.Level) + OnPlayLevel func(pack levelpack.LevelPack, level levelpack.Level) + OnCloseWindow func() // Internal variables window *ui.Window @@ -65,6 +66,7 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { // And each LevelPack's screen is a pager for its Levels. tabFrame := ui.NewTabFrame("Screens Manager") tabFrame.SetTabsHidden(true) + tabFrame.Supervise(config.Supervisor) window.Pack(tabFrame, ui.Pack{ Side: ui.N, FillX: true, @@ -90,12 +92,23 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { config.makeDetailScreen(tab, width, height, packmap[filename]) } - // indexTab.Resize(render.Rect{ - // W: width-4, - // H: height-4, - // }) + // Close button. + if config.OnCloseWindow != nil { + closeBtn := ui.NewButton("Close Window", ui.NewLabel(ui.Label{ + Text: "Close", + Font: balance.MenuFont, + })) + closeBtn.Handle(ui.Click, func(ed ui.EventData) error { + config.OnCloseWindow() + return nil + }) + config.Supervisor.Add(closeBtn) + window.Place(closeBtn, ui.Place{ + Bottom: 15, + Center: true, + }) + } - tabFrame.Supervise(config.Supervisor) window.Supervise(config.Supervisor) window.Hide() return window diff --git a/pkg/windows/open_level_editor.go b/pkg/windows/open_level_editor.go index 8347886..34e968d 100644 --- a/pkg/windows/open_level_editor.go +++ b/pkg/windows/open_level_editor.go @@ -6,6 +6,7 @@ import ( "strings" "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/native" @@ -32,8 +33,18 @@ type OpenLevelEditor struct { func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window { var ( width, height = config.Engine.WindowSize() + columns = 4 ) + // Show fewer columns on smaller devices. + if width <= enum.ScreenWidthXSmall { + columns = 1 + } else if width <= enum.ScreenWidthSmall { + columns = 2 + } else if width <= enum.ScreenWidthMedium { + columns = 3 + } + window := ui.NewWindow("Open Drawing") window.Configure(ui.Config{ Width: int(float64(width) * 0.75), @@ -86,7 +97,9 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window { func(i int, lvl string) { btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{ Text: lvl, - Font: balance.MenuFont, + Font: balance.MenuFont.Update(render.Text{ + PadY: 2, + }), })) btn.Handle(ui.Click, func(ed ui.EventData) error { if config.LoadForPlay { @@ -103,7 +116,7 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window { Fill: true, }) - if i > 0 && (i+1)%4 == 0 { + if columns == 1 || i > 0 && (i+1)%columns == 0 { lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i)) frame.Pack(lvlRow, ui.Pack{ Side: ui.N,