Noah Petherbridge
af6b8625d6
New features: * Flood Tool for the editor. It replaces pixels of one color with another, contiguously. Has limits on how far from the original pixel it will color, to avoid infinite loops in case the user clicked on wide open void. The limit when clicking an existing color is 1200px or only a 600px limit if clicking into the void. * Cheat code: 'master key' to play locked Story Mode levels. Level GameRules feature added: * A new tab in the Level Properties dialog * Difficulty has been moved to this tab * Survival Mode: for silver high score, longest time alive is better than fastest time, for Azulian Tag maps. Gold high score is still based on fastest time - find the hidden level exit without dying! Tweaks to the Azulians' jump heights: * Blue Azulian: 12 -> 14 * Red Azulian: 14 -> 18 * White Azulian: 16 -> 20 Bugs fixed: * When editing your Palette to rename a color or add a new color, it wasn't possible to draw with that color until the editor was completely unloaded and reloaded; this is now fixed. * Minor bugfix in Difficulty.String() for Peaceful (-1) difficulty to avoid a negative array index. * Try and prevent user giving the same name to multiple swatches on their palette. Replacing the whole palette can let duplication through still.
399 lines
9.0 KiB
Go
399 lines
9.0 KiB
Go
package doodle
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"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/sprites"
|
|
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
|
"git.kirsle.net/go/render"
|
|
"git.kirsle.net/go/ui"
|
|
"git.kirsle.net/go/ui/style"
|
|
)
|
|
|
|
// Global toolbarWidth, TODO: editor_ui.go wants it
|
|
var toolbarWidth int
|
|
|
|
// SetupToolbar configures the UI for the Tools panel.
|
|
func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
|
// Horizontal toolbar instead of vertical?
|
|
var (
|
|
toolbarSpriteSize = 24 // size of sprite images
|
|
frameSize render.Rect
|
|
isHoz = usercfg.Current.HorizontalToolbars
|
|
buttonsPerRow = 2
|
|
packAlign = ui.N
|
|
tooltipEdge = ui.Right
|
|
btnRowPack = ui.Pack{
|
|
Side: packAlign,
|
|
PadY: 1,
|
|
Fill: true,
|
|
}
|
|
btnPack = ui.Pack{
|
|
Side: ui.W,
|
|
PadX: 1,
|
|
}
|
|
)
|
|
if isHoz {
|
|
packAlign = ui.W
|
|
tooltipEdge = ui.Bottom
|
|
btnRowPack = ui.Pack{
|
|
Side: packAlign,
|
|
PadX: 2,
|
|
}
|
|
btnPack = ui.Pack{
|
|
Side: ui.N,
|
|
PadY: 1,
|
|
}
|
|
}
|
|
|
|
// Button Layout Controls:
|
|
// We can draw 2 buttons per row, but for very small screens
|
|
// e.g. mobile in portrait orientation, draw 1 button per row.
|
|
buttonsPerRow = 1
|
|
if isHoz {
|
|
if d.width < enum.ScreenWidthSmall {
|
|
// Narrow screens
|
|
buttonsPerRow = 2
|
|
}
|
|
} else {
|
|
if d.width >= enum.ScreenWidthSmall {
|
|
// Screen wider than 600px = can spare room for 2 buttons per row.
|
|
buttonsPerRow = 2
|
|
}
|
|
}
|
|
|
|
// Compute toolbar size to accommodate all buttons (+10 for borders/padding)
|
|
toolbarWidth = buttonsPerRow * (toolbarSpriteSize + 10)
|
|
frameSize = render.NewRect(toolbarWidth, 100)
|
|
|
|
frame := ui.NewFrame("Tool Bar")
|
|
frame.Resize(frameSize)
|
|
frame.Configure(ui.Config{
|
|
BorderSize: 2,
|
|
BorderStyle: ui.BorderRaised,
|
|
Background: render.Grey,
|
|
})
|
|
|
|
btnFrame := ui.NewFrame("Tool Buttons")
|
|
frame.Pack(btnFrame, ui.Pack{
|
|
Side: packAlign,
|
|
})
|
|
|
|
// Buttons.
|
|
var buttons = []struct {
|
|
Value string
|
|
Icon string
|
|
Tooltip string
|
|
Style *style.Button
|
|
Click func()
|
|
|
|
// Optional fields.
|
|
NoDoodad bool // tool not available for Doodad editing (Levels only)
|
|
}{
|
|
{
|
|
Value: drawtool.PanTool.String(),
|
|
Icon: "assets/sprites/pan-tool.png",
|
|
Tooltip: "Pan Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.PanTool
|
|
d.Flash("Pan Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.PencilTool.String(),
|
|
Icon: "assets/sprites/pencil-tool.png",
|
|
Tooltip: "Pencil Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.PencilTool
|
|
d.Flash("Pencil Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.LineTool.String(),
|
|
Icon: "assets/sprites/line-tool.png",
|
|
Tooltip: "Line Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.LineTool
|
|
d.Flash("Line Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.RectTool.String(),
|
|
Icon: "assets/sprites/rect-tool.png",
|
|
Tooltip: "Rectangle Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.RectTool
|
|
d.Flash("Rectangle Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.EllipseTool.String(),
|
|
Icon: "assets/sprites/ellipse-tool.png",
|
|
Tooltip: "Ellipse Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.EllipseTool
|
|
d.Flash("Ellipse Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.TextTool.String(),
|
|
Icon: "assets/sprites/text-tool.png",
|
|
Tooltip: "Text Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.TextTool
|
|
u.OpenTextTool()
|
|
d.Flash("Text Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.FloodTool.String(),
|
|
Icon: "assets/sprites/flood-tool.png",
|
|
Tooltip: "Flood Tool",
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.FloodTool
|
|
d.Flash("Flood Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.EraserTool.String(),
|
|
Icon: "assets/sprites/eraser-tool.png",
|
|
Tooltip: "Eraser Tool",
|
|
Style: &balance.ButtonLightRed,
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.EraserTool
|
|
|
|
// Set the brush size within range for the eraser.
|
|
if u.Canvas.BrushSize < balance.DefaultEraserBrushSize {
|
|
u.Canvas.BrushSize = balance.DefaultEraserBrushSize
|
|
} else if u.Canvas.BrushSize > balance.MaxEraserBrushSize {
|
|
u.Canvas.BrushSize = balance.MaxEraserBrushSize
|
|
}
|
|
|
|
d.Flash("Eraser Tool selected.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.ActorTool.String(),
|
|
Icon: "assets/sprites/actor-tool.png",
|
|
Tooltip: "Doodad Tool\nDrag-and-drop objects into your map",
|
|
NoDoodad: true,
|
|
Style: &balance.ButtonBabyBlue,
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.ActorTool
|
|
u.OpenDoodadDropper()
|
|
d.Flash("Actor Tool selected. Drag a Doodad from the drawer into your level.")
|
|
},
|
|
},
|
|
|
|
{
|
|
Value: drawtool.LinkTool.String(),
|
|
Icon: "assets/sprites/link-tool.png",
|
|
Tooltip: "Link Tool\nConnect doodads to each other",
|
|
Style: &balance.ButtonPink,
|
|
NoDoodad: true,
|
|
Click: func() {
|
|
u.Canvas.Tool = drawtool.LinkTool
|
|
d.Flash("Link Tool selected. Click a doodad in your level to link it to another.")
|
|
},
|
|
},
|
|
}
|
|
|
|
// Arrange the buttons 2x2.
|
|
var btnRow *ui.Frame
|
|
for i, button := range buttons {
|
|
button := button
|
|
if button.NoDoodad && u.Scene.DrawingType == enum.DoodadDrawing {
|
|
continue
|
|
}
|
|
|
|
if buttonsPerRow == 1 || i%buttonsPerRow == 0 {
|
|
btnRow = ui.NewFrame(fmt.Sprintf("Button Row %d", i))
|
|
btnFrame.Pack(btnRow, btnRowPack)
|
|
}
|
|
|
|
image, err := sprites.LoadImage(d.Engine, button.Icon)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
btn := ui.NewRadioButton(
|
|
button.Value,
|
|
&u.activeTool,
|
|
button.Value,
|
|
image,
|
|
)
|
|
if button.Style != nil {
|
|
btn.SetStyle(button.Style)
|
|
}
|
|
|
|
var btnSize = btn.BoxThickness(2) + toolbarSpriteSize
|
|
btn.SetBorderSize(1)
|
|
btn.Resize(render.NewRect(btnSize, btnSize))
|
|
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
button.Click()
|
|
return nil
|
|
})
|
|
u.Supervisor.Add(btn)
|
|
|
|
tt := ui.NewTooltip(btn, ui.Tooltip{
|
|
Text: button.Tooltip,
|
|
Edge: tooltipEdge,
|
|
})
|
|
tt.Supervise(u.Supervisor)
|
|
|
|
btnRow.Pack(btn, btnPack)
|
|
}
|
|
|
|
// Doodad Editor: show the Layers button.
|
|
if u.Scene.DrawingType == enum.DoodadDrawing {
|
|
btn := ui.NewButton("Layers Button", ui.NewLabel(ui.Label{
|
|
Text: "Lyr.",
|
|
Font: balance.MenuFont,
|
|
}))
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
u.OpenLayersWindow()
|
|
return nil
|
|
})
|
|
u.Supervisor.Add(btn)
|
|
btnFrame.Pack(btn, ui.Pack{
|
|
Side: packAlign,
|
|
PadY: 2,
|
|
})
|
|
}
|
|
|
|
// Spacer frame.
|
|
frame.Pack(ui.NewFrame("spacer"), ui.Pack{
|
|
Side: packAlign,
|
|
PadY: 8,
|
|
})
|
|
|
|
//////////////
|
|
// "Brush Size" label
|
|
bsFrame := ui.NewFrame("Brush Size Frame")
|
|
frame.Pack(bsFrame, ui.Pack{
|
|
Side: packAlign,
|
|
})
|
|
|
|
bsLabel := ui.NewLabel(ui.Label{
|
|
Text: "Size:",
|
|
Font: balance.SmallFont,
|
|
})
|
|
bsFrame.Pack(bsLabel, ui.Pack{
|
|
Side: ui.N,
|
|
})
|
|
|
|
tt := ui.NewTooltip(bsLabel, ui.Tooltip{
|
|
Text: "Set the line thickness for drawing",
|
|
Edge: tooltipEdge,
|
|
})
|
|
tt.Supervise(u.Supervisor)
|
|
u.Supervisor.Add(bsLabel)
|
|
|
|
sizeLabel := ui.NewLabel(ui.Label{
|
|
IntVariable: &u.Canvas.BrushSize,
|
|
Font: balance.SmallFont,
|
|
})
|
|
sizeLabel.Configure(ui.Config{
|
|
BorderSize: 1,
|
|
BorderStyle: ui.BorderSunken,
|
|
Background: render.Grey,
|
|
})
|
|
bsFrame.Pack(sizeLabel, ui.Pack{
|
|
Side: ui.N,
|
|
// FillX: true,
|
|
PadY: 0,
|
|
})
|
|
|
|
// Brush Size widget
|
|
{
|
|
sizeFrame := ui.NewFrame("Brush Size Frame")
|
|
frame.Pack(sizeFrame, ui.Pack{
|
|
Side: packAlign,
|
|
PadY: 0,
|
|
})
|
|
|
|
sizeBtnFrame := ui.NewFrame("Size Increment Button Frame")
|
|
sizeFrame.Pack(sizeBtnFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
var incButtons = []struct {
|
|
Label string
|
|
F func()
|
|
}{
|
|
{
|
|
Label: "-",
|
|
F: func() {
|
|
// Select next smaller brush size.
|
|
for i := len(balance.BrushSizeOptions) - 1; i >= 0; i-- {
|
|
if balance.BrushSizeOptions[i] < u.Canvas.BrushSize {
|
|
u.Canvas.BrushSize = balance.BrushSizeOptions[i]
|
|
break
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Label: "+",
|
|
F: func() {
|
|
// Select next bigger brush size.
|
|
for _, size := range balance.BrushSizeOptions {
|
|
if size > u.Canvas.BrushSize {
|
|
u.Canvas.BrushSize = size
|
|
break
|
|
}
|
|
}
|
|
|
|
// Limit the eraser brush size, too big and it's slow because
|
|
// the eraser has to scan and remember pixels to be able to
|
|
// Undo the erase and restore them.
|
|
if u.Canvas.Tool == drawtool.EraserTool && u.Canvas.BrushSize > balance.MaxEraserBrushSize {
|
|
u.Canvas.BrushSize = balance.MaxEraserBrushSize
|
|
}
|
|
},
|
|
},
|
|
}
|
|
for _, button := range incButtons {
|
|
button := button
|
|
btn := ui.NewButton("BrushSize"+button.Label, ui.NewLabel(ui.Label{
|
|
Text: button.Label,
|
|
Font: balance.SmallMonoFont,
|
|
}))
|
|
btn.SetBorderSize(1)
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
button.F()
|
|
return nil
|
|
})
|
|
u.Supervisor.Add(btn)
|
|
|
|
// Which side to pack on?
|
|
var side = ui.W
|
|
if !isHoz && buttonsPerRow == 1 {
|
|
// Vertical layout w/ narrow one-button-per-row, the +-
|
|
// buttons stick out so stack them vertically.
|
|
side = ui.S
|
|
}
|
|
sizeBtnFrame.Pack(btn, ui.Pack{
|
|
Side: side,
|
|
})
|
|
}
|
|
}
|
|
|
|
frame.Compute(d.Engine)
|
|
|
|
return frame
|
|
}
|