package windows import ( "fmt" "math" "git.kirsle.net/SketchyMaze/doodle/pkg/balance" "git.kirsle.net/SketchyMaze/doodle/pkg/level" "git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/modal" "git.kirsle.net/SketchyMaze/doodle/pkg/pattern" "git.kirsle.net/SketchyMaze/doodle/pkg/shmem" "git.kirsle.net/SketchyMaze/doodle/pkg/sprites" "git.kirsle.net/SketchyMaze/doodle/pkg/usercfg" "git.kirsle.net/go/render" "git.kirsle.net/go/ui" "git.kirsle.net/go/ui/style" ) // PaletteEditor lets you customize the level palette in Edit Mode. type PaletteEditor struct { Supervisor *ui.Supervisor Engine render.Engine IsDoodad bool // you're editing a doodad instead of a level? // Pointer to the currently edited palette, be it // from a level or a doodad. EditPalette *level.Palette // Callback functions. OnChange func() OnAddColor func() OnCancel func() } // NewPaletteEditor initializes the window. func NewPaletteEditor(config PaletteEditor) *ui.Window { // Default options. var ( title = "Level Palette" buttonSize = balance.DoodadButtonSize columns = balance.DoodadDropperCols rows = []*ui.Frame{} // size of the popup window width = buttonSize * columns height = (buttonSize * balance.DoodadDropperRows) + 64 // account for button borders :( // Column sizes of the palette table. col1 = 15 // ID no. col2 = 24 // Color col5 = 24 // Texture col3 = 130 // Name col4 = 140 // Attributes // col5 = 150 // Delete // pagination values page = 1 perPage = 5 ) if config.IsDoodad { title = "Doodad Palette" } window := ui.NewWindow(title) window.SetButtons(ui.CloseButton) window.Configure(ui.Config{ Width: width, Height: height, Background: render.Grey, }) frame := ui.NewFrame("Window Body Frame") window.Pack(frame, ui.Pack{ Side: ui.N, Fill: true, Expand: true, }) // Draw the header row. headers := []struct { Name string Size int }{ {"ID", col1}, {"Col", col2}, {"Tex", col5}, {"Name", col3}, {"Attributes", col4}, // {"Delete", col5}, } header := ui.NewFrame("Palette Header") for _, col := range headers { labelFrame := ui.NewFrame(col.Name) labelFrame.Configure(ui.Config{ Width: col.Size, Height: 24, }) label := ui.NewLabel(ui.Label{ Text: col.Name, Font: balance.MenuFontBold, }) labelFrame.Pack(label, ui.Pack{ Side: ui.N, }) header.Pack(labelFrame, ui.Pack{ Side: ui.W, Padding: 2, }) } header.Compute(config.Engine) frame.Pack(header, ui.Pack{ Side: ui.N, }) // Draw the main table of Palette rows. if pal := config.EditPalette; pal != nil { for i, swatch := range pal.Swatches { var ( i = i swatch = swatch ) var idStr = fmt.Sprintf("%d", i) row := ui.NewFrame("Swatch " + idStr) rows = append(rows, row) // Off the end of the first page? if i >= perPage { row.Hide() } ////////////// // ID label. idLabel := ui.NewLabel(ui.Label{ Text: idStr + ".", Font: balance.MenuFont, }) idLabel.Configure(ui.Config{ Width: col1, Height: 24, }) ////////////// // Name button (click to rename the swatch) btnName := ui.NewButton("Name", ui.NewLabel(ui.Label{ TextVariable: &swatch.Name, })) btnName.Configure(ui.Config{ Width: col3, Height: 24, }) btnName.Handle(ui.Click, func(ed ui.EventData) error { shmem.Prompt("New swatch name ["+swatch.Name+"]: ", func(answer string) { log.Warn("Answer: %s", answer) if answer != "" { // Confirm it is unique. for j, exist := range pal.Swatches { if exist.Name == answer && i != j { modal.Alert("That name is already used by another color.") return } } swatch.Name = answer if config.OnChange != nil { config.OnChange() } } }) return nil }) config.Supervisor.Add(btnName) ////////////// // Color Choice button. btnColor := ui.NewButton("Color", ui.NewFrame("Color Frame")) setPaletteButtonColor(btnColor, swatch.Color) btnColor.Resize(render.NewRect(col2, 24)) btnColor.Handle(ui.Click, func(ed ui.EventData) error { // Open a ColorPicker widget. picker, err := ui.NewColorPicker(ui.ColorPicker{ Title: "Select a color", Supervisor: config.Supervisor, Engine: config.Engine, Color: swatch.Color, OnManualInput: func(callback func(render.Color)) { // Prompt the user to enter a hex color using the developer shell. shmem.Prompt(fmt.Sprintf( "New color in hex notation [%s]: ", swatch.Color.ToHex()), func(answer string) { if answer != "" { // XXX: pure white renders as invisible, fudge it a bit. if answer == "FFFFFF" { answer = "FFFFFE" } color, err := render.HexColor(answer) if err != nil { shmem.Flash("Error with that color code: %s", err) return } callback(color) } }) }, }) if err != nil { log.Error("Couldn't open ColorPicker: %s", err) return err } picker.Then(func(color render.Color) { swatch.Color = color setPaletteButtonColor(btnColor, color) if config.OnChange != nil { config.OnChange() } }) picker.Center(shmem.CurrentRenderEngine.WindowSize()) picker.Show() return nil }) config.Supervisor.Add(btnColor) ////////////// // Texture (pattern) option. selTexture := ui.NewSelectBox("Texture", ui.Label{ Font: balance.MenuFont, }) selTexture.Resize(render.NewRect(col5, 24)) for _, t := range pattern.Builtins { if t.Hidden && !usercfg.Current.ShowHiddenDoodads { continue } selTexture.AddItem(t.Name, t.Filename, func() {}) } selTexture.SetValue(swatch.Pattern) setImageOnSelect(selTexture, swatch.Pattern) selTexture.Handle(ui.Change, func(ed ui.EventData) error { if val, ok := selTexture.GetValue(); ok { filename, _ := val.Value.(string) setImageOnSelect(selTexture, filename) swatch.Pattern = filename if config.OnChange != nil { config.OnChange() } } return nil }) selTexture.Supervise(config.Supervisor) config.Supervisor.Add(selTexture) ////////////// // Attribute flags. attrFrame := ui.NewFrame("Attributes") attrFrame.Configure(ui.Config{ Width: col4, Height: 24, }) attributes := []struct { Label string Sprite string Var *bool }{ { Label: "Solid", Sprite: balance.AttrSolid, Var: &swatch.Solid, }, { Label: "Semi-Solid", Sprite: balance.AttrSemiSolid, Var: &swatch.SemiSolid, }, { Label: "Fire", Sprite: balance.AttrFire, Var: &swatch.Fire, }, { Label: "Water", Sprite: balance.AttrWater, Var: &swatch.Water, }, { Label: "Slippery", Sprite: balance.AttrSlippery, Var: &swatch.Slippery, }, } // Do not show in Doodad editing mode. if !config.IsDoodad { for _, attr := range attributes { attr := attr var child ui.Widget icon, err := sprites.LoadImage(config.Engine, attr.Sprite) if err != nil { log.Error("Sprite loading error: %s", err) child = ui.NewLabel(ui.Label{ Text: attr.Label, Font: balance.MenuFont, }) } else { child = icon } btn := ui.NewCheckButton(attr.Label, attr.Var, child) btn.Handle(ui.Click, func(ed ui.EventData) error { if config.OnChange != nil { config.OnChange() } return nil }) config.Supervisor.Add(btn) tt := ui.NewTooltip(btn, ui.Tooltip{ Text: attr.Label, Edge: ui.Bottom, }) tt.Supervise(config.Supervisor) attrFrame.Pack(btn, ui.Pack{ Side: ui.W, PadX: 1, }) } } ////////////// // Pack all the widgets. row.Pack(idLabel, ui.Pack{ Side: ui.W, PadX: 2, }) row.Pack(btnColor, ui.Pack{ Side: ui.W, PadX: 2, }) row.Pack(selTexture, ui.Pack{ Side: ui.W, PadX: 2, }) row.Pack(btnName, ui.Pack{ Side: ui.W, PadX: 2, }) row.Pack(attrFrame, ui.Pack{ Side: ui.W, PadX: 2, }) row.Compute(config.Engine) frame.Pack(row, ui.Pack{ Side: ui.N, PadY: 2, }) } } { /****************** * Confirm/cancel buttons. ******************/ bottomFrame := ui.NewFrame("Button Frame") frame.Pack(bottomFrame, ui.Pack{ Side: ui.S, FillX: true, }) // Pager for the doodads. pager := ui.NewPager(ui.Pager{ Name: "Palette Editor Pager", Page: page, Pages: int(math.Ceil( float64(len(rows)) / float64(perPage), )), PerPage: perPage, MaxPageButtons: 6, Font: balance.MenuFont, OnChange: func(newPage, perPage int) { page = newPage log.Info("Page: %d, %d", page, perPage) // Re-evaluate which rows are shown/hidden for this page. var ( minRow = (page - 1) * perPage visible = 0 ) for i, row := range rows { if visible >= perPage { row.Hide() continue } if i < minRow { row.Hide() } else { row.Show() visible++ } } }, }) pager.Compute(config.Engine) pager.Supervise(config.Supervisor) bottomFrame.Place(pager, ui.Place{ Top: 20, Left: 20, }) btnFrame := ui.NewFrame("Window Buttons") var buttons = []struct { Label string F func(ui.EventData) error }{ {"Add Color", func(ed ui.EventData) error { if config.OnAddColor != nil { config.OnAddColor() } if config.OnChange != nil { config.OnChange() } return nil }}, {"Close", func(ed ui.EventData) error { if config.OnCancel != nil { 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) btn.Compute(config.Engine) config.Supervisor.Add(btn) btnFrame.Pack(btn, ui.Pack{ Side: ui.W, PadX: 4, }) } bottomFrame.Place(btnFrame, ui.Place{ Top: 20, Right: 20, }) } window.Hide() return window } // Helper function to get the Tex (pattern) select box to // show the image by its filename... for both onChange and // initial render needs. func setImageOnSelect(sel *ui.SelectBox, filename string) { if img, err := pattern.GetImageCropped(filename, render.NewRect(24, 24)); err == nil { sel.SetImage(img) } else { sel.SetImage(nil) } } // Helper function to assign a palette "color button" color. func setPaletteButtonColor(btn *ui.Button, color render.Color) { btn.SetStyle(&style.Button{ Background: color, HoverBackground: color.Lighten(40), OutlineColor: render.Black, OutlineSize: 1, BorderStyle: style.BorderRaised, BorderSize: 2, }) }