diff --git a/assets/sprites/actor-tool.png b/assets/sprites/actor-tool.png new file mode 100644 index 0000000..c7135bb Binary files /dev/null and b/assets/sprites/actor-tool.png differ diff --git a/assets/sprites/line-tool.png b/assets/sprites/line-tool.png new file mode 100644 index 0000000..460aff0 Binary files /dev/null and b/assets/sprites/line-tool.png differ diff --git a/assets/sprites/link-tool.png b/assets/sprites/link-tool.png new file mode 100644 index 0000000..bcd27ac Binary files /dev/null and b/assets/sprites/link-tool.png differ diff --git a/assets/sprites/new-button.png b/assets/sprites/new-button.png new file mode 100644 index 0000000..83d8db5 Binary files /dev/null and b/assets/sprites/new-button.png differ diff --git a/assets/sprites/pencil-tool.png b/assets/sprites/pencil-tool.png new file mode 100644 index 0000000..8dd76f6 Binary files /dev/null and b/assets/sprites/pencil-tool.png differ diff --git a/assets/sprites/rect-tool.png b/assets/sprites/rect-tool.png new file mode 100644 index 0000000..00afb2c Binary files /dev/null and b/assets/sprites/rect-tool.png differ diff --git a/lib/ui/image.go b/lib/ui/image.go index 8530f51..c6ef2b0 100644 --- a/lib/ui/image.go +++ b/lib/ui/image.go @@ -41,6 +41,13 @@ func NewImage(c Image) *Image { return w } +// ImageFromTexture creates an Image from a texture. +func ImageFromTexture(tex render.Texturer) *Image { + return &Image{ + texture: tex, + } +} + // OpenImage initializes an Image with a given file name. // // The file extension is important and should be a supported ImageType. diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index 64f6c43..04e9d22 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -41,6 +41,7 @@ type EditorUI struct { Workspace *ui.Frame MenuBar *ui.Frame StatusBar *ui.Frame + ToolBar *ui.Frame PlayButton *ui.Button // Palette window. @@ -48,6 +49,9 @@ type EditorUI struct { PaletteTab *ui.Frame DoodadTab *ui.Frame + // ToolBar window. + activeTool *string + // Draggable Doodad canvas. DraggableActor *DraggableActor @@ -67,6 +71,10 @@ func NewEditorUI(d *Doodle, s *EditorScene) *EditorUI { StatusScrollText: "Hello world", } + // Default tool in the toolbox. + activeTool := drawtool.PencilTool.String() + u.activeTool = &activeTool + // Bind the StatusBoxes arrays to the text variables. u.StatusBoxes = []*string{ &u.StatusMouseText, @@ -78,6 +86,7 @@ func NewEditorUI(d *Doodle, s *EditorScene) *EditorUI { u.Canvas = u.SetupCanvas(d) u.MenuBar = u.SetupMenuBar(d) u.StatusBar = u.SetupStatusBar(d) + u.ToolBar = u.SetupToolbar(d) u.Workspace = u.SetupWorkspace(d) // important that this is last! u.PlayButton = ui.NewButton("Play", ui.NewLabel(ui.Label{ @@ -147,16 +156,31 @@ func (u *EditorUI) Resized(d *Doodle) { u.Palette.Compute(d.Engine) } + var innerHeight = int32(u.d.height) - u.MenuBar.Size().H - u.StatusBar.Size().H + + // Tool Bar. + { + u.ToolBar.Configure(ui.Config{ + Width: toolbarWidth, + Height: innerHeight, + }) + u.ToolBar.MoveTo(render.NewPoint( + 0, + u.MenuBar.BoxSize().H, + )) + u.ToolBar.Compute(d.Engine) + } + // Position the workspace around with the other widgets. { frame := u.Workspace frame.MoveTo(render.NewPoint( - 0, + u.ToolBar.Size().W, u.MenuBar.Size().H, )) frame.Resize(render.NewRect( - int32(d.width)-u.Palette.Size().W, + int32(d.width)-u.Palette.Size().W-u.ToolBar.Size().W, int32(d.height)-u.MenuBar.Size().H-u.StatusBar.Size().H, )) frame.Compute(d.Engine) @@ -227,9 +251,14 @@ func (u *EditorUI) Loop(ev *events.State) error { } // Recompute widgets. + // Explanation: if I don't, the UI packing algorithm somehow causes widgets + // to creep away every frame and fly right off the screen. For example the + // ToolBar's buttons would start packed at the top of the bar but then just + // move themselves every frame downward and away. u.MenuBar.Compute(u.d.Engine) u.StatusBar.Compute(u.d.Engine) u.Palette.Compute(u.d.Engine) + u.ToolBar.Compute(u.d.Engine) // Only forward events to the Canvas if the UI hasn't stopped them. if !stopPropagation { @@ -249,6 +278,7 @@ func (u *EditorUI) Present(e render.Engine) { u.Palette.Present(e, u.Palette.Point()) u.MenuBar.Present(e, u.MenuBar.Point()) u.StatusBar.Present(e, u.StatusBar.Point()) + u.ToolBar.Present(e, u.ToolBar.Point()) u.Workspace.Present(e, u.Workspace.Point()) u.PlayButton.Present(e, u.PlayButton.Point()) diff --git a/pkg/editor_ui_doodad.go b/pkg/editor_ui_doodad.go index aba37d9..790b628 100644 --- a/pkg/editor_ui_doodad.go +++ b/pkg/editor_ui_doodad.go @@ -51,35 +51,6 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra frame.SetBackground(render.RGBA(0, 153, 255, 153)) - // Toolbar on top of the Doodad panel. - toolbar := ui.NewFrame("Doodad Palette Toolbar") - toolbar.Configure(ui.Config{ - Background: render.Grey, - BorderSize: 2, - BorderStyle: ui.BorderRaised, - Height: 24, - }) - { - // Link button. - linkButton := ui.NewButton("Link", ui.NewLabel(ui.Label{ - Text: "Link Doodads", - })) - linkButton.Handle(ui.Click, func(p render.Point) { - u.Canvas.LinkStart() - u.d.Flash("Click on the first Doodad to link to another one.") - }) - u.Supervisor.Add(linkButton) - - toolbar.Pack(linkButton, ui.Pack{ - Anchor: ui.N, - FillX: true, - }) - } - frame.Pack(toolbar, ui.Pack{ - Anchor: ui.N, - Fill: true, - }) - // Pager buttons on top of the doodad list. pager := ui.NewFrame("Doodad Pager") pager.SetBackground(render.RGBA(255, 0, 0, 20)) // TODO: if I don't set a background color, diff --git a/pkg/editor_ui_palette.go b/pkg/editor_ui_palette.go index fd5321d..a423b6f 100644 --- a/pkg/editor_ui_palette.go +++ b/pkg/editor_ui_palette.go @@ -4,8 +4,6 @@ import ( "git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/ui" "git.kirsle.net/apps/doodle/pkg/balance" - "git.kirsle.net/apps/doodle/pkg/drawtool" - "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/log" ) @@ -19,46 +17,6 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window { BorderColor: balance.WindowBorder, }) - // Frame that holds the tab buttons in Level Edit mode. - tabFrame := ui.NewFrame("Palette Tabs") - for _, name := range []string{"Palette", "Doodads"} { - if u.paletteTab == "" { - u.paletteTab = name - } - - tab := ui.NewRadioButton("Palette Tab", &u.paletteTab, name, ui.NewLabel(ui.Label{ - Text: name, - })) - tab.Handle(ui.Click, func(p render.Point) { - if u.paletteTab == "Palette" { - u.Canvas.Tool = drawtool.PencilTool - u.PaletteTab.Show() - u.DoodadTab.Hide() - } else { - u.Canvas.Tool = drawtool.ActorTool - u.PaletteTab.Hide() - u.DoodadTab.Show() - } - window.Compute(d.Engine) - }) - u.Supervisor.Add(tab) - tabFrame.Pack(tab, ui.Pack{ - Anchor: ui.W, - Fill: true, - Expand: true, - }) - } - window.Pack(tabFrame, ui.Pack{ - Anchor: ui.N, - Fill: true, - PadY: 4, - }) - - // Only show the tab frame in Level drawing mode! - if u.Scene.DrawingType != enum.LevelDrawing { - tabFrame.Hide() - } - // Doodad frame. { frame, err := u.setupDoodadFrame(d.Engine, window) diff --git a/pkg/editor_ui_toolbar.go b/pkg/editor_ui_toolbar.go new file mode 100644 index 0000000..5fe59d1 --- /dev/null +++ b/pkg/editor_ui_toolbar.go @@ -0,0 +1,123 @@ +package doodle + +import ( + "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/apps/doodle/lib/ui" + "git.kirsle.net/apps/doodle/pkg/drawtool" + "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/sprites" +) + +// Width of the toolbar frame. +var toolbarWidth int32 = 44 // 38px button (32px sprite + borders) + padding +var toolbarSpriteSize int32 = 32 // 32x32 sprites. + +// SetupToolbar configures the UI for the Tools panel. +func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame { + frame := ui.NewFrame("Tool Bar") + frame.Resize(render.NewRect(toolbarWidth, 100)) + frame.Configure(ui.Config{ + BorderSize: 2, + BorderStyle: ui.BorderRaised, + Background: render.Grey, + }) + + btnFrame := ui.NewFrame("Tool Buttons") + frame.Pack(btnFrame, ui.Pack{ + Anchor: ui.N, + }) + + // Buttons. + var buttons = []struct { + Value string + Icon string + Click func() + }{ + { + Value: drawtool.PencilTool.String(), + Icon: "assets/sprites/pencil-tool.png", + Click: func() { + u.Canvas.Tool = drawtool.PencilTool + u.DoodadTab.Hide() + u.PaletteTab.Show() + d.Flash("Pencil Tool selected.") + }, + }, + + { + Value: drawtool.LineTool.String(), + Icon: "assets/sprites/line-tool.png", + Click: func() { + u.Canvas.Tool = drawtool.LineTool + u.DoodadTab.Hide() + u.PaletteTab.Show() + d.Flash("Line Tool selected.") + }, + }, + + { + Value: drawtool.RectTool.String(), + Icon: "assets/sprites/rect-tool.png", + Click: func() { + u.Canvas.Tool = drawtool.RectTool + u.DoodadTab.Hide() + u.PaletteTab.Show() + d.Flash("Rectangle Tool selected.") + }, + }, + + { + Value: drawtool.ActorTool.String(), + Icon: "assets/sprites/actor-tool.png", + Click: func() { + u.Canvas.Tool = drawtool.ActorTool + u.PaletteTab.Hide() + u.DoodadTab.Show() + d.Flash("Actor Tool selected. Drag a Doodad from the drawer into your level.") + }, + }, + + { + Value: drawtool.LinkTool.String(), + Icon: "assets/sprites/link-tool.png", + Click: func() { + u.Canvas.Tool = drawtool.LinkTool + u.PaletteTab.Hide() + u.DoodadTab.Show() + d.Flash("Link Tool selected. Click a doodad in your level to link it to another.") + }, + }, + } + for _, button := range buttons { + button := button + image, err := sprites.LoadImage(d.Engine, button.Icon) + if err != nil { + panic(err) + } + + btn := ui.NewRadioButton( + button.Value, + u.activeTool, + button.Value, + image, + ) + + var btnSize int32 = btn.BoxThickness(2) + toolbarSpriteSize + log.Info("BtnSize: %d", btnSize) + btn.Resize(render.NewRect(btnSize, btnSize)) + + btn.Handle(ui.Click, func(p render.Point) { + button.Click() + }) + u.Supervisor.Add(btn) + + btnFrame.Pack(btn, ui.Pack{ + Anchor: ui.N, + PadY: 2, + }) + } + + frame.Compute(d.Engine) + + return frame +} diff --git a/pkg/shell.go b/pkg/shell.go index 5c9a7d8..87abef0 100644 --- a/pkg/shell.go +++ b/pkg/shell.go @@ -326,7 +326,7 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error { // Otherwise, just draw flashed messages. valid := false // Did we actually draw any? - outputY := int32(d.height - (lineHeight * 2)) + outputY := int32(d.height - (lineHeight * 2) - 16) for i := len(s.Flashes); i > 0; i-- { flash := s.Flashes[i-1] if d.ticks >= flash.Expires { @@ -342,7 +342,7 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error { Shadow: render.Black, }, render.Point{ - X: balance.ShellPadding, + X: balance.ShellPadding + toolbarWidth, Y: outputY, }, ) diff --git a/pkg/sprites/sprites.go b/pkg/sprites/sprites.go new file mode 100644 index 0000000..6ddbf88 --- /dev/null +++ b/pkg/sprites/sprites.go @@ -0,0 +1,61 @@ +package sprites + +import ( + "bytes" + "errors" + "image/png" + "io/ioutil" + "os" + + "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/apps/doodle/lib/ui" + "git.kirsle.net/apps/doodle/pkg/bindata" + "git.kirsle.net/apps/doodle/pkg/log" +) + +// LoadImage loads a sprite as a ui.Image object. It checks Doodle's embedded +// bindata, then the filesystem before erroring out. +// +// NOTE: only .png images supported as of now. TODO +func LoadImage(e render.Engine, filename string) (*ui.Image, error) { + // Try the bindata first. + if data, err := bindata.Asset(filename); err == nil { + log.Debug("sprites.LoadImage: %s from bindata", filename) + + img, err := png.Decode(bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + + tex, err := e.StoreTexture(filename, img) + if err != nil { + return nil, err + } + + return ui.ImageFromTexture(tex), nil + } + + // Then try the file system. + if _, err := os.Stat(filename); !os.IsNotExist(err) { + log.Debug("sprites.LoadImage: %s from filesystem", filename) + + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + img, err := png.Decode(bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + + tex, err := e.StoreTexture(filename, img) + if err != nil { + return nil, err + } + + return ui.ImageFromTexture(tex), nil + } + + return nil, errors.New("no such sprite found") +}