Compare commits
7 commits
58ea970102
...
404b9a6efe
| Author | SHA1 | Date | |
|---|---|---|---|
| 404b9a6efe | |||
| c25022bc37 | |||
| 402192742b | |||
| 8a0f738767 | |||
| 6bda2efb2e | |||
| fe8c4d1a43 | |||
| 97aa9f159c |
19 changed files with 547 additions and 124 deletions
|
|
@ -5,11 +5,12 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
SFX_SHAPE_LOCKED audio.SoundID = 0
|
||||
SFX_ROW_CLEARED audio.SoundID = 1
|
||||
SFX_MENU_SELECT audio.SoundID = 0
|
||||
SFX_MENU_ENTER audio.SoundID = 1
|
||||
SFX_GAME_OVER audio.SoundID = 2
|
||||
SFX_SHAPE_LOCKED audio.SoundID = 0
|
||||
SFX_ROW_CLEARED audio.SoundID = 1
|
||||
SFX_MENU_SELECT audio.SoundID = 0
|
||||
SFX_MENU_ENTER audio.SoundID = 1
|
||||
SFX_MENU_SOUND_VOLUME_SELECT audio.SoundID = 1
|
||||
SFX_GAME_OVER audio.SoundID = 2
|
||||
)
|
||||
|
||||
func LoadSound() *audio.Library {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ func LoadLibrary(library *Library) {
|
|||
defaultManager.Load(library)
|
||||
}
|
||||
|
||||
func SetVolume(value float32) {
|
||||
defaultManager.SetVolume(value)
|
||||
}
|
||||
|
||||
func Volume() float32 {
|
||||
return defaultManager.Volume()
|
||||
}
|
||||
|
||||
func Play(id SoundID) {
|
||||
defaultManager.Play(id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ func (sm *Manager) Load(library *Library) {
|
|||
sm.library = library
|
||||
}
|
||||
|
||||
func (sm Manager) SetVolume(value float32) {
|
||||
rl.SetMasterVolume(value)
|
||||
}
|
||||
|
||||
func (sm Manager) Volume() float32 {
|
||||
return rl.GetMasterVolume()
|
||||
}
|
||||
|
||||
func (sm *Manager) play(id SoundID, looping bool) {
|
||||
snd := sm.library.Get(id)
|
||||
|
||||
|
|
|
|||
9
engine/core/num_clamp.go
Normal file
9
engine/core/num_clamp.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package core
|
||||
|
||||
func ByteToClampedFloat32(v byte) float32 {
|
||||
return min(float32(v)/255.0, 1.0)
|
||||
}
|
||||
|
||||
func ClampedFloat32ToByte(v float32) byte {
|
||||
return byte(min(v, 1.0) * 255)
|
||||
}
|
||||
8
engine/input/keyboard.go
Normal file
8
engine/input/keyboard.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package input
|
||||
|
||||
import rl "github.com/gen2brain/raylib-go/raylib"
|
||||
|
||||
func KeyPressedWithRepeat(key int32) bool {
|
||||
return rl.IsKeyPressed(key) || rl.IsKeyPressedRepeat(key)
|
||||
}
|
||||
|
||||
|
|
@ -17,10 +17,15 @@ func DrawRectBorder(rect rl.RectangleInt32, col color.RGBA, border_size int32, b
|
|||
Height: float32(rect.Height),
|
||||
}, col)
|
||||
|
||||
rl.DrawRectangleLinesEx(rl.Rectangle{
|
||||
X: float32(rect.X - border_size),
|
||||
Y: float32(rect.Y - border_size),
|
||||
Width: float32(rect.Width + (border_size * 2)),
|
||||
Height: float32(rect.Height + (border_size * 2)),
|
||||
}, float32(border_size), border_col)
|
||||
DrawRectOutlineBorder(rect, border_size, border_col)
|
||||
}
|
||||
|
||||
// DrawRectOutlineBorder draws a border (outer) around the rectangle.
|
||||
func DrawRectOutlineBorder(rect rl.RectangleInt32, size int32, col color.RGBA) {
|
||||
rl.DrawRectangleLinesEx(rl.Rectangle{
|
||||
X: float32(rect.X - size),
|
||||
Y: float32(rect.Y - size),
|
||||
Width: float32(rect.Width + (size * 2)),
|
||||
Height: float32(rect.Height + (size * 2)),
|
||||
}, float32(size), col)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,33 +6,37 @@ import (
|
|||
"tetris/game"
|
||||
"tetris/game/state"
|
||||
"tetris/game/ui"
|
||||
"tetris/game/ui/layouts"
|
||||
"tetris/game/ui/widgets"
|
||||
"tetris/game/uievents"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type MainMenu struct {
|
||||
menu *ui.Menu
|
||||
list *layouts.ListBox
|
||||
}
|
||||
|
||||
func NewMainMenu(fsm state.Transitioner) *MainMenu {
|
||||
return &MainMenu{
|
||||
menu: ui.NewMenu([]ui.Widget{
|
||||
ui.NewButton("Start", func() { fsm.Switch("gameplay") }),
|
||||
ui.NewButton("Quit", func() { fsm.Switch("quit") }),
|
||||
}).OnSelect(uievents.MenuSelect),
|
||||
list: layouts.NewListBox([]ui.InputWidget{
|
||||
widgets.NewButton("Start", 32, func() { fsm.Switch("gameplay") }),
|
||||
widgets.NewButton("Options", 32, func() { fsm.Switch("options") }),
|
||||
widgets.NewButton("Quit", 32, func() { fsm.Switch("quit") }),
|
||||
}).Spacing(10).
|
||||
OnSelect(uievents.MenuSelect),
|
||||
}
|
||||
}
|
||||
|
||||
func (main *MainMenu) Enter() {
|
||||
main.menu.Select(0)
|
||||
main.list.Select(0)
|
||||
}
|
||||
|
||||
func (MainMenu) Exit() {
|
||||
}
|
||||
|
||||
func (menu *MainMenu) Update(fsm state.Transitioner, delta float32) {
|
||||
menu.menu.HandleInput()
|
||||
menu.list.HandleInput()
|
||||
}
|
||||
|
||||
func (MainMenu) renderLogo(offset_x, offset_y int32) {
|
||||
|
|
@ -57,19 +61,11 @@ func (MainMenu) renderLogo(offset_x, offset_y int32) {
|
|||
}
|
||||
}
|
||||
|
||||
func (menu MainMenu) renderEntries(offset_x, offset_y int32) {
|
||||
y := offset_y
|
||||
for i, entry := range menu.menu.Entries() {
|
||||
entry.Draw(offset_x, y, menu.menu.IsSelected(i))
|
||||
y += 40
|
||||
}
|
||||
}
|
||||
|
||||
func (menu MainMenu) Render() {
|
||||
render.Begin(rl.Black)
|
||||
|
||||
menu.renderLogo(20, 150)
|
||||
menu.renderEntries(340, 400)
|
||||
menu.list.Draw(340, 400)
|
||||
|
||||
render.End()
|
||||
}
|
||||
|
|
|
|||
63
game/state/handlers/options.go
Normal file
63
game/state/handlers/options.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"tetris/assets"
|
||||
"tetris/engine/audio"
|
||||
"tetris/engine/core"
|
||||
"tetris/engine/render"
|
||||
"tetris/game/state"
|
||||
"tetris/game/ui"
|
||||
"tetris/game/ui/layouts"
|
||||
"tetris/game/ui/widgets"
|
||||
"tetris/game/uievents"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type OptionsMenu struct {
|
||||
sndVolume *widgets.Slider
|
||||
list *layouts.ListBox
|
||||
}
|
||||
|
||||
func soundVolumeChanged(widget *widgets.Slider) {
|
||||
value := core.ByteToClampedFloat32(byte(widget.Value))
|
||||
|
||||
audio.SetVolume(value)
|
||||
audio.Play(assets.SFX_MENU_SOUND_VOLUME_SELECT)
|
||||
}
|
||||
|
||||
func NewOptionsMenu() *OptionsMenu {
|
||||
sndVolume := widgets.NewSlider(0, 255, 10).WithOnChange(soundVolumeChanged)
|
||||
return &OptionsMenu{
|
||||
sndVolume: sndVolume,
|
||||
list: layouts.NewListBox([]ui.InputWidget{
|
||||
layouts.NewInputControl(widgets.NewLabel("Sound Volume", 16), sndVolume),
|
||||
widgets.NewSlider(0, 255, 10).WithOnChange(soundVolumeChanged),
|
||||
}).Spacing(10).OnSelect(uievents.MenuSelect),
|
||||
}
|
||||
}
|
||||
|
||||
func (menu *OptionsMenu) Enter() {
|
||||
vol := core.ClampedFloat32ToByte(audio.Volume())
|
||||
menu.sndVolume.SetValue(int(vol))
|
||||
}
|
||||
|
||||
func (OptionsMenu) Exit() {
|
||||
}
|
||||
|
||||
func (menu *OptionsMenu) Update(fsm state.Transitioner, delta float32) {
|
||||
if rl.IsKeyPressed(rl.KeyEscape) {
|
||||
fsm.Switch("menu")
|
||||
} else {
|
||||
menu.list.HandleInput()
|
||||
}
|
||||
}
|
||||
|
||||
func (opt OptionsMenu) Render() {
|
||||
render.Begin(rl.Black)
|
||||
|
||||
render.DrawTextCenter(340, 100, 32, "Options", rl.White)
|
||||
opt.list.Draw(150, 200)
|
||||
|
||||
render.End()
|
||||
}
|
||||
26
game/ui/base/focus.go
Normal file
26
game/ui/base/focus.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package base
|
||||
|
||||
// WithFocus is a base component that implements the Focusable interface
|
||||
type WithFocus struct {
|
||||
focus bool
|
||||
}
|
||||
|
||||
// SetFocus - Set the focus value on this component
|
||||
func (f *WithFocus) SetFocus(value bool) {
|
||||
f.focus = value
|
||||
}
|
||||
|
||||
// Focus - focus this component, syntactic sugar for f.SetFocus(true)
|
||||
func (f *WithFocus) Focus() {
|
||||
f.SetFocus(true)
|
||||
}
|
||||
|
||||
// Unfocus - unfocus this component, syntactic sugar for f.SetFocus(false)
|
||||
func (f *WithFocus) Unfocus() {
|
||||
f.SetFocus(false)
|
||||
}
|
||||
|
||||
// HasFocus - Returns true if this component has focus.
|
||||
func (f WithFocus) HasFocus() bool {
|
||||
return f.focus
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"tetris/engine/render"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
Text string
|
||||
Action func()
|
||||
}
|
||||
|
||||
func NewButton(text string, action func()) Button {
|
||||
return Button{
|
||||
Text: text,
|
||||
Action: action,
|
||||
}
|
||||
}
|
||||
|
||||
func (b Button) HandleInput() {
|
||||
if rl.IsKeyPressed(rl.KeyEnter) {
|
||||
b.Action()
|
||||
}
|
||||
}
|
||||
|
||||
func (b Button) Draw(x, y int32, selected bool) {
|
||||
col := rl.White
|
||||
if selected {
|
||||
col = rl.Red
|
||||
}
|
||||
render.DrawTextCenter(x, y, 32, b.Text, col)
|
||||
}
|
||||
14
game/ui/component.go
Normal file
14
game/ui/component.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package ui
|
||||
|
||||
// Focusable is a component that can have focus
|
||||
type Focusable interface {
|
||||
SetFocus(value bool)
|
||||
Focus()
|
||||
Unfocus()
|
||||
HasFocus() bool
|
||||
}
|
||||
|
||||
// ReceivesInput is a component that can receive user input.
|
||||
type ReceivesInput interface {
|
||||
HandleInput()
|
||||
}
|
||||
57
game/ui/layouts/input_control.go
Normal file
57
game/ui/layouts/input_control.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package layouts
|
||||
|
||||
import (
|
||||
"tetris/engine/core"
|
||||
"tetris/game/ui"
|
||||
"tetris/game/ui/base"
|
||||
"tetris/game/ui/widgets"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
// InputControl is a layout that has a label and an a widget associated with it.
|
||||
type InputControl struct {
|
||||
*base.WithFocus
|
||||
label *widgets.Label
|
||||
widget ui.InputWidget
|
||||
spacing int
|
||||
}
|
||||
|
||||
func NewInputControl(label *widgets.Label, wiget ui.InputWidget) *InputControl {
|
||||
return &InputControl{
|
||||
WithFocus: &base.WithFocus{},
|
||||
label: label,
|
||||
widget: wiget,
|
||||
spacing: 10,
|
||||
}
|
||||
}
|
||||
|
||||
func (ic InputControl) Size() core.Vec2i {
|
||||
return core.Vec2i{
|
||||
X: ic.label.Size().X + ic.widget.Size().X + ic.spacing,
|
||||
Y: max(ic.label.Size().Y, ic.widget.Size().Y),
|
||||
}
|
||||
}
|
||||
|
||||
func (ic *InputControl) SetFocus(value bool) {
|
||||
ic.WithFocus.SetFocus(value)
|
||||
ic.widget.SetFocus(value)
|
||||
|
||||
// TODO: use a theme system here so colors are not hardcoded.
|
||||
if value {
|
||||
ic.label.SetColor(rl.Red)
|
||||
} else {
|
||||
ic.label.SetColor(rl.White)
|
||||
}
|
||||
}
|
||||
|
||||
func (ic InputControl) HandleInput() {
|
||||
ic.widget.HandleInput()
|
||||
}
|
||||
|
||||
func (ic InputControl) Draw(x, y int32) {
|
||||
size := ic.Size()
|
||||
label_y := (int32(size.Y) - int32(ic.label.Size().Y)) / 2
|
||||
ic.label.Draw(x, y+label_y)
|
||||
ic.widget.Draw(x+int32(ic.label.Size().X+ic.spacing), y)
|
||||
}
|
||||
84
game/ui/layouts/list_box.go
Normal file
84
game/ui/layouts/list_box.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package layouts
|
||||
|
||||
import (
|
||||
"tetris/game/ui"
|
||||
"tetris/game/ui/base"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type OnSelectCallback func()
|
||||
|
||||
type ListBox struct {
|
||||
*base.WithFocus
|
||||
selected int
|
||||
entries []ui.InputWidget
|
||||
onSelect OnSelectCallback
|
||||
spacing int
|
||||
}
|
||||
|
||||
func NewListBox(entries []ui.InputWidget) *ListBox {
|
||||
lb := &ListBox{
|
||||
WithFocus: &base.WithFocus{},
|
||||
entries: entries,
|
||||
onSelect: func() {},
|
||||
}
|
||||
lb.entries[0].SetFocus(true)
|
||||
return lb
|
||||
}
|
||||
|
||||
func (lb *ListBox) Spacing(value int) *ListBox {
|
||||
lb.spacing = value
|
||||
return lb
|
||||
}
|
||||
|
||||
func (lb *ListBox) OnSelect(callback OnSelectCallback) *ListBox {
|
||||
lb.onSelect = callback
|
||||
return lb
|
||||
}
|
||||
|
||||
func (lb ListBox) Entries() []ui.InputWidget {
|
||||
return lb.entries
|
||||
}
|
||||
|
||||
func (lb *ListBox) Select(index int) {
|
||||
if index >= 0 && index < len(lb.entries) {
|
||||
lb.entries[lb.selected].SetFocus(false)
|
||||
lb.selected = index
|
||||
lb.entries[lb.selected].SetFocus(true)
|
||||
lb.onSelect()
|
||||
}
|
||||
}
|
||||
|
||||
func (lb ListBox) Selected() ui.InputWidget {
|
||||
return lb.entries[lb.selected]
|
||||
}
|
||||
|
||||
func (lb ListBox) IsSelected(index int) bool {
|
||||
return lb.selected == index
|
||||
}
|
||||
|
||||
func (lb *ListBox) Next() {
|
||||
lb.Select(lb.selected + 1)
|
||||
}
|
||||
|
||||
func (lb *ListBox) Previous() {
|
||||
lb.Select(lb.selected - 1)
|
||||
}
|
||||
|
||||
func (lb *ListBox) HandleInput() {
|
||||
if rl.IsKeyPressed(rl.KeyDown) {
|
||||
lb.Next()
|
||||
} else if rl.IsKeyPressed(rl.KeyUp) {
|
||||
lb.Previous()
|
||||
} else {
|
||||
lb.Selected().HandleInput()
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *ListBox) Draw(x, y int32) {
|
||||
for _, ent := range lb.entries {
|
||||
ent.Draw(x, y)
|
||||
y += int32(ent.Size().Y + lb.spacing)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type OnSelectCallback func()
|
||||
|
||||
type Menu struct {
|
||||
selected int
|
||||
entries []Widget
|
||||
onSelect OnSelectCallback
|
||||
}
|
||||
|
||||
func NewMenu(entries []Widget) *Menu {
|
||||
return &Menu{
|
||||
entries: entries,
|
||||
onSelect: func() {},
|
||||
}
|
||||
}
|
||||
|
||||
func (menu *Menu) OnSelect(callback OnSelectCallback) *Menu {
|
||||
menu.onSelect = callback
|
||||
return menu
|
||||
}
|
||||
|
||||
func (menu Menu) Entries() []Widget {
|
||||
return menu.entries
|
||||
}
|
||||
|
||||
func (menu *Menu) Select(index int) {
|
||||
if index >= 0 && index < len(menu.entries) {
|
||||
menu.selected = index
|
||||
menu.onSelect()
|
||||
}
|
||||
}
|
||||
|
||||
func (menu Menu) Selected() Widget {
|
||||
return menu.entries[menu.selected]
|
||||
}
|
||||
|
||||
func (menu Menu) IsSelected(index int) bool {
|
||||
return menu.selected == index
|
||||
}
|
||||
|
||||
func (menu *Menu) Next() {
|
||||
menu.Select(menu.selected + 1)
|
||||
}
|
||||
|
||||
func (menu *Menu) Previous() {
|
||||
menu.Select(menu.selected - 1)
|
||||
}
|
||||
|
||||
func (menu *Menu) HandleInput() {
|
||||
if rl.IsKeyPressed(rl.KeyDown) {
|
||||
menu.Next()
|
||||
} else if rl.IsKeyPressed(rl.KeyUp) {
|
||||
menu.Previous()
|
||||
} else {
|
||||
menu.Selected().HandleInput()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,18 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"tetris/engine/core"
|
||||
)
|
||||
|
||||
// Widget is a base widget (Can be drawn on screen)
|
||||
type Widget interface {
|
||||
HandleInput()
|
||||
Draw(x, y int32, selected bool)
|
||||
Size() core.Vec2i
|
||||
Draw(x, y int32)
|
||||
}
|
||||
|
||||
// InputWidget is a widget that also handles user input and can have focus.
|
||||
type InputWidget interface {
|
||||
Widget
|
||||
Focusable
|
||||
ReceivesInput
|
||||
}
|
||||
|
|
|
|||
48
game/ui/widgets/button.go
Normal file
48
game/ui/widgets/button.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package widgets
|
||||
|
||||
import (
|
||||
"tetris/engine/core"
|
||||
"tetris/engine/render"
|
||||
"tetris/game/ui/base"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
*base.WithFocus
|
||||
Text string
|
||||
TextSize int32
|
||||
Action func()
|
||||
}
|
||||
|
||||
func NewButton(text string, size int32, action func()) Button {
|
||||
return Button{
|
||||
WithFocus: &base.WithFocus{},
|
||||
Text: text,
|
||||
TextSize: size,
|
||||
Action: action,
|
||||
}
|
||||
}
|
||||
|
||||
func (b Button) Size() core.Vec2i {
|
||||
s := int(b.TextSize)
|
||||
return core.Vec2i{
|
||||
X: len(b.Text) * s,
|
||||
Y: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (b Button) HandleInput() {
|
||||
if rl.IsKeyPressed(rl.KeyEnter) {
|
||||
b.Action()
|
||||
}
|
||||
}
|
||||
|
||||
func (b Button) Draw(x, y int32) {
|
||||
// TODO: use a theme system here so colors are not hardcoded.
|
||||
col := rl.White
|
||||
if b.HasFocus() {
|
||||
col = rl.Red
|
||||
}
|
||||
render.DrawTextCenter(x, y, b.TextSize, b.Text, col)
|
||||
}
|
||||
39
game/ui/widgets/label.go
Normal file
39
game/ui/widgets/label.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package widgets
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"tetris/engine/core"
|
||||
"tetris/engine/render"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
text string
|
||||
size int32
|
||||
color color.RGBA
|
||||
}
|
||||
|
||||
func NewLabel(text string, size int32) *Label {
|
||||
return &Label{
|
||||
text: text,
|
||||
size: size,
|
||||
color: rl.White,
|
||||
}
|
||||
}
|
||||
|
||||
func (label Label) Size() core.Vec2i {
|
||||
return core.Vec2i{
|
||||
X: int(label.size) * len(label.text),
|
||||
Y: int(label.size),
|
||||
}
|
||||
}
|
||||
|
||||
func (label *Label) SetColor(col color.RGBA) {
|
||||
label.color = col
|
||||
}
|
||||
|
||||
func (label Label) Draw(x, y int32) {
|
||||
render.DrawText(x, y, label.size, label.text, label.color)
|
||||
}
|
||||
136
game/ui/widgets/slider.go
Normal file
136
game/ui/widgets/slider.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
|
||||
"tetris/engine/core"
|
||||
"tetris/engine/input"
|
||||
"tetris/engine/render"
|
||||
"tetris/game/ui/base"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
const (
|
||||
sliderBorderSize = 4
|
||||
sliderWidth = 200
|
||||
sliderHeight = 30
|
||||
sliderTextSize = 12
|
||||
sliderTextOffset = 6
|
||||
)
|
||||
|
||||
type SliderOnChange func(this *Slider)
|
||||
|
||||
type Slider struct {
|
||||
*base.WithFocus
|
||||
Min int
|
||||
Max int
|
||||
Step int
|
||||
Value int
|
||||
borderSize int32
|
||||
onChange SliderOnChange
|
||||
}
|
||||
|
||||
func NewSlider(min, max, step int) *Slider {
|
||||
return &Slider{
|
||||
WithFocus: &base.WithFocus{},
|
||||
Min: min,
|
||||
Max: max,
|
||||
Step: step,
|
||||
borderSize: 4,
|
||||
}
|
||||
}
|
||||
|
||||
func (slider *Slider) WithOnChange(callback SliderOnChange) *Slider {
|
||||
slider.onChange = callback
|
||||
return slider
|
||||
}
|
||||
|
||||
func (slider Slider) Size() core.Vec2i {
|
||||
w := sliderWidth + (sliderBorderSize * 2)
|
||||
return core.Vec2i{
|
||||
X: w + sliderTextOffset + (sliderTextSize * len(slider.ValueText())),
|
||||
Y: sliderHeight + (sliderBorderSize * 2),
|
||||
}
|
||||
}
|
||||
|
||||
func (slider *Slider) HandleInput() {
|
||||
if input.KeyPressedWithRepeat(rl.KeyLeft) {
|
||||
slider.Decrement()
|
||||
} else if input.KeyPressedWithRepeat(rl.KeyRight) {
|
||||
slider.Increment()
|
||||
}
|
||||
}
|
||||
|
||||
func (slider *Slider) SetValue(value int) {
|
||||
if value != slider.Value {
|
||||
slider.Value = value
|
||||
slider.onChange(slider)
|
||||
}
|
||||
}
|
||||
|
||||
func (slider *Slider) Increment() {
|
||||
slider.SetValue(min(slider.Max, slider.Value+slider.Step))
|
||||
}
|
||||
|
||||
func (slider *Slider) Decrement() {
|
||||
slider.SetValue(max(slider.Min, slider.Value-slider.Step))
|
||||
}
|
||||
|
||||
func (slider Slider) Percent() float32 {
|
||||
return float32(slider.Min+slider.Value) / float32(slider.Max)
|
||||
}
|
||||
|
||||
func (slider Slider) drawTrack(rect rl.RectangleInt32) {
|
||||
spacing := int32(2)
|
||||
num_marks := int32(10)
|
||||
mark_width := float32(rect.Width-((num_marks+1)*spacing)) / float32(num_marks)
|
||||
|
||||
markRect := rl.Rectangle{
|
||||
X: float32(rect.X + spacing),
|
||||
Y: float32(rect.Y + spacing),
|
||||
Width: mark_width,
|
||||
Height: float32(rect.Height - (spacing * 2)),
|
||||
}
|
||||
|
||||
for range num_marks {
|
||||
rl.DrawRectangleRec(markRect, rl.DarkGray)
|
||||
markRect.X += mark_width + float32(spacing)
|
||||
}
|
||||
}
|
||||
|
||||
func (slider Slider) drawHandle(x, y, width int32, height int32, col color.RGBA) {
|
||||
handleWidth := int32(10)
|
||||
x = x + int32(float32(width-handleWidth-2)*slider.Percent())
|
||||
rl.DrawRectangle(x, y, handleWidth, height, col)
|
||||
}
|
||||
|
||||
func (slider Slider) ValueText() string {
|
||||
return fmt.Sprintf("%d", int(slider.Percent()*100))
|
||||
}
|
||||
|
||||
func (slider Slider) Draw(x, y int32) {
|
||||
// rl.DrawRectangle(x, y, int32(slider.Size().X), int32(slider.Size().Y), rl.Green)
|
||||
|
||||
// TODO: use a theme system here so colors are not hardcoded.
|
||||
handleColor := rl.White
|
||||
if slider.HasFocus() {
|
||||
handleColor = rl.Red
|
||||
}
|
||||
|
||||
rect := rl.RectangleInt32{
|
||||
X: x + sliderBorderSize,
|
||||
Y: y + sliderBorderSize,
|
||||
Width: sliderWidth,
|
||||
Height: sliderHeight,
|
||||
}
|
||||
|
||||
render.DrawRectOutlineBorder(rect, slider.borderSize, handleColor)
|
||||
slider.drawTrack(rect)
|
||||
slider.drawHandle(rect.X+1, rect.Y+1, int32(rect.Width), sliderHeight-2, handleColor)
|
||||
|
||||
textOffset := x + int32(rect.Width) + (sliderBorderSize * 2) + sliderTextOffset
|
||||
|
||||
render.DrawText(textOffset, rect.Y+8, sliderTextSize, slider.ValueText(), rl.White)
|
||||
}
|
||||
|
|
@ -22,6 +22,9 @@ func main() {
|
|||
})
|
||||
defer render.Exit()
|
||||
|
||||
// Dont close application when user presses escape
|
||||
rl.SetExitKey(rl.KeyNull)
|
||||
|
||||
// Set window icon
|
||||
if icon := graphics.LoadImageFromMemory(".png", assets.Icon); icon != nil {
|
||||
rl.SetWindowIcon(*icon)
|
||||
|
|
@ -42,6 +45,7 @@ func main() {
|
|||
// Setup state machine.
|
||||
fsm := machine.New()
|
||||
fsm.Register("menu", handlers.NewMainMenu(fsm))
|
||||
fsm.Register("options", handlers.NewOptionsMenu())
|
||||
fsm.Register("gameover", &handlers.GameOver{})
|
||||
fsm.Register("gameplay", handlers.NewGamePlay())
|
||||
fsm.Start("menu")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue