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:
parent
8ca411a0ae
commit
0ec259b171
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ var (
|
||||||
|
|
||||||
// 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))
|
||||||
|
|
|
@ -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
70
pkg/uix/crosshair.go
Normal 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],
|
||||||
|
)
|
||||||
|
}
|
|
@ -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"`
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user