diff --git a/internal/config/config.go b/internal/config/config.go index d611364..69b3e69 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,7 +12,8 @@ type Ordoor struct { 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 { PlayMovies bool `toml:"play_movies"` Animations bool `toml:"animations"` diff --git a/internal/ordoor/interfaces.go b/internal/ordoor/interfaces.go index ff10b57..c76b528 100644 --- a/internal/ordoor/interfaces.go +++ b/internal/ordoor/interfaces.go @@ -47,16 +47,27 @@ func (o *Ordoor) optionsDriver(main *ui.Driver) (*ui.Driver, error) { return nil, err } - // TODO: load current options state into UI - try(options.OnClick("2.8", func() {}), &err) // Keyboard settings button - // Resolution slider is 2.9 - // Music volume slider is 2.10 - // Sound FX volume slider is 2.11 + h3Slider := map[int]int{1: 8, 2: 56, 3: 110} + v10Slider := map[int]int{ + 0: 0, + 10: 9, 20: 18, 30: 27, 40: 36, 50: 45, + 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) // 13...23 are "hypertext" try(options.OnClick("2.24", cancelOptionsFn(o, main, options)), &err) - // Unit speed slider is 2,26 - // Looping effect speed slider is 2,27 + try(options.ConfigureSlider("2.26", h9Slider), &err) // Unit speed slider + try(options.ConfigureSlider("2.27", h9Slider), &err) // Animation speed slider // Sample of unit speed animation is 2,28 // Sample of effect speed animation is 2,29 diff --git a/internal/ui/driver.go b/internal/ui/driver.go index fa0f423..f99adbc 100644 --- a/internal/ui/driver.go +++ b/internal/ui/driver.go @@ -4,6 +4,7 @@ import ( "fmt" "image" "log" + "strconv" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" @@ -18,7 +19,6 @@ func init() { // FIXME: these need further investigation / implementation registerBuilder(menus.TypeOverlay, nil) - registerBuilder(menus.TypeSlider, nil) } const ( @@ -56,6 +56,7 @@ type Driver struct { clickables []clickable freezables []freezable hoverables []hoverable + mouseables []mouseable paintables []paintable 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) } +// 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 { // This will be updated while processing hovers d.tooltip = "" @@ -190,6 +221,10 @@ func (d *Driver) Update(screenX, screenY int) error { d.mouseUpEvent(clickable, inBounds, mouseWasDown, mouseIsDown) } + for _, mouseable := range d.mouseables { + mouseable.registerMousePosition(d.cursorOrig) + } + return nil } diff --git a/internal/ui/interfaces.go b/internal/ui/interfaces.go index 73536e4..f8894e6 100644 --- a/internal/ui/interfaces.go +++ b/internal/ui/interfaces.go @@ -109,6 +109,19 @@ func (h *hoverImpl) setHoverState(hovering bool) { 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 type paintable interface { regions(tick int) []region diff --git a/internal/ui/selectors.go b/internal/ui/selectors.go index 8894073..e245b69 100644 --- a/internal/ui/selectors.go +++ b/internal/ui/selectors.go @@ -1,19 +1,43 @@ package ui import ( + "image" + "log" + "math" + "strconv" + + "code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/menus" ) func init() { registerBuilder(menus.TypeCheckbox, registerCheckbox) + registerBuilder(menus.TypeSlider, registerSlider) } +// A checkbox can be a fancy button type checkbox struct { button 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. func registerCheckbox(d *Driver, r *menus.Record) error { 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 } +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() { if c.value() == "1" { // Click disables c.setValue("0") @@ -60,3 +106,154 @@ func (c *checkbox) regions(tick int) []region { 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()) +}