Add a slider UI widget
I'm not too happy with how I have to configure the step for each one separately, but it's the best I can do at the moment.
This commit is contained in:
@@ -12,7 +12,8 @@ type Ordoor struct {
|
|||||||
VideoPlayer []string `toml:"video_player"`
|
VideoPlayer []string `toml:"video_player"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Things set
|
// Things set in the options hash
|
||||||
|
// TODO: load defaults from Data/GenericData.dat if they're not set
|
||||||
type Options struct {
|
type Options struct {
|
||||||
PlayMovies bool `toml:"play_movies"`
|
PlayMovies bool `toml:"play_movies"`
|
||||||
Animations bool `toml:"animations"`
|
Animations bool `toml:"animations"`
|
||||||
|
@@ -47,16 +47,27 @@ func (o *Ordoor) optionsDriver(main *ui.Driver) (*ui.Driver, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: load current options state into UI
|
h3Slider := map[int]int{1: 8, 2: 56, 3: 110}
|
||||||
try(options.OnClick("2.8", func() {}), &err) // Keyboard settings button
|
v10Slider := map[int]int{
|
||||||
// Resolution slider is 2.9
|
0: 0,
|
||||||
// Music volume slider is 2.10
|
10: 9, 20: 18, 30: 27, 40: 36, 50: 45,
|
||||||
// Sound FX volume slider is 2.11
|
60: 54, 70: 63, 80: 72, 90: 81, 100: 90,
|
||||||
|
}
|
||||||
|
h9Slider := map[int]int{
|
||||||
|
0: 0,
|
||||||
|
10: 10, 20: 20, 30: 30, 40: 40,
|
||||||
|
50: 50, 60: 60, 70: 70, 80: 80,
|
||||||
|
}
|
||||||
|
|
||||||
|
try(options.OnClick("2.8", func() {}), &err) // Keyboard settings button
|
||||||
|
try(options.ConfigureSlider("2.9", h3Slider), &err) // Resolution slider
|
||||||
|
try(options.ConfigureSlider("2.10", v10Slider), &err) // Music volume slider
|
||||||
|
try(options.ConfigureSlider("2.11", v10Slider), &err) // SFX volume slider
|
||||||
try(options.OnClick("2.12", acceptOptionsFn(o, main, options)), &err)
|
try(options.OnClick("2.12", acceptOptionsFn(o, main, options)), &err)
|
||||||
// 13...23 are "hypertext"
|
// 13...23 are "hypertext"
|
||||||
try(options.OnClick("2.24", cancelOptionsFn(o, main, options)), &err)
|
try(options.OnClick("2.24", cancelOptionsFn(o, main, options)), &err)
|
||||||
// Unit speed slider is 2,26
|
try(options.ConfigureSlider("2.26", h9Slider), &err) // Unit speed slider
|
||||||
// Looping effect speed slider is 2,27
|
try(options.ConfigureSlider("2.27", h9Slider), &err) // Animation speed slider
|
||||||
// Sample of unit speed animation is 2,28
|
// Sample of unit speed animation is 2,28
|
||||||
// Sample of effect speed animation is 2,29
|
// Sample of effect speed animation is 2,29
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
@@ -18,7 +19,6 @@ func init() {
|
|||||||
|
|
||||||
// FIXME: these need further investigation / implementation
|
// FIXME: these need further investigation / implementation
|
||||||
registerBuilder(menus.TypeOverlay, nil)
|
registerBuilder(menus.TypeOverlay, nil)
|
||||||
registerBuilder(menus.TypeSlider, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,6 +56,7 @@ type Driver struct {
|
|||||||
clickables []clickable
|
clickables []clickable
|
||||||
freezables []freezable
|
freezables []freezable
|
||||||
hoverables []hoverable
|
hoverables []hoverable
|
||||||
|
mouseables []mouseable
|
||||||
paintables []paintable
|
paintables []paintable
|
||||||
valueables []valueable
|
valueables []valueable
|
||||||
|
|
||||||
@@ -149,6 +150,36 @@ func (d *Driver) OnClick(id string, f func()) error {
|
|||||||
return fmt.Errorf("Couldn't find clickable widget %q", id)
|
return fmt.Errorf("Couldn't find clickable widget %q", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: HURK. Surely I'm missing something? steps is value:offset
|
||||||
|
func (d *Driver) ConfigureSlider(id string, steps map[int]int) error {
|
||||||
|
for _, clickable := range d.clickables {
|
||||||
|
if slider, ok := clickable.(*slider); ok && slider.id() == id {
|
||||||
|
slider.steps = steps
|
||||||
|
log.Printf("Found slider %#+v", slider)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Couldn't find slider %q", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) ValueInt(id string, into *int) error {
|
||||||
|
var vStr string
|
||||||
|
if err := d.Value(id, &vStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.Atoi(vStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*into = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) Update(screenX, screenY int) error {
|
func (d *Driver) Update(screenX, screenY int) error {
|
||||||
// This will be updated while processing hovers
|
// This will be updated while processing hovers
|
||||||
d.tooltip = ""
|
d.tooltip = ""
|
||||||
@@ -190,6 +221,10 @@ func (d *Driver) Update(screenX, screenY int) error {
|
|||||||
d.mouseUpEvent(clickable, inBounds, mouseWasDown, mouseIsDown)
|
d.mouseUpEvent(clickable, inBounds, mouseWasDown, mouseIsDown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, mouseable := range d.mouseables {
|
||||||
|
mouseable.registerMousePosition(d.cursorOrig)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -109,6 +109,19 @@ func (h *hoverImpl) setHoverState(hovering bool) {
|
|||||||
h.hovering = hovering
|
h.hovering = hovering
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mouseables are told where on the (original) screen the mouse cursor is
|
||||||
|
type mouseable interface {
|
||||||
|
registerMousePosition(image.Point)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mouseImpl struct {
|
||||||
|
pos image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mouseImpl) registerMousePosition(pt image.Point) {
|
||||||
|
m.pos = pt
|
||||||
|
}
|
||||||
|
|
||||||
// Paintable encapsulates one or more regions to be painted to the screen
|
// Paintable encapsulates one or more regions to be painted to the screen
|
||||||
type paintable interface {
|
type paintable interface {
|
||||||
regions(tick int) []region
|
regions(tick int) []region
|
||||||
|
@@ -1,19 +1,43 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
||||||
"code.ur.gs/lupine/ordoor/internal/menus"
|
"code.ur.gs/lupine/ordoor/internal/menus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerBuilder(menus.TypeCheckbox, registerCheckbox)
|
registerBuilder(menus.TypeCheckbox, registerCheckbox)
|
||||||
|
registerBuilder(menus.TypeSlider, registerSlider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A checkbox can be a fancy button
|
||||||
type checkbox struct {
|
type checkbox struct {
|
||||||
button
|
button
|
||||||
|
|
||||||
valueImpl
|
valueImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A slider is harder. Two separate elements to render
|
||||||
|
type slider struct {
|
||||||
|
path string
|
||||||
|
|
||||||
|
baseSpr *assetstore.Sprite
|
||||||
|
clickSpr *assetstore.Sprite
|
||||||
|
sliderSpr *assetstore.Sprite
|
||||||
|
|
||||||
|
hv bool // horizontal (false) or vertical (true) slider
|
||||||
|
steps map[int]int // A list of valid steps. value:offset
|
||||||
|
|
||||||
|
clickImpl
|
||||||
|
mouseImpl
|
||||||
|
valueImpl
|
||||||
|
}
|
||||||
|
|
||||||
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
|
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
|
||||||
func registerCheckbox(d *Driver, r *menus.Record) error {
|
func registerCheckbox(d *Driver, r *menus.Record) error {
|
||||||
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked
|
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked
|
||||||
@@ -41,6 +65,28 @@ func registerCheckbox(d *Driver, r *menus.Record) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerSlider(d *Driver, r *menus.Record) error {
|
||||||
|
sprites, err := d.menu.Sprites(r.Share, 3) // base, clicked, slider element
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slider := &slider{
|
||||||
|
path: r.Path(),
|
||||||
|
baseSpr: sprites[0],
|
||||||
|
clickSpr: sprites[1],
|
||||||
|
sliderSpr: sprites[2],
|
||||||
|
hv: sprites[0].Rect.Dy() > sprites[0].Rect.Dx(), // A best guess
|
||||||
|
}
|
||||||
|
|
||||||
|
d.clickables = append(d.clickables, slider)
|
||||||
|
d.mouseables = append(d.mouseables, slider)
|
||||||
|
d.paintables = append(d.paintables, slider)
|
||||||
|
d.valueables = append(d.valueables, slider)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *checkbox) registerMouseClick() {
|
func (c *checkbox) registerMouseClick() {
|
||||||
if c.value() == "1" { // Click disables
|
if c.value() == "1" { // Click disables
|
||||||
c.setValue("0")
|
c.setValue("0")
|
||||||
@@ -60,3 +106,154 @@ func (c *checkbox) regions(tick int) []region {
|
|||||||
|
|
||||||
return oneRegion(c.bounds().Min, c.baseSpr.Image)
|
return oneRegion(c.bounds().Min, c.baseSpr.Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *slider) id() string {
|
||||||
|
return s.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bounds of the slider are the whole thing
|
||||||
|
func (s *slider) bounds() image.Rectangle {
|
||||||
|
return s.baseSpr.Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) registerMouseClick() {
|
||||||
|
var value int
|
||||||
|
if s.hv {
|
||||||
|
value = s.valueFromPix(s.bounds().Min.Y, s.sliderPos().Y)
|
||||||
|
} else {
|
||||||
|
value = s.valueFromPix(s.bounds().Min.X, s.sliderPos().X)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.valueImpl.str = strconv.Itoa(value)
|
||||||
|
log.Printf("Slider value: %#v", s.valueImpl.str)
|
||||||
|
log.Printf("%#+v", s.steps)
|
||||||
|
|
||||||
|
s.clickImpl.registerMouseClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) regions(tick int) []region {
|
||||||
|
var out []region
|
||||||
|
|
||||||
|
if s.mouseDownState() {
|
||||||
|
out = append(out, oneRegion(s.bounds().Min, s.clickSpr.Image)...)
|
||||||
|
} else {
|
||||||
|
out = append(out, oneRegion(s.bounds().Min, s.baseSpr.Image)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, oneRegion(s.sliderPos(), s.sliderSpr.Image)...)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) sliderPos() image.Point {
|
||||||
|
if s.hv {
|
||||||
|
return s.sliderPosVertical()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.sliderPosHorizontal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) sliderPosHorizontal() image.Point {
|
||||||
|
pos := s.bounds().Min
|
||||||
|
|
||||||
|
if s.mouseDownState() {
|
||||||
|
pos.X = s.constrainPix(s.bounds().Min.X, s.mouseImpl.pos.X)
|
||||||
|
} else {
|
||||||
|
pos.X = s.bounds().Min.X + s.offsetFromValue(s.valueInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) sliderPosVertical() image.Point {
|
||||||
|
pos := s.bounds().Min
|
||||||
|
|
||||||
|
if s.mouseDownState() {
|
||||||
|
pos.Y = s.constrainPix(s.bounds().Min.Y, s.mouseImpl.pos.Y)
|
||||||
|
} else {
|
||||||
|
pos.Y = s.bounds().Min.Y + s.offsetFromValue(s.valueInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) valueFromPix(start, actual int) int {
|
||||||
|
if len(s.steps) == 0 {
|
||||||
|
return actual - start
|
||||||
|
}
|
||||||
|
|
||||||
|
minDistance := 9999
|
||||||
|
var out int
|
||||||
|
|
||||||
|
for value, offset := range s.steps {
|
||||||
|
pix := start + offset
|
||||||
|
distance := int(math.Abs(float64(actual - pix)))
|
||||||
|
|
||||||
|
if distance < minDistance {
|
||||||
|
minDistance = distance
|
||||||
|
out = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) offsetFromValue(value int) int {
|
||||||
|
if len(s.steps) == 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
value = s.constrainValue(value)
|
||||||
|
|
||||||
|
return s.steps[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) constrainPix(start, actual int) int {
|
||||||
|
if len(s.steps) == 0 {
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
|
||||||
|
minDistance := 9999
|
||||||
|
out := actual
|
||||||
|
|
||||||
|
for _, offset := range s.steps {
|
||||||
|
pix := start + offset
|
||||||
|
distance := int(math.Abs(float64(actual - pix)))
|
||||||
|
|
||||||
|
if distance < minDistance {
|
||||||
|
minDistance = distance
|
||||||
|
out = pix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
func (s *slider) constrainValue(actual int) int {
|
||||||
|
if len(s.steps) == 0 {
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
|
||||||
|
minDistance := 9999
|
||||||
|
out := actual
|
||||||
|
|
||||||
|
for value, _ := range s.steps {
|
||||||
|
distance := int(math.Abs(float64(value - actual)))
|
||||||
|
|
||||||
|
if distance < minDistance {
|
||||||
|
minDistance = distance
|
||||||
|
out = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) valueInt() int {
|
||||||
|
v, _ := strconv.Atoi(s.valueImpl.value())
|
||||||
|
|
||||||
|
return s.constrainValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *slider) value() string {
|
||||||
|
return strconv.Itoa(s.valueInt())
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user