Crosshair Option + Doodad Editor crash fix

* The level scroll logic was getting a null pointer crash if you open a
  doodad rather than a level file.
* Add a crosshair option to the level editor, configurable in the Game
  Settings window.
This commit is contained in:
Noah 2021-10-11 15:57:33 -07:00
parent 8ca411a0ae
commit 0ec259b171
9 changed files with 207 additions and 1 deletions

View File

@ -20,6 +20,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/sound" "git.kirsle.net/apps/doodle/pkg/sound"
"git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/apps/doodle/pkg/usercfg"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/sdl" "git.kirsle.net/go/render/sdl"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -62,6 +63,11 @@ func main() {
log.Error("Error loading user settings (defaults will be used): %s", err) log.Error("Error loading user settings (defaults will be used): %s", err)
} }
// Set default user settings.
if usercfg.Current.CrosshairColor == render.Invisible {
usercfg.Current.CrosshairColor = balance.DefaultCrosshairColor
}
app.Version = fmt.Sprintf("%s build %s%s. Built on %s", app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
branding.Version, branding.Version,
Build, Build,

View File

@ -187,6 +187,8 @@ var (
ButtonBabyBlue = style.DefaultButton ButtonBabyBlue = style.DefaultButton
ButtonPink = style.DefaultButton ButtonPink = style.DefaultButton
ButtonLightRed = style.DefaultButton ButtonLightRed = style.DefaultButton
DefaultCrosshairColor = render.RGBA(0, 153, 255, 255)
) )
// Customize the various button styles. // Customize the various button styles.

View File

@ -69,6 +69,7 @@ func New(debug bool, engine render.Engine) *Doodle {
// Make the render engine globally available. TODO: for wasm/ToBitmap // Make the render engine globally available. TODO: for wasm/ToBitmap
shmem.CurrentRenderEngine = engine shmem.CurrentRenderEngine = engine
shmem.Flash = d.Flash shmem.Flash = d.Flash
shmem.FlashError = d.FlashError
shmem.Prompt = d.Prompt shmem.Prompt = d.Prompt
if debug { if debug {
@ -240,6 +241,8 @@ func (d *Doodle) MakeSettingsWindow(supervisor *ui.Supervisor) *ui.Window {
DebugCollision: &DebugCollision, DebugCollision: &DebugCollision,
HorizontalToolbars: &usercfg.Current.HorizontalToolbars, HorizontalToolbars: &usercfg.Current.HorizontalToolbars,
EnableFeatures: &usercfg.Current.EnableFeatures, EnableFeatures: &usercfg.Current.EnableFeatures,
CrosshairSize: &usercfg.Current.CrosshairSize,
CrosshairColor: &usercfg.Current.CrosshairColor,
} }
return windows.MakeSettingsWindow(d.width, d.height, cfg) return windows.MakeSettingsWindow(d.width, d.height, cfg)
} }

View File

@ -351,6 +351,9 @@ func (u *EditorUI) Present(e render.Engine) {
u.screen.Present(e, render.Origin) u.screen.Present(e, render.Origin)
// Draw the crosshair over the level canvas, but not over UI popups.
uix.DrawCrosshair(e, u.Canvas, usercfg.Current.CrosshairColor, usercfg.Current.CrosshairSize)
// Draw any windows being managed by Supervisor. // Draw any windows being managed by Supervisor.
u.Supervisor.Present(e) u.Supervisor.Present(e)

View File

@ -23,7 +23,8 @@ var (
OfflineMode bool OfflineMode bool
// Globally available Flash() function so we can emit text to the Doodle UI. // Globally available Flash() function so we can emit text to the Doodle UI.
Flash func(string, ...interface{}) Flash func(string, ...interface{})
FlashError func(string, ...interface{})
// Globally available Prompt() function. // Globally available Prompt() function.
Prompt func(string, func(string)) Prompt func(string, func(string))

View File

@ -116,6 +116,11 @@ func (w *Canvas) loopConstrainScroll() error {
return errors.New("NoLimitScroll enabled") return errors.New("NoLimitScroll enabled")
} }
// Levels only.
if w.level == nil {
return nil
}
var ( var (
capped bool capped bool
maxWidth = w.level.MaxWidth maxWidth = w.level.MaxWidth

70
pkg/uix/crosshair.go Normal file
View File

@ -0,0 +1,70 @@
package uix
import (
"git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
)
/*
Functions for the Crosshair feature of the game.
NOT dependent on Canvas!
*/
type Crosshair struct {
LengthPct float64 // between 0 and 1
Widget ui.Widget
}
func NewCrosshair(child ui.Widget, length float64) *Crosshair {
return &Crosshair{
LengthPct: length,
Widget: child,
}
}
// DrawCrosshair renders a crosshair on the screen. It appears while the mouse cursor is
// over the child widget and draws within the bounds of the child widget.
//
// The lengthPct is an integer ranged 0 to 100 to be a percentage length of the crosshair.
// A value of zero will not draw anything and just return.
func DrawCrosshair(e render.Engine, child ui.Widget, color render.Color, lengthPct int) {
// Get our window boundaries based on our widget.
var (
Position = ui.AbsolutePosition(child)
Size = child.Size()
Cursor = shmem.Cursor
VertLine = []render.Point{
{X: Cursor.X, Y: Position.Y},
{X: Cursor.X, Y: Position.Y + Size.H},
}
HozLine = []render.Point{
{X: Position.X, Y: Cursor.Y},
{X: Position.X + Size.W, Y: Cursor.Y},
}
)
if lengthPct > 100 {
lengthPct = 100
} else if lengthPct <= 0 {
return
}
// Mouse outside our box.
if Cursor.X < Position.X || Cursor.X > Position.X+Size.W ||
Cursor.Y < Position.Y || Cursor.Y > Position.Y+Size.H {
return
}
e.DrawLine(
color,
VertLine[0],
VertLine[1],
)
e.DrawLine(
color,
HozLine[0],
HozLine[1],
)
}

View File

@ -18,6 +18,7 @@ import (
"time" "time"
"git.kirsle.net/apps/doodle/pkg/userdir" "git.kirsle.net/apps/doodle/pkg/userdir"
"git.kirsle.net/go/render"
) )
// Settings are the available game settings. // Settings are the available game settings.
@ -30,6 +31,8 @@ type Settings struct {
// Configurable settings (pkg/windows/settings.go) // Configurable settings (pkg/windows/settings.go)
HorizontalToolbars bool `json:",omitempty"` HorizontalToolbars bool `json:",omitempty"`
EnableFeatures bool `json:",omitempty"` EnableFeatures bool `json:",omitempty"`
CrosshairSize int `json:",omitempty"`
CrosshairColor render.Color
// Secret boolprops from balance/boolprops.go // Secret boolprops from balance/boolprops.go
ShowHiddenDoodads bool `json:",omitempty"` ShowHiddenDoodads bool `json:",omitempty"`

View File

@ -1,11 +1,13 @@
package windows package windows
import ( import (
"strconv"
"strings" "strings"
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/native" "git.kirsle.net/apps/doodle/pkg/native"
"git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/apps/doodle/pkg/usercfg"
"git.kirsle.net/apps/doodle/pkg/userdir" "git.kirsle.net/apps/doodle/pkg/userdir"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
@ -24,6 +26,8 @@ type Settings struct {
DebugCollision *bool DebugCollision *bool
HorizontalToolbars *bool HorizontalToolbars *bool
EnableFeatures *bool EnableFeatures *bool
CrosshairSize *int
CrosshairColor *render.Color
// Configuration options. // Configuration options.
SceneName string // name of scene which called this window SceneName string // name of scene which called this window
@ -106,11 +110,14 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F
return nil return nil
} }
var inputBoxWidth = 120
rows := []struct { rows := []struct {
Header string Header string
Text string Text string
Boolean *bool Boolean *bool
Integer *int
TextVariable *string TextVariable *string
Color *render.Color
PadY int PadY int
PadX int PadX int
name string // for special cases name string // for special cases
@ -128,6 +135,16 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F
PadX: 4, PadX: 4,
name: "toolbars", name: "toolbars",
}, },
{
Integer: c.CrosshairSize,
Text: "Editor: Crosshair size (0 to disable):",
PadX: 4,
},
{
Color: c.CrosshairColor,
Text: "Editor: Crosshair color:",
PadX: 4,
},
{ {
Header: "Debug Options (temporary)", Header: "Debug Options (temporary)",
}, },
@ -196,6 +213,14 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F
PadX: row.PadX, PadX: row.PadX,
}) })
continue continue
} else {
// Reserve indented space where the checkbox would have gone.
spacer := ui.NewFrame("Spacer")
spacer.Resize(render.NewRect(9, 9)) // TODO: ugly UI hack ;)
frame.Pack(spacer, ui.Pack{
Side: ui.W,
PadX: row.PadX,
})
} }
// Any leftover Text gets packed to the left. // Any leftover Text gets packed to the left.
@ -212,6 +237,94 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F
Side: ui.W, Side: ui.W,
}) })
} }
// Int variables draw as a button to prompt for new value.
// In future: TextVariable works here too.
if row.Integer != nil {
varButton := ui.NewButton("VarButton", ui.NewLabel(ui.Label{
IntVariable: row.Integer,
Font: ui.MenuFont,
}))
varButton.Handle(ui.Click, func(ed ui.EventData) error {
shmem.Prompt(row.Text+" ", func(answer string) {
if answer == "" {
return
}
a, err := strconv.Atoi(answer)
if err != nil {
shmem.FlashError(err.Error())
return
}
if a < 0 {
a = 0
} else if a > 100 {
a = 100
}
*row.Integer = a
shmem.Flash("Crosshair size set to %d%% (WIP)", a)
// call onClick to save change to disk now
onClick(ed)
})
return nil
})
varButton.Compute(c.Engine)
varButton.Resize(render.Rect{
W: inputBoxWidth,
H: varButton.Size().H,
})
c.Supervisor.Add(varButton)
frame.Pack(varButton, ui.Pack{
Side: ui.E,
PadX: row.PadX,
})
}
// Color picker button.
if row.Color != nil {
btn := ui.NewButton("ColorBtn", ui.NewFrame(""))
style := style.DefaultButton
style.Background = *row.Color
style.HoverBackground = style.Background.Lighten(20)
btn.SetStyle(&style)
btn.Handle(ui.Click, func(ed ui.EventData) error {
shmem.Prompt("Enter color in hexadecimal notation: ", func(answer string) {
if answer == "" {
return
}
color, err := render.HexColor(answer)
if err != nil {
shmem.FlashError("Invalid color value: %s", err)
}
*row.Color = color
style.Background = color
style.HoverBackground = style.Background.Lighten(20)
// call onClick to save change to disk now
onClick(ed)
})
return nil
})
btn.Compute(c.Engine)
btn.Resize(render.Rect{
W: inputBoxWidth,
H: 20, // TODO
})
c.Supervisor.Add(btn)
frame.Pack(btn, ui.Pack{
Side: ui.E,
PadX: row.PadX,
})
}
} }
// Button toolbar. // Button toolbar.