From 6f2c212417459519cc11c85a6b92e231b0f9058e Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Mon, 24 Feb 2020 19:35:21 -0800 Subject: [PATCH] Continued work on GUI (#316) * Configuration cleanup * Cleanup * Continued UI work --- d2core/d2asset/font.go | 4 + d2core/d2gui/button.go | 131 +++++++++++++ d2core/d2gui/common.go | 46 +++++ d2core/d2gui/d2gui.go | 21 +- d2core/d2gui/label.go | 57 ++---- d2core/d2gui/layout.go | 308 +++++++++++++++++++++++++----- d2core/d2gui/manager.go | 35 +++- d2core/d2gui/spacer.go | 31 +++ d2core/d2gui/sprite.go | 63 ++---- d2core/d2gui/style.go | 34 +++- d2core/d2gui/widget.go | 76 +++++--- d2core/d2scene/d2scene.go | 2 +- d2game/d2gamescene/gui_testing.go | 49 +++++ main.go | 3 + 14 files changed, 668 insertions(+), 192 deletions(-) create mode 100644 d2core/d2gui/button.go create mode 100644 d2core/d2gui/common.go create mode 100644 d2core/d2gui/spacer.go create mode 100644 d2game/d2gamescene/gui_testing.go diff --git a/d2core/d2asset/font.go b/d2core/d2asset/font.go index 772a490d..c1fe7f3d 100644 --- a/d2core/d2asset/font.go +++ b/d2core/d2asset/font.go @@ -60,6 +60,10 @@ func loadFont(tablePath, spritePath, palettePath string) (*Font, error) { return font, nil } +func (f *Font) SetColor(color color.Color) { + f.color = color +} + func (f *Font) GetTextMetrics(text string) (int, int) { var ( lineWidth int diff --git a/d2core/d2gui/button.go b/d2core/d2gui/button.go new file mode 100644 index 00000000..a824ee27 --- /dev/null +++ b/d2core/d2gui/button.go @@ -0,0 +1,131 @@ +package d2gui + +import ( + "errors" + "image/color" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" +) + +type buttonState int + +const ( + buttonStateDefault buttonState = iota + buttonStatePressed + buttonStateToggled + buttonStatePressedToggled +) + +type Button struct { + widgetBase + + width int + height int + state buttonState + surfaces []d2render.Surface +} + +func createButton(text string, buttonStyle ButtonStyle) (*Button, error) { + config, ok := buttonStyleConfigs[buttonStyle] + if !ok { + return nil, errors.New("invalid button style") + } + + animation, err := d2asset.LoadAnimation(config.animationPath, config.palettePath) + if err != nil { + return nil, err + } + + var buttonWidth int + for i := 0; i < config.segmentsX; i++ { + w, _, err := animation.GetFrameSize(i) + if err != nil { + return nil, err + } + + buttonWidth += w + } + + var buttonHeight int + for i := 0; i < config.segmentsY; i++ { + _, h, err := animation.GetFrameSize(i * config.segmentsY) + if err != nil { + return nil, err + } + + buttonHeight += h + } + + font, err := loadFont(config.fontStyle) + if err != nil { + return nil, err + } + + textColor := color.RGBA{R: 0x64, G: 0x64, B: 0x64, A: 0xff} + textWidth, textHeight := font.GetTextMetrics(text) + textX := buttonWidth/2 - textWidth/2 + textY := buttonHeight/2 - textHeight/2 + config.textOffset + + surfaceCount := animation.GetFrameCount() / (config.segmentsX * config.segmentsY) + surfaces := make([]d2render.Surface, surfaceCount) + for i := 0; i < surfaceCount; i++ { + surface, err := d2render.NewSurface(buttonWidth, buttonHeight, d2render.FilterNearest) + if err != nil { + return nil, err + } + + if err := renderSegmented(animation, config.segmentsX, config.segmentsY, i, surface); err != nil { + return nil, err + } + + font.SetColor(textColor) + + var textOffsetX, textOffsetY int + switch buttonState(i) { + case buttonStatePressed, buttonStatePressedToggled: + textOffsetX = -2 + textOffsetY = 2 + break + } + + surface.PushTranslation(textX+textOffsetX, textY+textOffsetY) + err = font.RenderText(text, surface) + surface.Pop() + + if err != nil { + return nil, err + } + + surfaces[i] = surface + } + + button := &Button{width: buttonWidth, height: buttonHeight, surfaces: surfaces} + button.SetVisible(true) + + return button, nil +} + +func (b *Button) onMouseButtonDown(event d2input.MouseEvent) bool { + b.state = buttonStatePressed + return false +} + +func (b *Button) onMouseButtonUp(event d2input.MouseEvent) bool { + b.state = buttonStateDefault + return false +} + +func (b *Button) onMouseLeave(event d2input.MouseMoveEvent) bool { + b.state = buttonStateDefault + return false +} + +func (b *Button) render(target d2render.Surface) error { + return target.Render(b.surfaces[b.state]) +} + +func (b *Button) getSize() (int, int) { + return b.width, b.height +} diff --git a/d2core/d2gui/common.go b/d2core/d2gui/common.go new file mode 100644 index 00000000..8d561200 --- /dev/null +++ b/d2core/d2gui/common.go @@ -0,0 +1,46 @@ +package d2gui + +import ( + "errors" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" +) + +func loadFont(fontStyle FontStyle) (*d2asset.Font, error) { + config, ok := fontStyleConfigs[fontStyle] + if !ok { + return nil, errors.New("invalid font style") + } + + return d2asset.LoadFont(config.fontBasePath+".tbl", config.fontBasePath+".dc6", config.palettePath) +} + +func renderSegmented(animation *d2asset.Animation, segmentsX, segmentsY, frameOffset int, target d2render.Surface) error { + var currentY int + for y := 0; y < segmentsY; y++ { + var currentX int + var maxHeight int + for x := 0; x < segmentsX; x++ { + if err := animation.SetCurrentFrame(x + y*segmentsX + frameOffset*segmentsX*segmentsY); err != nil { + return err + } + + target.PushTranslation(x+currentX, y+currentY) + err := animation.Render(target) + target.Pop() + if err != nil { + return err + } + + width, height := animation.GetCurrentFrameSize() + maxHeight = d2common.MaxInt(maxHeight, height) + currentX += width + } + + currentY += maxHeight + } + + return nil +} diff --git a/d2core/d2gui/d2gui.go b/d2core/d2gui/d2gui.go index a9881a4e..5debb715 100644 --- a/d2core/d2gui/d2gui.go +++ b/d2core/d2gui/d2gui.go @@ -34,21 +34,14 @@ func Advance(elapsed float64) error { return singleton.advance(elapsed) } -func AddLayout() *Layout { - return singleton.addLayout() -} - -func AddSprite(imagePath, palettePath string) *Sprite { - return singleton.addSprite(imagePath, palettePath) -} - -func AddLabel(text string, fontStyle FontStyle) *Label { - return singleton.addLabel(text, fontStyle) -} - -func Clear() { +func CreateLayout(positionType PositionType) *Layout { verifyWasInit() - singleton.clear() + return createLayout(positionType) +} + +func SetLayout(layout *Layout) { + verifyWasInit() + singleton.SetLayout(layout) } func ShowLoadScreen(progress float64) { diff --git a/d2core/d2gui/label.go b/d2core/d2gui/label.go index 341252ce..fc919737 100644 --- a/d2core/d2gui/label.go +++ b/d2core/d2gui/label.go @@ -1,60 +1,41 @@ package d2gui import ( - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) type Label struct { widgetBase - text string - font *d2asset.Font surface d2render.Surface } -func createLabel(text string, fontStyle FontStyle) *Label { - font, _ := loadFont(fontStyle) - label := &Label{font: font} - label.SetText(text) - label.visible = true - return label -} +func createLabel(text string, fontStyle FontStyle) (*Label, error) { + font, err := loadFont(fontStyle) + if err != nil { + return nil, err + } -func (l *Label) SetText(text string) *Label { - l.text = text - l.cache() - return l + width, height := font.GetTextMetrics(text) + surface, err := d2render.NewSurface(width, height, d2render.FilterNearest) + if err != nil { + return nil, err + } + + if err := font.RenderText(text, surface); err != nil { + return nil, err + } + + label := &Label{surface: surface} + label.SetVisible(true) + + return label, nil } func (l *Label) render(target d2render.Surface) error { - if l.surface == nil { - return nil - } - return target.Render(l.surface) } -func (l *Label) cache() error { - l.surface = nil - if l.font == nil { - return nil - } - - width, height := l.font.GetTextMetrics(l.text) - - var err error - if l.surface, err = d2render.NewSurface(width, height, d2render.FilterNearest); err != nil { - return err - } - - return l.font.RenderText(l.text, l.surface) -} - func (l *Label) getSize() (int, int) { - if l.surface == nil { - return 0, 0 - } - return l.surface.GetSize() } diff --git a/d2core/d2gui/layout.go b/d2core/d2gui/layout.go index 67530385..0eb2bda5 100644 --- a/d2core/d2gui/layout.go +++ b/d2core/d2gui/layout.go @@ -3,6 +3,7 @@ package d2gui import ( "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) @@ -10,26 +11,136 @@ import ( type layoutEntry struct { widget widget + x int + y int + width int + height int + mouseOver bool mouseDown [3]bool } +type VerticalAlign int + +const ( + VerticalAlignTop VerticalAlign = iota + VerticalAlignMiddle + VerticalAlignBottom +) + +type HorizontalAlign int + +const ( + HorizontalAlignLeft HorizontalAlign = iota + HorizontalAlignCenter + HorizontalAlignRight +) + +type PositionType int + +const ( + PositionTypeAbsolute PositionType = iota + PositionTypeVertical + PositionTypeHorizontal +) + type Layout struct { widgetBase - entries []*layoutEntry + + width int + height int + verticalAlign VerticalAlign + horizontalAlign HorizontalAlign + positionType PositionType + entries []*layoutEntry } -func createLayout() *Layout { - layout := new(Layout) - layout.visible = true +func createLayout(positionType PositionType) *Layout { + layout := &Layout{positionType: positionType} + layout.SetVisible(true) + return layout } +func (l *Layout) SetSize(width, height int) { + l.width = width + l.height = height +} + +func (l *Layout) SetVerticalAlign(verticalAlign VerticalAlign) { + l.verticalAlign = verticalAlign +} + +func (l *Layout) SetHorizontalAlign(horizontalAlign HorizontalAlign) { + l.horizontalAlign = horizontalAlign +} + +func (l *Layout) AddLayout(positionType PositionType) *Layout { + layout := createLayout(positionType) + l.entries = append(l.entries, &layoutEntry{widget: layout}) + return layout +} + +func (l *Layout) AddSpacerStatic(width, height int) *SpacerStatic { + spacer := createSpacerStatic(width, height) + l.entries = append(l.entries, &layoutEntry{widget: spacer}) + return spacer +} + +func (l *Layout) AddSpacerDynamic() *SpacerDynamic { + spacer := createSpacerDynamic() + l.entries = append(l.entries, &layoutEntry{widget: spacer}) + return spacer +} + +func (l *Layout) AddSprite(imagePath, palettePath string) (*Sprite, error) { + sprite, err := createSprite(imagePath, palettePath) + if err != nil { + return nil, err + } + + l.entries = append(l.entries, &layoutEntry{widget: sprite}) + return sprite, nil +} + +func (l *Layout) AddLabel(text string, fontStyle FontStyle) (*Label, error) { + label, err := createLabel(text, fontStyle) + if err != nil { + return nil, err + } + + l.entries = append(l.entries, &layoutEntry{widget: label}) + return label, nil +} + +func (l *Layout) AddButton(text string, buttonStyle ButtonStyle) (*Button, error) { + button, err := createButton(text, buttonStyle) + if err != nil { + return nil, err + } + + l.entries = append(l.entries, &layoutEntry{widget: button}) + return button, nil +} + +func (l *Layout) Clear() { + l.entries = nil +} + func (l *Layout) render(target d2render.Surface) error { + l.adjustEntryPlacement() + for _, entry := range l.entries { - if entry.widget.isVisible() { - l.renderWidget(entry.widget, target) - l.renderWidgetDebug(entry.widget, target) + if !entry.widget.isVisible() { + continue + } + + if err := l.renderEntry(entry, target); err != nil { + return err + } + + if err := l.renderEntryDebug(entry, target); err != nil { + return err } } @@ -38,51 +149,88 @@ func (l *Layout) render(target d2render.Surface) error { func (l *Layout) advance(elapsed float64) error { for _, entry := range l.entries { - if entry.widget.isVisible() { - if err := entry.widget.advance(elapsed); err != nil { - return err - } + if err := entry.widget.advance(elapsed); err != nil { + return err } } return nil } -func (l *Layout) renderWidget(widget widget, target d2render.Surface) { - target.PushTranslation(widget.getPosition()) +func (l *Layout) renderEntry(entry *layoutEntry, target d2render.Surface) error { + target.PushTranslation(entry.x, entry.y) defer target.Pop() - widget.render(target) + return entry.widget.render(target) } -func (l *Layout) renderWidgetDebug(widget widget, target d2render.Surface) { - target.PushTranslation(widget.getPosition()) +func (l *Layout) renderEntryDebug(entry *layoutEntry, target d2render.Surface) error { + target.PushTranslation(entry.x, entry.y) defer target.Pop() - drawColor := color.RGBA{R: 0x00, G: 0x00, B: 0xff, A: 0xb0} + drawColor := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} + switch entry.widget.(type) { + case *Layout: + drawColor = color.RGBA{R: 0xff, G: 0x00, B: 0xff, A: 0xff} + case *SpacerStatic, *SpacerDynamic: + drawColor = color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff} + case *Label: + drawColor = color.RGBA{R: 0x00, G: 0x00, B: 0xff, A: 0xff} + case *Button: + drawColor = color.RGBA{R: 0xff, G: 0xff, B: 0x00, A: 0xff} + } - width, height := widget.getSize() - target.DrawLine(width, 0, drawColor) - target.DrawLine(0, height, drawColor) + target.DrawLine(entry.width, 0, drawColor) + target.DrawLine(0, entry.height, drawColor) - target.PushTranslation(width, 0) - target.DrawLine(0, height, drawColor) + target.PushTranslation(entry.width, 0) + target.DrawLine(0, entry.height, drawColor) target.Pop() - target.PushTranslation(0, height) - target.DrawLine(width, 0, drawColor) + target.PushTranslation(0, entry.height) + target.DrawLine(entry.width, 0, drawColor) target.Pop() + + return nil +} + +func (l *Layout) getContentSize() (int, int) { + var width, height int + + for _, entry := range l.entries { + x, y := entry.widget.getPosition() + w, h := entry.widget.getSize() + + switch l.positionType { + case PositionTypeVertical: + width = d2common.MaxInt(width, w) + height += h + break + case PositionTypeHorizontal: + width += w + height = d2common.MaxInt(height, h) + break + case PositionTypeAbsolute: + width = d2common.MaxInt(width, x+w) + height = d2common.MaxInt(height, y+h) + break + } + } + + return width, height } func (l *Layout) getSize() (int, int) { - return 0, 0 + width, height := l.getContentSize() + return d2common.MaxInt(width, l.width), d2common.MaxInt(height, l.height) } func (l *Layout) onMouseButtonDown(event d2input.MouseEvent) bool { for _, entry := range l.entries { eventLocal := event - if l.adjustEventCoords(entry.widget, &eventLocal.X, &eventLocal.Y) { + if l.adjustEntryEvent(entry, &eventLocal.X, &eventLocal.Y) { + entry.widget.onMouseButtonDown(eventLocal) entry.mouseDown[event.Button] = true } } @@ -94,9 +242,10 @@ func (l *Layout) onMouseButtonUp(event d2input.MouseEvent) bool { for _, entry := range l.entries { eventLocal := event - if l.adjustEventCoords(entry.widget, &eventLocal.X, &eventLocal.Y) { + if l.adjustEntryEvent(entry, &eventLocal.X, &eventLocal.Y) { if entry.mouseDown[event.Button] { - entry.widget.onMouseClick(eventLocal) + entry.widget.onMouseButtonClick(eventLocal) + entry.widget.onMouseButtonUp(eventLocal) } } @@ -110,7 +259,8 @@ func (l *Layout) onMouseMove(event d2input.MouseMoveEvent) bool { for _, entry := range l.entries { eventLocal := event - if l.adjustEventCoords(entry.widget, &eventLocal.X, &eventLocal.Y) { + if l.adjustEntryEvent(entry, &eventLocal.X, &eventLocal.Y) { + entry.widget.onMouseMove(eventLocal) if entry.mouseOver { entry.widget.onMouseOver(eventLocal) } else { @@ -126,38 +276,90 @@ func (l *Layout) onMouseMove(event d2input.MouseMoveEvent) bool { return false } -func (l *Layout) adjustEventCoords(widget widget, eventX, eventY *int) bool { - x, y := widget.getPosition() - width, height := widget.getSize() +func (l *Layout) adjustEntryEvent(entry *layoutEntry, eventX, eventY *int) bool { + *eventX -= entry.x + *eventY -= entry.y - *eventX -= x - *eventY -= y - - if *eventX < 0 || *eventY < 0 || *eventX >= width || *eventY >= height { + if *eventX < 0 || *eventY < 0 || *eventX >= entry.width || *eventY >= entry.height { return false } return true } -func (l *Layout) addLayout() *Layout { - layout := createLayout() - l.entries = append(l.entries, &layoutEntry{widget: layout}) - return layout -} +func (l *Layout) adjustEntryPlacement() { + width, height := l.getSize() -func (l *Layout) addSprite(imagePath, palettePath string) *Sprite { - sprite := createSprite(imagePath, palettePath) - l.entries = append(l.entries, &layoutEntry{widget: sprite}) - return sprite -} + var expanderCount int + for _, entry := range l.entries { + if entry.widget.isVisible() && entry.widget.isExpanding() { + expanderCount++ + } + } -func (l *Layout) addLabel(text string, fontStyle FontStyle) *Label { - label := createLabel(text, fontStyle) - l.entries = append(l.entries, &layoutEntry{widget: label}) - return label -} + var expanderWidth, expanderHeight int + if expanderCount > 0 { + contentWidth, contentHeight := l.getContentSize() -func (l *Layout) clear() { - l.entries = nil + switch l.positionType { + case PositionTypeVertical: + expanderHeight = (height - contentHeight) / expanderCount + break + case PositionTypeHorizontal: + expanderWidth = (width - contentWidth) / expanderCount + break + } + + expanderWidth = d2common.MaxInt(0, expanderWidth) + expanderHeight = d2common.MaxInt(0, expanderHeight) + } + + var offsetX, offsetY int + for _, entry := range l.entries { + if !entry.widget.isVisible() { + continue + } + + if entry.widget.isExpanding() { + entry.width, entry.height = expanderWidth, expanderHeight + } else { + entry.width, entry.height = entry.widget.getSize() + } + + switch l.positionType { + case PositionTypeVertical: + entry.y = offsetY + offsetY += entry.height + switch l.horizontalAlign { + case HorizontalAlignLeft: + entry.x = 0 + break + case HorizontalAlignCenter: + entry.x = width/2 - entry.width/2 + break + case HorizontalAlignRight: + entry.x = width - entry.width + break + } + break + case PositionTypeHorizontal: + entry.x = offsetX + offsetX += entry.width + switch l.verticalAlign { + case VerticalAlignTop: + entry.y = 0 + break + case VerticalAlignMiddle: + entry.y = height/2 - entry.height/2 + break + case VerticalAlignBottom: + entry.y = height - entry.height + break + } + break + case PositionTypeAbsolute: + entry.x, entry.y = entry.widget.getPosition() + break + } + } } diff --git a/d2core/d2gui/manager.go b/d2core/d2gui/manager.go index 1e94a4b7..9b95d114 100644 --- a/d2core/d2gui/manager.go +++ b/d2core/d2gui/manager.go @@ -11,7 +11,7 @@ import ( ) type manager struct { - Layout + layout *Layout cursorAnim *d2asset.Animation cursorX int @@ -46,19 +46,35 @@ func createGuiManager() (*manager, error) { return manager, nil } +func (m *manager) SetLayout(layout *Layout) { + m.layout = layout +} + func (m *manager) OnMouseButtonDown(event d2input.MouseEvent) bool { - return m.Layout.onMouseButtonDown(event) + if m.layout == nil { + return false + } + + return m.layout.onMouseButtonDown(event) } func (m *manager) OnMouseButtonUp(event d2input.MouseEvent) bool { - return m.Layout.onMouseButtonUp(event) + if m.layout == nil { + return false + } + + return m.layout.onMouseButtonUp(event) } func (m *manager) OnMouseMove(event d2input.MouseMoveEvent) bool { m.cursorX = event.X m.cursorY = event.Y - return m.Layout.onMouseMove(event) + if m.layout == nil { + return false + } + + return m.layout.onMouseMove(event) } func (m *manager) render(target d2render.Surface) error { @@ -66,8 +82,9 @@ func (m *manager) render(target d2render.Surface) error { if err := m.renderLoadScreen(target); err != nil { return err } - } else { - if err := m.Layout.render(target); err != nil { + } else if m.layout != nil { + m.layout.SetSize(target.GetSize()) + if err := m.layout.render(target); err != nil { return err } } @@ -103,8 +120,8 @@ func (m *manager) renderCursor(target d2render.Surface) error { } func (m *manager) advance(elapsed float64) error { - if !m.loading { - if err := m.Layout.advance(elapsed); err != nil { + if !m.loading && m.layout != nil { + if err := m.layout.advance(elapsed); err != nil { return err } } @@ -136,6 +153,6 @@ func (m *manager) hideCursor() { } func (m *manager) clear() { - m.Layout.clear() + m.SetLayout(nil) m.hideLoadScreen() } diff --git a/d2core/d2gui/spacer.go b/d2core/d2gui/spacer.go new file mode 100644 index 00000000..32b9d90a --- /dev/null +++ b/d2core/d2gui/spacer.go @@ -0,0 +1,31 @@ +package d2gui + +type SpacerStatic struct { + widgetBase + + width int + height int +} + +func createSpacerStatic(width, height int) *SpacerStatic { + spacer := &SpacerStatic{width: width, height: height} + spacer.SetVisible(true) + + return spacer +} + +func (s *SpacerStatic) getSize() (int, int) { + return s.width, s.height +} + +type SpacerDynamic struct { + widgetBase +} + +func createSpacerDynamic() *SpacerDynamic { + spacer := &SpacerDynamic{} + spacer.SetVisible(true) + spacer.SetExpanding(true) + + return spacer +} diff --git a/d2core/d2gui/sprite.go b/d2core/d2gui/sprite.go index e366b5c8..b955b7b2 100644 --- a/d2core/d2gui/sprite.go +++ b/d2core/d2gui/sprite.go @@ -1,7 +1,6 @@ package d2gui import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) @@ -16,11 +15,17 @@ type Sprite struct { animation *d2asset.Animation } -func createSprite(imagePath, palettePath string) *Sprite { - sprite := new(Sprite) - sprite.animation, _ = d2asset.LoadAnimation(imagePath, palettePath) - sprite.visible = true - return sprite +func createSprite(imagePath, palettePath string) (*Sprite, error) { + animation, err := d2asset.LoadAnimation(imagePath, palettePath) + if err != nil { + return nil, err + } + + sprite := &Sprite{} + sprite.animation = animation + sprite.SetVisible(true) + + return sprite, nil } func (s *Sprite) SetSegmented(segmentsX, segmentsY, frameOffset int) { @@ -30,57 +35,13 @@ func (s *Sprite) SetSegmented(segmentsX, segmentsY, frameOffset int) { } func (s *Sprite) render(target d2render.Surface) error { - if s.animation == nil { - return nil - } - - _, height := s.animation.GetCurrentFrameSize() - target.PushTranslation(0, -height) - defer target.Pop() - - if s.segmentsX == 0 && s.segmentsY == 0 { - return s.animation.Render(target) - } - - var currentY int - for y := 0; y < s.segmentsY; y++ { - var currentX int - var maxHeight int - for x := 0; x < s.segmentsX; x++ { - if err := s.animation.SetCurrentFrame(x + y*s.segmentsX + s.frameOffset*s.segmentsX*s.segmentsY); err != nil { - return err - } - - target.PushTranslation(s.x+currentX, s.y+currentY) - err := s.animation.Render(target) - target.Pop() - if err != nil { - return err - } - - width, height := s.animation.GetCurrentFrameSize() - maxHeight = d2common.MaxInt(maxHeight, height) - currentX += width - } - - currentY += maxHeight - } - - return nil + return renderSegmented(s.animation, s.segmentsX, s.segmentsY, s.frameOffset, target) } func (s *Sprite) advance(elapsed float64) error { - if s.animation == nil { - return nil - } - return s.animation.Advance(elapsed) } func (s *Sprite) getSize() (int, int) { - if s.animation == nil { - return 0, 0 - } - return s.animation.GetCurrentFrameSize() } diff --git a/d2core/d2gui/style.go b/d2core/d2gui/style.go index 2d0a8de4..ea66ec7c 100644 --- a/d2core/d2gui/style.go +++ b/d2core/d2gui/style.go @@ -2,7 +2,6 @@ package d2gui import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) type FontStyle int @@ -11,9 +10,11 @@ const ( FontStyle16Units FontStyle = iota FontStyle30Units FontStyle42Units + FontStyleExocet10 FontStyleFormal10Static FontStyleFormal11Units FontStyleFormal12Static + FontStyleRediculous ) type fontStyleConfig struct { @@ -25,12 +26,37 @@ var fontStyleConfigs = map[FontStyle]fontStyleConfig{ FontStyle16Units: {d2resource.Font16, d2resource.PaletteUnits}, FontStyle30Units: {d2resource.Font30, d2resource.PaletteUnits}, FontStyle42Units: {d2resource.Font42, d2resource.PaletteUnits}, + FontStyleExocet10: {d2resource.FontExocet10, d2resource.PaletteUnits}, FontStyleFormal10Static: {d2resource.FontFormal10, d2resource.PaletteStatic}, FontStyleFormal11Units: {d2resource.FontFormal11, d2resource.PaletteUnits}, FontStyleFormal12Static: {d2resource.FontFormal12, d2resource.PaletteStatic}, + FontStyleRediculous: {d2resource.FontRediculous, d2resource.PaletteUnits}, } -func loadFont(fontStyle FontStyle) (*d2asset.Font, error) { - config := fontStyleConfigs[fontStyle] - return d2asset.LoadFont(config.fontBasePath+".tbl", config.fontBasePath+".dc6", config.palettePath) +type ButtonStyle int + +type buttonStyleConfig struct { + segmentsX int + segmentsY int + animationPath string + palettePath string + fontStyle FontStyle + textOffset int +} + +const ( + ButtonStyleMedium ButtonStyle = iota + ButtonStyleNarrow + ButtonStyleOkCancel + ButtonStyleShort + ButtonStyleTall + ButtonStyleWide +) + +var buttonStyleConfigs = map[ButtonStyle]buttonStyleConfig{ + ButtonStyleMedium: {1, 1, d2resource.MediumButtonBlank, d2resource.PaletteUnits, FontStyleExocet10, 0}, + ButtonStyleOkCancel: {1, 1, d2resource.CancelButton, d2resource.PaletteUnits, FontStyleRediculous, 0}, + ButtonStyleShort: {1, 1, d2resource.ShortButtonBlank, d2resource.PaletteUnits, FontStyleRediculous, -1}, + ButtonStyleTall: {1, 1, d2resource.TallButtonBlank, d2resource.PaletteUnits, FontStyleExocet10, 5}, + ButtonStyleWide: {2, 1, d2resource.WideButtonBlank, d2resource.PaletteUnits, FontStyleExocet10, 1}, } diff --git a/d2core/d2gui/widget.go b/d2core/d2gui/widget.go index 58f5c56c..4d0bfdd8 100644 --- a/d2core/d2gui/widget.go +++ b/d2core/d2gui/widget.go @@ -12,26 +12,30 @@ type widget interface { render(target d2render.Surface) error advance(elapsed float64) error - onMouseEnter(event d2input.MouseMoveEvent) - onMouseLeave(event d2input.MouseMoveEvent) - onMouseOver(event d2input.MouseMoveEvent) - onMouseClick(event d2input.MouseEvent) + onMouseMove(event d2input.MouseMoveEvent) bool + onMouseEnter(event d2input.MouseMoveEvent) bool + onMouseLeave(event d2input.MouseMoveEvent) bool + onMouseOver(event d2input.MouseMoveEvent) bool + onMouseButtonDown(event d2input.MouseEvent) bool + onMouseButtonUp(event d2input.MouseEvent) bool + onMouseButtonClick(event d2input.MouseEvent) bool getPosition() (int, int) getSize() (int, int) getLayer() int isVisible() bool + isExpanding() bool } type widgetBase struct { - x int - y int - layer int - visible bool + x int + y int + layer int + visible bool + expanding bool mouseEnterHandler MouseMoveHandler mouseLeaveHandler MouseMoveHandler - mouseMoveHandler MouseMoveHandler mouseClickHandler MouseHandler } @@ -48,6 +52,10 @@ func (w *widgetBase) SetVisible(visible bool) { w.visible = visible } +func (w *widgetBase) SetExpanding(expanding bool) { + w.expanding = expanding +} + func (w *widgetBase) SetMouseEnterHandler(handler MouseMoveHandler) { w.mouseEnterHandler = handler } @@ -56,10 +64,6 @@ func (w *widgetBase) SetMouseLeaveHandler(handler MouseMoveHandler) { w.mouseLeaveHandler = handler } -func (w *widgetBase) SetMouseMoveHandler(handler MouseMoveHandler) { - w.mouseMoveHandler = handler -} - func (w *widgetBase) SetMouseClickHandler(handler MouseHandler) { w.mouseClickHandler = handler } @@ -68,6 +72,10 @@ func (w *widgetBase) getPosition() (int, int) { return w.x, w.y } +func (w *widgetBase) getSize() (int, int) { + return 0, 0 +} + func (w *widgetBase) getLayer() int { return w.layer } @@ -76,30 +84,54 @@ func (w *widgetBase) isVisible() bool { return w.visible } +func (w *widgetBase) isExpanding() bool { + return w.expanding +} + +func (w *widgetBase) render(target d2render.Surface) error { + return nil +} + func (w *widgetBase) advance(elapsed float64) error { return nil } -func (w *widgetBase) onMouseEnter(event d2input.MouseMoveEvent) { +func (w *widgetBase) onMouseEnter(event d2input.MouseMoveEvent) bool { if w.mouseEnterHandler != nil { w.mouseEnterHandler(event) } + + return false } -func (w *widgetBase) onMouseLeave(event d2input.MouseMoveEvent) { +func (w *widgetBase) onMouseLeave(event d2input.MouseMoveEvent) bool { if w.mouseLeaveHandler != nil { w.mouseLeaveHandler(event) } + + return false } -func (w *widgetBase) onMouseOver(event d2input.MouseMoveEvent) { - if w.mouseMoveHandler != nil { - w.mouseMoveHandler(event) - } -} - -func (w *widgetBase) onMouseClick(event d2input.MouseEvent) { +func (w *widgetBase) onMouseButtonClick(event d2input.MouseEvent) bool { if w.mouseClickHandler != nil { w.mouseClickHandler(event) } + + return false +} + +func (w *widgetBase) onMouseMove(event d2input.MouseMoveEvent) bool { + return false +} + +func (w *widgetBase) onMouseOver(event d2input.MouseMoveEvent) bool { + return false +} + +func (w *widgetBase) onMouseButtonDown(event d2input.MouseEvent) bool { + return false +} + +func (w *widgetBase) onMouseButtonUp(event d2input.MouseEvent) bool { + return false } diff --git a/d2core/d2scene/d2scene.go b/d2core/d2scene/d2scene.go index bbc67d8d..5fb7793b 100644 --- a/d2core/d2scene/d2scene.go +++ b/d2core/d2scene/d2scene.go @@ -43,7 +43,7 @@ func Advance(elapsed float64) error { } d2ui.Reset() - d2gui.Clear() + d2gui.SetLayout(nil) if _, ok := singleton.nextScene.(SceneLoadHandler); ok { d2gui.ShowLoadScreen(0) diff --git a/d2game/d2gamescene/gui_testing.go b/d2game/d2gamescene/gui_testing.go new file mode 100644 index 00000000..b4c54ac6 --- /dev/null +++ b/d2game/d2gamescene/gui_testing.go @@ -0,0 +1,49 @@ +package d2gamescene + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" +) + +type GuiTestMain struct{} + +func CreateGuiTestMain() *GuiTestMain { + return &GuiTestMain{} +} + +func (g *GuiTestMain) OnLoad() error { + layout := d2gui.CreateLayout(d2gui.PositionTypeHorizontal) + // + layoutLeft := layout.AddLayout(d2gui.PositionTypeVertical) + layoutLeft.SetHorizontalAlign(d2gui.HorizontalAlignCenter) + layoutLeft.AddLabel("FontStyle16Units", d2gui.FontStyle16Units) + layoutLeft.AddSpacerStatic(0, 100) + layoutLeft.AddLabel("FontStyle30Units", d2gui.FontStyle30Units) + layoutLeft.AddLabel("FontStyle42Units", d2gui.FontStyle42Units) + layoutLeft.AddLabel("FontStyleFormal10Static", d2gui.FontStyleFormal10Static) + layoutLeft.AddLabel("FontStyleFormal11Units", d2gui.FontStyleFormal11Units) + layoutLeft.AddLabel("FontStyleFormal12Static", d2gui.FontStyleFormal12Static) + + layout.AddSpacerDynamic() + + layoutRight := layout.AddLayout(d2gui.PositionTypeVertical) + layoutRight.SetHorizontalAlign(d2gui.HorizontalAlignRight) + layoutRight.AddButton("Medium", d2gui.ButtonStyleMedium) + layoutRight.AddButton("Narrow", d2gui.ButtonStyleNarrow) + layoutRight.AddButton("OkCancel", d2gui.ButtonStyleOkCancel) + layoutRight.AddButton("Short", d2gui.ButtonStyleShort) + layoutRight.AddButton("Wide", d2gui.ButtonStyleWide) + + layout.SetVerticalAlign(d2gui.VerticalAlignMiddle) + d2gui.SetLayout(layout) + + return nil +} + +func (g *GuiTestMain) Render(screen d2render.Surface) error { + return nil +} + +func (g *GuiTestMain) Advance(tickTime float64) error { + return nil +} diff --git a/main.go b/main.go index 8d056b85..197c0f1e 100644 --- a/main.go +++ b/main.go @@ -153,6 +153,9 @@ func initialize() error { d2term.BindAction("quit", "exits the game", func() { os.Exit(0) }) + d2term.BindAction("scene-gui", "enters the gui playground scene", func() { + d2scene.SetNextScene(d2gamescene.CreateGuiTestMain()) + }) if err := d2asset.Initialize(); err != nil { return err