From aa1fca84d5a0ad5537d98d9d4fd07ca319c6ef75 Mon Sep 17 00:00:00 2001 From: juander Date: Fri, 6 Nov 2020 11:48:49 +0100 Subject: [PATCH 1/9] d2core/ui: Introduce clickable widgets not all widgets need to be clickable, so let's make it it's own interface. --- d2core/d2ui/ui_manager.go | 35 ++++++++++++++++++++--------------- d2core/d2ui/widget.go | 7 ++++++- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/d2core/d2ui/ui_manager.go b/d2core/d2ui/ui_manager.go index f2030308..5137a697 100644 --- a/d2core/d2ui/ui_manager.go +++ b/d2core/d2ui/ui_manager.go @@ -12,16 +12,17 @@ import ( // UIManager manages a collection of UI elements (buttons, textboxes, labels) type UIManager struct { - asset *d2asset.AssetManager - renderer d2interface.Renderer - inputManager d2interface.InputManager - audio d2interface.AudioProvider - widgets []Widget - cursorButtons CursorButton - CursorX int - CursorY int - pressedWidget Widget - clickSfx d2interface.SoundEffect + asset *d2asset.AssetManager + renderer d2interface.Renderer + inputManager d2interface.InputManager + audio d2interface.AudioProvider + widgets []Widget + clickableWidgets []ClickableWidget + cursorButtons CursorButton + CursorX int + CursorY int + pressedWidget ClickableWidget + clickSfx d2interface.SoundEffect } // Note: methods for creating buttons and stuff are in their respective files @@ -44,6 +45,7 @@ func (ui *UIManager) Initialize() { // Reset resets the state of the UI manager. Typically called for new screens func (ui *UIManager) Reset() { ui.widgets = nil + ui.clickableWidgets = nil ui.pressedWidget = nil } @@ -54,8 +56,12 @@ func (ui *UIManager) addWidget(widget Widget) { log.Print(err) } - ui.widgets = append(ui.widgets, widget) + clickable, ok := widget.(ClickableWidget) + if ok { + ui.clickableWidgets = append(ui.clickableWidgets, clickable) + } + ui.widgets = append(ui.widgets, widget) widget.bindManager(ui) } @@ -67,13 +73,12 @@ func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool { // activate previously pressed widget if cursor is still hovering w := ui.pressedWidget - if w != nil && ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w. - GetEnabled() { + if w != nil && ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() { w.Activate() } // unpress all widgets that are pressed - for _, w := range ui.widgets { + for _, w := range ui.clickableWidgets { w.SetPressed(false) } } @@ -87,7 +92,7 @@ func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool { if event.Button() == d2enum.MouseButtonLeft { // find and press a widget on screen ui.pressedWidget = nil - for _, w := range ui.widgets { + for _, w := range ui.clickableWidgets { if ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() { w.SetPressed(true) ui.pressedWidget = w diff --git a/d2core/d2ui/widget.go b/d2core/d2ui/widget.go index 7e3307b2..f2ac7ea6 100644 --- a/d2core/d2ui/widget.go +++ b/d2core/d2ui/widget.go @@ -4,9 +4,14 @@ package d2ui type Widget interface { Drawable bindManager(ui *UIManager) - GetEnabled() bool +} + +// ClickableWidget defines an object that can be clicked +type ClickableWidget interface { + Widget SetEnabled(enabled bool) SetPressed(pressed bool) + GetEnabled() bool GetPressed() bool OnActivated(callback func()) Activate() From 881d5f1f71988a7a46bf04bfc310db699934398f Mon Sep 17 00:00:00 2001 From: juander Date: Fri, 6 Nov 2020 12:38:20 +0100 Subject: [PATCH 2/9] d2ui: Create default base widget this encapsulates all the repeating functions defined for all widgets in the same way, like Set/GetPosition(). --- d2core/d2ui/button.go | 49 +++------------------ d2core/d2ui/checkbox.go | 44 ++----------------- d2core/d2ui/drawable.go | 1 + d2core/d2ui/frame.go | 17 ++++---- d2core/d2ui/label.go | 26 ++++------- d2core/d2ui/scrollbar.go | 39 +++-------------- d2core/d2ui/sprite.go | 21 ++++----- d2core/d2ui/textbox.go | 39 ++++------------- d2core/d2ui/tooltip.go | 14 ++---- d2core/d2ui/widget.go | 57 +++++++++++++++++++++++++ d2game/d2gamescreen/character_select.go | 5 ++- d2game/d2gamescreen/credits.go | 7 ++- 12 files changed, 121 insertions(+), 198 deletions(-) diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index 9389fd7a..b9a647ba 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -242,32 +242,28 @@ var _ Widget = &Button{} // static check to ensure button implements widget // Button defines a standard wide UI button type Button struct { - manager *UIManager + *BaseWidget buttonLayout ButtonLayout normalSurface d2interface.Surface pressedSurface d2interface.Surface toggledSurface d2interface.Surface pressedToggledSurface d2interface.Surface disabledSurface d2interface.Surface - x int - y int - width int - height int onClick func() enabled bool - visible bool pressed bool toggled bool } // NewButton creates an instance of Button func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button { + base := NewBaseWidget(ui) + base.SetVisible(true) + btn := &Button{ - width: 0, - height: 0, - visible: true, - enabled: true, - pressed: false, + BaseWidget: base, + enabled: true, + pressed: false, } buttonLayout := getButtonLayouts()[buttonType] @@ -426,11 +422,6 @@ func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, lab } } -// bindManager binds the button to the UI manager -func (v *Button) bindManager(manager *UIManager) { - v.manager = manager -} - // OnActivated defines the callback handler for the activate event func (v *Button) OnActivated(callback func()) { v.onClick = callback @@ -495,32 +486,6 @@ func (v *Button) SetEnabled(enabled bool) { v.enabled = enabled } -// GetSize returns the size of the button -func (v *Button) GetSize() (width, height int) { - return v.width, v.height -} - -// SetPosition moves the button -func (v *Button) SetPosition(x, y int) { - v.x = x - v.y = y -} - -// GetPosition returns the location of the button -func (v *Button) GetPosition() (x, y int) { - return v.x, v.y -} - -// GetVisible returns the visibility of the button -func (v *Button) GetVisible() bool { - return v.visible -} - -// SetVisible sets the visibility of the button -func (v *Button) SetVisible(visible bool) { - v.visible = visible -} - // GetPressed returns the pressed state of the button func (v *Button) GetPressed() bool { return v.pressed diff --git a/d2core/d2ui/checkbox.go b/d2core/d2ui/checkbox.go index 21daa0c8..f2617fc2 100644 --- a/d2core/d2ui/checkbox.go +++ b/d2core/d2ui/checkbox.go @@ -10,16 +10,11 @@ import ( // Checkbox represents a checkbox UI element type Checkbox struct { - manager *UIManager + *BaseWidget Image d2interface.Surface checkedImage d2interface.Surface - x int - y int - width int - height int onClick func() checkState bool - visible bool enabled bool } @@ -27,11 +22,11 @@ type Checkbox struct { func (ui *UIManager) NewCheckbox(checkState bool) *Checkbox { var err error + base := NewBaseWidget(ui) + result := &Checkbox{ + BaseWidget: base, checkState: checkState, - visible: true, - width: 0, - height: 0, enabled: true, } @@ -70,11 +65,6 @@ func (ui *UIManager) NewCheckbox(checkState bool) *Checkbox { return result } -// bindManager binds the checkbox to the UI manager -func (v *Checkbox) bindManager(manager *UIManager) { - v.manager = manager -} - // Render renders the checkbox func (v *Checkbox) Render(target d2interface.Surface) error { target.PushTranslation(v.x, v.y) @@ -140,29 +130,3 @@ func (v *Checkbox) Activate() { v.onClick() } - -// GetPosition returns the position of the checkbox -func (v *Checkbox) GetPosition() (x, y int) { - return v.x, v.y -} - -// GetSize returns the size of the checkbox -func (v *Checkbox) GetSize() (width, height int) { - return v.width, v.height -} - -// GetVisible returns the visibility state of the checkbox -func (v *Checkbox) GetVisible() bool { - return v.visible -} - -// SetPosition sets the position of the checkbox -func (v *Checkbox) SetPosition(x, y int) { - v.x = x - v.y = y -} - -// SetVisible sets the visibility of the checkbox -func (v *Checkbox) SetVisible(visible bool) { - v.visible = visible -} diff --git a/d2core/d2ui/drawable.go b/d2core/d2ui/drawable.go index 09e0852d..277525b0 100644 --- a/d2core/d2ui/drawable.go +++ b/d2core/d2ui/drawable.go @@ -11,6 +11,7 @@ type Drawable interface { GetSize() (width, height int) SetPosition(x, y int) GetPosition() (x, y int) + OffsetPosition(xo, yo int) GetVisible() bool SetVisible(visible bool) } diff --git a/d2core/d2ui/frame.go b/d2core/d2ui/frame.go index 54275737..f5d7c828 100644 --- a/d2core/d2ui/frame.go +++ b/d2core/d2ui/frame.go @@ -19,11 +19,9 @@ const ( // UIFrame is a representation of a ui panel that occupies the left or right half of the screen // when it is visible. type UIFrame struct { + *BaseWidget asset *d2asset.AssetManager - uiManager *UIManager frame *Sprite - originX int - originY int frameOrientation frameOrientation } @@ -58,12 +56,13 @@ func NewUIFrame( originY = 0 } + base := NewBaseWidget(uiManager) + base.SetPosition(originX, originY) + frame := &UIFrame{ + BaseWidget: base, asset: asset, - uiManager: uiManager, frameOrientation: frameOrientation, - originX: originX, - originY: originY, } frame.Load() @@ -72,7 +71,7 @@ func NewUIFrame( // Load the necessary frame resources func (u *UIFrame) Load() { - sprite, err := u.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky) + sprite, err := u.manager.NewSprite(d2resource.Frame, d2resource.PaletteSky) if err != nil { log.Print(err) } @@ -105,7 +104,7 @@ func (u *UIFrame) renderLeft(target d2interface.Surface) error { // the frame coordinates coord := make(map[int]*struct{ x, y int }) - startX, startY := u.originX, u.originY + startX, startY := u.GetPosition() currentX, currentY := startX, startY // first determine the coordinates for each frame @@ -162,7 +161,7 @@ func (u *UIFrame) renderRight(target d2interface.Surface) error { // the frame coordinates coord := make(map[int]*struct{ x, y int }) - startX, startY := u.originX, u.originY + startX, startY := u.GetPosition() currentX, currentY := startX, startY // first determine the coordinates for each frame diff --git a/d2core/d2ui/label.go b/d2core/d2ui/label.go index cb16a6db..3fa4db4f 100644 --- a/d2core/d2ui/label.go +++ b/d2core/d2ui/label.go @@ -15,10 +15,8 @@ import ( // Label represents a user interface label type Label struct { - manager *UIManager + *BaseWidget text string - X int - Y int Alignment d2gui.HorizontalAlign font *d2asset.Font Color map[int]color.Color @@ -33,10 +31,13 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label { return nil } + base := NewBaseWidget(ui) + result := &Label{ - Alignment: d2gui.HorizontalAlignLeft, - Color: map[int]color.Color{0: color.White}, - font: font, + BaseWidget: base, + Alignment: d2gui.HorizontalAlignLeft, + Color: map[int]color.Color{0: color.White}, + font: font, } result.bindManager(ui) @@ -46,7 +47,7 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label { // Render draws the label on the screen, respliting the lines to allow for other alignments. func (v *Label) Render(target d2interface.Surface) { - target.PushTranslation(v.X, v.Y) + target.PushTranslation(v.GetPosition()) lines := strings.Split(v.text, "\n") yOffset := 0 @@ -91,17 +92,6 @@ func (v *Label) Render(target d2interface.Surface) { target.Pop() } -// bindManager binds the label to the UI manager -func (v *Label) bindManager(manager *UIManager) { - v.manager = manager -} - -// SetPosition moves the label to the specified location -func (v *Label) SetPosition(x, y int) { - v.X = x - v.Y = y -} - // GetSize returns the size of the label func (v *Label) GetSize() (width, height int) { return v.font.GetTextMetrics(v.text) diff --git a/d2core/d2ui/scrollbar.go b/d2core/d2ui/scrollbar.go index 7354ea6b..399f09ab 100644 --- a/d2core/d2ui/scrollbar.go +++ b/d2core/d2ui/scrollbar.go @@ -17,9 +17,7 @@ const ( // Scrollbar is a vertical slider ui element type Scrollbar struct { - manager *UIManager - x, y, height int - visible bool + *BaseWidget enabled bool currentOffset int maxOffset int @@ -36,12 +34,13 @@ func (ui *UIManager) NewScrollbar(x, y, height int) *Scrollbar { return nil } + base := NewBaseWidget(ui) + base.SetPosition(x, y) + base.height = height + result := &Scrollbar{ - visible: true, + BaseWidget: base, enabled: true, - x: x, - y: y, - height: height, scrollbarSprite: scrollbarSprite, } @@ -146,11 +145,6 @@ func (v *Scrollbar) Render(target d2interface.Surface) error { return nil } -// bindManager binds the scrollbar to the UI manager -func (v *Scrollbar) bindManager(manager *UIManager) { - v.manager = manager -} - // Advance advances the scrollbar sprite func (v *Scrollbar) Advance(elapsed float64) error { return v.scrollbarSprite.Advance(elapsed) @@ -161,27 +155,6 @@ func (v *Scrollbar) GetSize() (width, height int) { return scrollbarWidth, v.height } -// SetPosition sets the scrollbar x,y position -func (v *Scrollbar) SetPosition(x, y int) { - v.x = x - v.y = y -} - -// GetPosition returns the scrollbar x,y position -func (v *Scrollbar) GetPosition() (x, y int) { - return v.x, v.y -} - -// GetVisible returns whether or not the scrollbar is visible -func (v *Scrollbar) GetVisible() bool { - return v.visible -} - -// SetVisible sets the scrollbar visibility state -func (v *Scrollbar) SetVisible(visible bool) { - v.visible = visible -} - // SetMaxOffset sets the maximum offset of the scrollbar func (v *Scrollbar) SetMaxOffset(maxOffset int) { v.maxOffset = maxOffset diff --git a/d2core/d2ui/sprite.go b/d2core/d2ui/sprite.go index e1cc4f74..fd28bafb 100644 --- a/d2core/d2ui/sprite.go +++ b/d2core/d2ui/sprite.go @@ -13,8 +13,7 @@ import ( // Sprite is a positioned visual object. type Sprite struct { - x int - y int + *BaseWidget animation d2interface.Animation } @@ -31,7 +30,11 @@ func (ui *UIManager) NewSprite(animationPath, palettePath string) (*Sprite, erro animation.BindRenderer(ui.renderer) - return &Sprite{animation: animation}, nil + base := NewBaseWidget(ui) + + return &Sprite{ + BaseWidget: base, + animation: animation}, nil } // Render renders the sprite on the given surface @@ -80,15 +83,9 @@ func (s *Sprite) RenderSegmented(target d2interface.Surface, segmentsX, segments return nil } -// SetPosition places the sprite in 2D -func (s *Sprite) SetPosition(x, y int) { - s.x = x - s.y = y -} - -// GetPosition retrieves the 2D position of the sprite -func (s *Sprite) GetPosition() (x, y int) { - return s.x, s.y +// GetSize returns the size of the current frame +func (s *Sprite) GetSize() (width, height int) { + return s.GetCurrentFrameSize() } // GetFrameSize gets the Size(width, height) of a indexed frame. diff --git a/d2core/d2ui/textbox.go b/d2core/d2ui/textbox.go index 588effdd..0d104af5 100644 --- a/d2core/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -12,15 +12,12 @@ import ( // TextBox represents a text input box type TextBox struct { - manager *UIManager + *BaseWidget textLabel *Label lineBar *Label text string filter string - x int - y int bgSprite *Sprite - visible bool enabled bool isFocused bool } @@ -33,13 +30,15 @@ func (ui *UIManager) NewTextbox() *TextBox { return nil } + base := NewBaseWidget(ui) + tb := &TextBox{ - filter: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - bgSprite: bgSprite, - textLabel: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits), - lineBar: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits), - enabled: true, - visible: true, + BaseWidget: base, + filter: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + bgSprite: bgSprite, + textLabel: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits), + lineBar: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits), + enabled: true, } tb.lineBar.SetText("_") @@ -69,11 +68,6 @@ func (v *TextBox) Render(target d2interface.Surface) error { return nil } -// bindManager binds the textbox to the UI manager -func (v *TextBox) bindManager(manager *UIManager) { - v.manager = manager -} - // OnKeyChars handles key character events func (v *TextBox) OnKeyChars(event d2interface.KeyCharsEvent) bool { if !v.isFocused || !v.visible || !v.enabled { @@ -189,21 +183,6 @@ func (v *TextBox) SetPosition(x, y int) { v.bgSprite.SetPosition(v.x, v.y+26) } -// GetPosition returns the position of the text box -func (v *TextBox) GetPosition() (x, y int) { - return v.x, v.y -} - -// GetVisible returns the visibility of the text box -func (v *TextBox) GetVisible() bool { - return v.visible -} - -// SetVisible sets the visibility of the text box -func (v *TextBox) SetVisible(visible bool) { - v.visible = visible -} - // GetEnabled returns the enabled state of the text box func (v *TextBox) GetEnabled() bool { return v.enabled diff --git a/d2core/d2ui/tooltip.go b/d2core/d2ui/tooltip.go index af3489ae..6f514645 100644 --- a/d2core/d2ui/tooltip.go +++ b/d2core/d2ui/tooltip.go @@ -16,11 +16,10 @@ const ( // Tooltip contains a label containing text with a transparent, black background type Tooltip struct { - manager *UIManager + *BaseWidget lines []string label *Label backgroundColor int - x, y int originX tooltipXOrigin originY tooltipYOrigin boxEnabled bool @@ -56,11 +55,12 @@ func (ui *UIManager) NewTooltip(font, label := ui.NewLabel(font, palette) label.Alignment = d2gui.HorizontalAlignCenter + base := NewBaseWidget(ui) + res := &Tooltip{ + BaseWidget: base, backgroundColor: blackAlpha70, label: label, - x: 0, - y: 0, originX: originX, originY: originY, boxEnabled: true, @@ -70,12 +70,6 @@ func (ui *UIManager) NewTooltip(font, return res } -// SetPosition sets the position of the origin point of the tooltip -func (t *Tooltip) SetPosition(x, y int) { - t.x = x - t.y = y -} - // SetTextLines sets the tooltip text in the form of an array of strings func (t *Tooltip) SetTextLines(lines []string) { t.lines = lines diff --git a/d2core/d2ui/widget.go b/d2core/d2ui/widget.go index f2ac7ea6..5466583a 100644 --- a/d2core/d2ui/widget.go +++ b/d2core/d2ui/widget.go @@ -16,3 +16,60 @@ type ClickableWidget interface { OnActivated(callback func()) Activate() } + +// BaseWidget contains default functionality that all widgets share +type BaseWidget struct { + manager *UIManager + x int + y int + width int + height int + visible bool +} + +// NewBaseWidget creates a new BaseWidget with defaults +func NewBaseWidget(manager *UIManager) *BaseWidget { + return &BaseWidget{ + manager: manager, + x: 0, + y: 0, + width: 0, + height: 0, + visible: true, + } +} + +func (b *BaseWidget) bindManager(manager *UIManager) { + b.manager = manager +} + +// GetSize returns the size of the widget +func (b *BaseWidget) GetSize() (width, height int) { + return b.width, b.height +} + +// SetPosition sets the position of the widget +func (b *BaseWidget) SetPosition(x, y int) { + b.x, b.y = x, y +} + +// OffsetPosition moves the widget by x and y +func (b *BaseWidget) OffsetPosition(x, y int) { + b.x += x + b.y += y +} + +// GetPosition returns the position of the widget +func (b *BaseWidget) GetPosition() (x, y int) { + return b.x, b.y +} + +// GetVisible returns whether the widget is visible +func (b *BaseWidget) GetVisible() (visible bool) { + return b.visible +} + +// SetVisible make the widget visible, not visible +func (b *BaseWidget) SetVisible(visible bool) { + b.visible = visible +} diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index 160880a4..76b74b65 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -392,8 +392,9 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) { v.characterStatsLabel[i].Render(screen) v.characterExpLabel[i].Render(screen) - charImgX := v.characterNameLabel[i].X - selectionBoxImageOffsetX - charImgY := v.characterNameLabel[i].Y + selectionBoxImageOffsetY + x, y := v.characterNameLabel[i].GetPosition() + charImgX := x - selectionBoxImageOffsetX + charImgY := y + selectionBoxImageOffsetY screen.PushTranslation(charImgX, charImgY) v.characterImage[i].Render(screen) screen.Pop() diff --git a/d2game/d2gamescreen/credits.go b/d2game/d2gamescreen/credits.go index fa3afbde..3664883a 100644 --- a/d2game/d2gamescreen/credits.go +++ b/d2game/d2gamescreen/credits.go @@ -159,11 +159,14 @@ func (v *Credits) Advance(tickTime float64) error { continue } - if label.Label.Y-1 < -15 { + _, y := label.Label.GetPosition() + + if y-1 < -15 { label.Available = true continue } - label.Label.Y-- + + label.Label.OffsetPosition(0, -1) } } From 627bc8ec651fcdc78219c34b0b4dd48cea6b880d Mon Sep 17 00:00:00 2001 From: juander Date: Fri, 6 Nov 2020 13:28:24 +0100 Subject: [PATCH 3/9] d2ui: Add WidgetGroup this allows us to groups screens together, such that they can be de-/activated by de-/activating the group instead of every ui element by hand. Before we were deactivating buttons and stopped rendering to deactivate ui elements. This tied the renderer to these elements. --- d2core/d2ui/drawable.go | 2 + d2core/d2ui/ui_manager.go | 19 ++++++++ d2core/d2ui/widget.go | 47 ++++++++++++++----- d2core/d2ui/widget_group.go | 92 +++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 d2core/d2ui/widget_group.go diff --git a/d2core/d2ui/drawable.go b/d2core/d2ui/drawable.go index 277525b0..dc623ff9 100644 --- a/d2core/d2ui/drawable.go +++ b/d2core/d2ui/drawable.go @@ -14,4 +14,6 @@ type Drawable interface { OffsetPosition(xo, yo int) GetVisible() bool SetVisible(visible bool) + SetRenderPriority(priority RenderPriority) + GetRenderPriority() (priority RenderPriority) } diff --git a/d2core/d2ui/ui_manager.go b/d2core/d2ui/ui_manager.go index 5137a697..708cb02b 100644 --- a/d2core/d2ui/ui_manager.go +++ b/d2core/d2ui/ui_manager.go @@ -2,6 +2,7 @@ package d2ui import ( "log" + "sort" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" @@ -17,6 +18,7 @@ type UIManager struct { inputManager d2interface.InputManager audio d2interface.AudioProvider widgets []Widget + widgetsGroups []*WidgetGroup clickableWidgets []ClickableWidget cursorButtons CursorButton CursorX int @@ -49,6 +51,14 @@ func (ui *UIManager) Reset() { ui.pressedWidget = nil } +// addWidgetGroup adds a widgetGroup to the UI manager and sorts by priority +func (ui *UIManager) addWidgetGroup(group *WidgetGroup) { + ui.widgetsGroups = append(ui.widgetsGroups, group) + sort.SliceStable(ui.widgetsGroups, func(i, j int) bool { + return ui.widgetsGroups[i].priority < ui.widgetsGroups[j].priority + }) +} + // addWidget adds a widget to the UI manager func (ui *UIManager) addWidget(widget Widget) { err := ui.inputManager.BindHandler(widget) @@ -120,6 +130,15 @@ func (ui *UIManager) Render(target d2interface.Surface) { } } } + + for _, widgetGroup := range ui.widgetsGroups { + if widgetGroup.GetVisible() { + err := widgetGroup.Render(target) + if err != nil { + log.Print(err) + } + } + } } // contains determines whether a given x,y coordinate lands within a Widget diff --git a/d2core/d2ui/widget.go b/d2core/d2ui/widget.go index 5466583a..2c23959e 100644 --- a/d2core/d2ui/widget.go +++ b/d2core/d2ui/widget.go @@ -1,5 +1,16 @@ package d2ui +// RenderPriority determines in which order ui elements are drawn. +// The higher the number the later an element is drawn. +type RenderPriority int + +const ( + // RenderPriorityBackground is the first element drawn + RenderPriorityBackground RenderPriority = iota + // RenderPriorityForeground is the last element drawn + RenderPriorityForeground +) + // Widget defines an object that is a UI widget type Widget interface { Drawable @@ -19,23 +30,25 @@ type ClickableWidget interface { // BaseWidget contains default functionality that all widgets share type BaseWidget struct { - manager *UIManager - x int - y int - width int - height int - visible bool + manager *UIManager + x int + y int + width int + height int + renderPriority RenderPriority + visible bool } // NewBaseWidget creates a new BaseWidget with defaults func NewBaseWidget(manager *UIManager) *BaseWidget { return &BaseWidget{ - manager: manager, - x: 0, - y: 0, - width: 0, - height: 0, - visible: true, + manager: manager, + x: 0, + y: 0, + width: 0, + height: 0, + visible: true, + renderPriority: RenderPriorityBackground, } } @@ -73,3 +86,13 @@ func (b *BaseWidget) GetVisible() (visible bool) { func (b *BaseWidget) SetVisible(visible bool) { b.visible = visible } + +// GetRenderPriority returns the order in which this widget is rendered +func (b *BaseWidget) GetRenderPriority() (prio RenderPriority) { + return b.renderPriority +} + +// SetRenderPriority sets the order in which this widget is rendered +func (b *BaseWidget) SetRenderPriority(prio RenderPriority) { + b.renderPriority = prio +} diff --git a/d2core/d2ui/widget_group.go b/d2core/d2ui/widget_group.go new file mode 100644 index 00000000..da55cd63 --- /dev/null +++ b/d2core/d2ui/widget_group.go @@ -0,0 +1,92 @@ +package d2ui + +import ( + "sort" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" +) + +// static check that WidgetGroup implements widget +var _ Widget = &WidgetGroup{} + +// WidgetGroup allows the grouping of widgets to apply actions to all +// widgets at once. +type WidgetGroup struct { + *BaseWidget + entries []Widget + priority RenderPriority +} + +// NewWidgetGroup creates a new widget group +func (ui *UIManager) NewWidgetGroup(priority RenderPriority) *WidgetGroup { + base := NewBaseWidget(ui) + base.SetRenderPriority(priority) + + group := &WidgetGroup{ + BaseWidget: base, + } + + ui.addWidgetGroup(group) + + return group +} + +// AddWidget adds a widget to the group +func (wg *WidgetGroup) AddWidget(w Widget) { + wg.adjustSize(w) + wg.entries = append(wg.entries, w) + sort.SliceStable(wg.entries, func(i, j int) bool { + return wg.entries[i].GetRenderPriority() < wg.entries[j].GetRenderPriority() + }) +} + +// adjustSize recalculates the bounding box if a new widget is added +func (wg *WidgetGroup) adjustSize(w Widget) { + x, y := w.GetPosition() + width, height := w.GetSize() + + if x+width > wg.width { + wg.width = x + width + } + + if wg.x > x { + wg.width += wg.x - x + wg.x = x + } + + if y+height > wg.height { + wg.height = x + height + } + + if wg.y > y { + wg.height += wg.y - y + wg.y = y + } +} + +// Advance is a no-op here +func (wg *WidgetGroup) Advance(elapsed float64) error { + // No-op + return nil +} + +// Render draw the widgets to the screen +func (wg *WidgetGroup) Render(target d2interface.Surface) error { + for _, entry := range wg.entries { + if entry.GetVisible() { + err := entry.Render(target) + if err != nil { + return err + } + } + } + + return nil +} + +// SetVisible sets the visibility of all widgets in the group +func (wg *WidgetGroup) SetVisible(visible bool) { + for _, entry := range wg.entries { + entry.SetVisible(visible) + } +} From bad07defe80e597b42c4cd93f18930b2db294d8c Mon Sep 17 00:00:00 2001 From: juander Date: Mon, 9 Nov 2020 12:34:56 +0100 Subject: [PATCH 4/9] d2ui: Add a custom widget this allows us to encapsulate any custom render functionality into this custom widget. It further helps us to remove the renderer from game_controls. --- d2core/d2ui/custom_widget.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 d2core/d2ui/custom_widget.go diff --git a/d2core/d2ui/custom_widget.go b/d2core/d2ui/custom_widget.go new file mode 100644 index 00000000..0b3899bb --- /dev/null +++ b/d2core/d2ui/custom_widget.go @@ -0,0 +1,32 @@ +package d2ui + +import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + +// static check that CustomWidget implements widget +var _ Widget = &CustomWidget{} + +// CustomWidget is a widget with a fully custom render function +type CustomWidget struct { + *BaseWidget + renderFunc func(target d2interface.Surface) error +} + +// NewCustomWidget creates a new widget with custom render function +func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface) error) *CustomWidget { + base := NewBaseWidget(ui) + + return &CustomWidget{ + BaseWidget: base, + renderFunc: renderFunc, + } +} + +// Render draws the custom widget +func (c *CustomWidget) Render(target d2interface.Surface) error { + return c.renderFunc(target) +} + +// Advance is a no-op +func (c *CustomWidget) Advance(elapsed float64) error { + return nil +} From 01927d0f3b0a51f139abf49cc9bed612e53b419b Mon Sep 17 00:00:00 2001 From: juander Date: Mon, 9 Nov 2020 15:46:08 +0100 Subject: [PATCH 5/9] d2core/d2ui: Add checks to all widgets if they implement Widget this also adds missing methods to elements not implementing widget. Note here that we do not enable sprite and label, as this would produce a crazy amount of linter warnings due to render() requiering error handling then, which non of the callers handle. Since we remove the render calls later anyways, we can postpone this static check for now. --- d2core/d2ui/checkbox.go | 3 +++ d2core/d2ui/frame.go | 8 +++++++ d2core/d2ui/label.go | 5 +++++ d2core/d2ui/scrollbar.go | 3 +++ d2core/d2ui/textbox.go | 3 +++ d2core/d2ui/tooltip.go | 12 ++++++++++- d2game/d2player/hud.go | 31 +++++++++++++++++++++------ d2game/d2player/inventory.go | 5 ++++- d2game/d2player/skill_select_panel.go | 4 +++- 9 files changed, 65 insertions(+), 9 deletions(-) diff --git a/d2core/d2ui/checkbox.go b/d2core/d2ui/checkbox.go index f2617fc2..fb7fd6e7 100644 --- a/d2core/d2ui/checkbox.go +++ b/d2core/d2ui/checkbox.go @@ -8,6 +8,9 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" ) +// static check that Checkbox implements Widget +var _ Widget = &Checkbox{} + // Checkbox represents a checkbox UI element type Checkbox struct { *BaseWidget diff --git a/d2core/d2ui/frame.go b/d2core/d2ui/frame.go index f5d7c828..735db3b0 100644 --- a/d2core/d2ui/frame.go +++ b/d2core/d2ui/frame.go @@ -8,6 +8,9 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) +// static check that UIFrame implements Widget +var _ Widget = &UIFrame{} + type frameOrientation = int // Frame orientations @@ -228,3 +231,8 @@ func (u *UIFrame) renderFramePiece(sfc d2interface.Surface, x, y, idx int) error return nil } + +// Advance is a no-op +func (u *UIFrame) Advance(elapsed float64) error { + return nil +} diff --git a/d2core/d2ui/label.go b/d2core/d2ui/label.go index 3fa4db4f..aa768ee1 100644 --- a/d2core/d2ui/label.go +++ b/d2core/d2ui/label.go @@ -162,6 +162,11 @@ func (v *Label) getAlignOffset(textWidth int) int { } } +// Advance is a no-op +func (v *Label) Advance(elapsed float64) error { + return nil +} + func getColor(token ColorToken) color.Color { // https://github.com/OpenDiablo2/OpenDiablo2/issues/823 colors := map[ColorToken]color.Color{ diff --git a/d2core/d2ui/scrollbar.go b/d2core/d2ui/scrollbar.go index 399f09ab..18268663 100644 --- a/d2core/d2ui/scrollbar.go +++ b/d2core/d2ui/scrollbar.go @@ -15,6 +15,9 @@ const ( scrollbarWidth = 10 ) +// static check that Scrollbar implements widget +var _ Widget = &Scrollbar{} + // Scrollbar is a vertical slider ui element type Scrollbar struct { *BaseWidget diff --git a/d2core/d2ui/textbox.go b/d2core/d2ui/textbox.go index 0d104af5..256ac2f7 100644 --- a/d2core/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -10,6 +10,9 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" ) +// static check that TextBox implements widget +var _ Widget = &TextBox{} + // TextBox represents a text input box type TextBox struct { *BaseWidget diff --git a/d2core/d2ui/tooltip.go b/d2core/d2ui/tooltip.go index 6f514645..0cb4fc89 100644 --- a/d2core/d2ui/tooltip.go +++ b/d2core/d2ui/tooltip.go @@ -14,6 +14,9 @@ const ( screenHeight = 600 ) +// static check that Tooltip implements widget +var _ Widget = &Tooltip{} + // Tooltip contains a label containing text with a transparent, black background type Tooltip struct { *BaseWidget @@ -137,7 +140,7 @@ func (t *Tooltip) GetSize() (sx, sy int) { } // Render draws the tooltip -func (t *Tooltip) Render(target d2interface.Surface) { +func (t *Tooltip) Render(target d2interface.Surface) error { maxW, maxH := t.GetSize() // nolint:gomnd // no magic numbers, their meaning is obvious @@ -189,4 +192,11 @@ func (t *Tooltip) Render(target d2interface.Surface) { } target.PopN(len(t.lines)) + + return nil +} + +// Advance is a no-op +func (t *Tooltip) Advance(elapsed float64) error { + return nil } diff --git a/d2game/d2player/hud.go b/d2game/d2player/hud.go index 290ef07f..390c7c3f 100644 --- a/d2game/d2player/hud.go +++ b/d2game/d2player/hud.go @@ -595,7 +595,10 @@ func (h *HUD) renderMiniPanel(target d2interface.Surface) error { labelY := centerY - halfButtonHeight - labelHeight h.miniPanelTooltip.SetPosition(labelX, labelY) - h.miniPanelTooltip.Render(target) + + if err := h.miniPanelTooltip.Render(target); err != nil { + return err + } } return nil @@ -627,6 +630,7 @@ func (h *HUD) renderNewSkillsButton(x, _ int, target d2interface.Surface) error return nil } +//nolint:golint,dupl // we clean this up later func (h *HUD) renderHealthTooltip(target d2interface.Surface) { mx, my := h.lastMouseX, h.lastMouseY @@ -641,9 +645,13 @@ func (h *HUD) renderHealthTooltip(target d2interface.Surface) { } h.healthTooltip.SetText(strPanelHealth) - h.healthTooltip.Render(target) + + if err := h.healthTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } +//nolint:golint,dupl // we clean this up later func (h *HUD) renderManaTooltip(target d2interface.Surface) { mx, my := h.lastMouseX, h.lastMouseY @@ -657,7 +665,10 @@ func (h *HUD) renderManaTooltip(target d2interface.Surface) { } h.manaTooltip.SetText(strPanelMana) - h.manaTooltip.Render(target) + + if err := h.manaTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } func (h *HUD) renderRunWalkTooltip(target d2interface.Surface) { @@ -679,7 +690,9 @@ func (h *HUD) renderRunWalkTooltip(target d2interface.Surface) { h.runWalkTooltip.SetText(h.asset.TranslateString(stringTableKey)) - h.runWalkTooltip.Render(target) + if err := h.runWalkTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } func (h *HUD) renderStaminaTooltip(target d2interface.Surface) { @@ -696,7 +709,10 @@ func (h *HUD) renderStaminaTooltip(target d2interface.Surface) { strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax) h.staminaTooltip.SetText(strPanelStamina) - h.staminaTooltip.Render(target) + + if err := h.staminaTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } func (h *HUD) renderExperienceTooltip(target d2interface.Surface) { @@ -720,7 +736,10 @@ func (h *HUD) renderExperienceTooltip(target d2interface.Surface) { strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax) h.experienceTooltip.SetText(strPanelExp) - h.experienceTooltip.Render(target) + + if err := h.experienceTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) { diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index 1b3352cc..56e7fa75 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -242,5 +242,8 @@ func (g *Inventory) renderItemDescription(target d2interface.Surface, i Inventor g.itemTooltip.SetTextLines(lines) _, y := g.grid.SlotToScreen(i.InventoryGridSlot()) g.itemTooltip.SetPosition(g.hoverX, y) - g.itemTooltip.Render(target) + + if err := g.itemTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } diff --git a/d2game/d2player/skill_select_panel.go b/d2game/d2player/skill_select_panel.go index d5785c1e..3aa6f5a9 100644 --- a/d2game/d2player/skill_select_panel.go +++ b/d2game/d2player/skill_select_panel.go @@ -129,7 +129,9 @@ func (s *SkillPanel) Render(target d2interface.Surface) error { } if s.hoveredSkill != nil { - s.hoverTooltip.Render(target) + if err := s.hoverTooltip.Render(target); err != nil { + log.Printf("Cannot render tooltip, %e", err) + } } return nil From f2a55312e41a774e2d2bcb6e070547c888021b3f Mon Sep 17 00:00:00 2001 From: juander Date: Mon, 9 Nov 2020 17:55:34 +0100 Subject: [PATCH 6/9] d2ui/label: Refactor Render() to RenderNoError() this allows us to create a Render() method that implements the Widget interface without killing us with linter warnings. --- d2core/d2ui/button.go | 4 ++-- d2core/d2ui/label.go | 13 +++++++++++-- d2core/d2ui/textbox.go | 4 ++-- d2core/d2ui/tooltip.go | 2 +- d2game/d2gamescreen/character_select.go | 10 +++++----- d2game/d2gamescreen/credits.go | 2 +- d2game/d2gamescreen/main_menu.go | 18 +++++++++--------- d2game/d2gamescreen/select_hero_class.go | 16 ++++++++-------- d2game/d2player/help_overlay.go | 2 +- d2game/d2player/hero_stats_panel.go | 4 ++-- d2game/d2player/hud.go | 4 ++-- d2game/d2player/skilltree.go | 4 ++-- 12 files changed, 46 insertions(+), 37 deletions(-) diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index b9a647ba..3cd8c371 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -344,7 +344,7 @@ func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, lab xOffset := half(v.width) label.SetPosition(xOffset, textY) - label.Render(v.normalSurface) + label.RenderNoError(v.normalSurface) if !btnLayout.HasImage || !btnLayout.AllowFrameChange { return @@ -418,7 +418,7 @@ func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, lab } label.SetPosition(state.offsetX, state.offsetY) - label.Render(*state.prerenderdestination) + label.RenderNoError(*state.prerenderdestination) } } diff --git a/d2core/d2ui/label.go b/d2core/d2ui/label.go index aa768ee1..32bd3409 100644 --- a/d2core/d2ui/label.go +++ b/d2core/d2ui/label.go @@ -13,6 +13,9 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" ) +// static check that UIFrame implements Widget +var _ Widget = &Label{} + // Label represents a user interface label type Label struct { *BaseWidget @@ -45,8 +48,14 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label { return result } -// Render draws the label on the screen, respliting the lines to allow for other alignments. -func (v *Label) Render(target d2interface.Surface) { +// Render draws the label on the screen +func (v *Label) Render(target d2interface.Surface) error { + v.RenderNoError(target) + return nil +} + +// RenderNoError draws the label on the screen, respliting the lines to allow for other alignments. +func (v *Label) RenderNoError(target d2interface.Surface) { target.PushTranslation(v.GetPosition()) lines := strings.Split(v.text, "\n") diff --git a/d2core/d2ui/textbox.go b/d2core/d2ui/textbox.go index 256ac2f7..a63327d2 100644 --- a/d2core/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -62,10 +62,10 @@ func (v *TextBox) Render(target d2interface.Surface) error { } v.bgSprite.Render(target) - v.textLabel.Render(target) + v.textLabel.RenderNoError(target) if (time.Now().UnixNano()/1e6)&(1<<8) > 0 { - v.lineBar.Render(target) + v.lineBar.RenderNoError(target) } return nil diff --git a/d2core/d2ui/tooltip.go b/d2core/d2ui/tooltip.go index 0cb4fc89..fc27818a 100644 --- a/d2core/d2ui/tooltip.go +++ b/d2core/d2ui/tooltip.go @@ -187,7 +187,7 @@ func (t *Tooltip) Render(target d2interface.Surface) error { for i := range t.lines { t.label.SetText(t.lines[i]) _, h := t.label.GetTextMetrics(t.lines[i]) - t.label.Render(target) + t.label.RenderNoError(target) target.PushTranslation(0, h) } diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index 76b74b65..76832383 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -373,7 +373,7 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) { return } - v.d2HeroTitle.Render(screen) + v.d2HeroTitle.RenderNoError(screen) actualSelectionIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2) if v.selectedCharacter > -1 && actualSelectionIndex >= 0 && actualSelectionIndex < 8 { @@ -388,9 +388,9 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) { continue } - v.characterNameLabel[i].Render(screen) - v.characterStatsLabel[i].Render(screen) - v.characterExpLabel[i].Render(screen) + v.characterNameLabel[i].RenderNoError(screen) + v.characterStatsLabel[i].RenderNoError(screen) + v.characterExpLabel[i].RenderNoError(screen) x, y := v.characterNameLabel[i].GetPosition() charImgX := x - selectionBoxImageOffsetX @@ -407,7 +407,7 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) { return } - v.deleteCharConfirmLabel.Render(screen) + v.deleteCharConfirmLabel.RenderNoError(screen) } } diff --git a/d2game/d2gamescreen/credits.go b/d2game/d2gamescreen/credits.go index 3664883a..eb850fcc 100644 --- a/d2game/d2gamescreen/credits.go +++ b/d2game/d2gamescreen/credits.go @@ -139,7 +139,7 @@ func (v *Credits) Render(screen d2interface.Surface) { continue } - label.Label.Render(screen) + label.Label.RenderNoError(screen) } } diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index b0d82f55..b722a5d1 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -462,21 +462,21 @@ func (v *MainMenu) renderLogos(screen d2interface.Surface) { func (v *MainMenu) renderLabels(screen d2interface.Surface) { switch v.screenMode { case ScreenModeServerIP: - v.tcpIPOptionsLabel.Render(screen) - v.tcpJoinGameLabel.Render(screen) + v.tcpIPOptionsLabel.RenderNoError(screen) + v.tcpJoinGameLabel.RenderNoError(screen) case ScreenModeTCPIP: - v.tcpIPOptionsLabel.Render(screen) + v.tcpIPOptionsLabel.RenderNoError(screen) case ScreenModeTrademark: - v.copyrightLabel.Render(screen) - v.copyrightLabel2.Render(screen) + v.copyrightLabel.RenderNoError(screen) + v.copyrightLabel2.RenderNoError(screen) if v.errorLabel != nil { - v.errorLabel.Render(screen) + v.errorLabel.RenderNoError(screen) } case ScreenModeMainMenu: - v.openDiabloLabel.Render(screen) - v.versionLabel.Render(screen) - v.commitLabel.Render(screen) + v.openDiabloLabel.RenderNoError(screen) + v.versionLabel.RenderNoError(screen) + v.commitLabel.RenderNoError(screen) } } diff --git a/d2game/d2gamescreen/select_hero_class.go b/d2game/d2gamescreen/select_hero_class.go index 9ec03ece..678c0df1 100644 --- a/d2game/d2gamescreen/select_hero_class.go +++ b/d2game/d2gamescreen/select_hero_class.go @@ -515,13 +515,13 @@ func (v *SelectHeroClass) Render(screen d2interface.Surface) { return } - v.headingLabel.Render(screen) + v.headingLabel.RenderNoError(screen) if v.selectedHero != d2enum.HeroNone { - v.heroClassLabel.Render(screen) - v.heroDesc1Label.Render(screen) - v.heroDesc2Label.Render(screen) - v.heroDesc3Label.Render(screen) + v.heroClassLabel.RenderNoError(screen) + v.heroDesc1Label.RenderNoError(screen) + v.heroDesc2Label.RenderNoError(screen) + v.heroDesc3Label.RenderNoError(screen) } for heroClass, heroInfo := range v.heroRenderInfo { @@ -539,9 +539,9 @@ func (v *SelectHeroClass) Render(screen d2interface.Surface) { v.campfire.Render(screen) if v.heroNameTextbox.GetVisible() { - v.heroNameLabel.Render(screen) - v.expansionCharLabel.Render(screen) - v.hardcoreCharLabel.Render(screen) + v.heroNameLabel.RenderNoError(screen) + v.expansionCharLabel.RenderNoError(screen) + v.hardcoreCharLabel.RenderNoError(screen) } } diff --git a/d2game/d2player/help_overlay.go b/d2game/d2player/help_overlay.go index 8414d3c8..496d9e98 100644 --- a/d2game/d2player/help_overlay.go +++ b/d2game/d2player/help_overlay.go @@ -624,7 +624,7 @@ func (h *HelpOverlay) Render(target d2interface.Surface) error { } for _, t := range h.text { - t.Render(target) + t.RenderNoError(target) } for _, l := range h.lines { diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index 64399af5..b4599910 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -295,7 +295,7 @@ func (s *HeroStatsPanel) renderStaticLabels(target d2interface.Surface) { cfg.centerAlign, }) - label.Render(target) + label.RenderNoError(target) } } @@ -348,7 +348,7 @@ func (s *HeroStatsPanel) renderStatValues(target d2interface.Surface) { func (s *HeroStatsPanel) renderStatValueNum(label *d2ui.Label, value int, target d2interface.Surface) { label.SetText(strconv.Itoa(value)) - label.Render(target) + label.RenderNoError(target) } func (s *HeroStatsPanel) createStatValueLabel(stat, x, y int) *d2ui.Label { diff --git a/d2game/d2player/hud.go b/d2game/d2player/hud.go index 390c7c3f..538ee6e1 100644 --- a/d2game/d2player/hud.go +++ b/d2game/d2player/hud.go @@ -772,7 +772,7 @@ func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) { xLabel, yLabel := entScreenX-xOff, entScreenY-yOff-entityHeight-hoverLabelOuterPad h.nameLabel.SetPosition(xLabel, yLabel) - h.nameLabel.Render(target) + h.nameLabel.RenderNoError(target) entity.Highlight() break @@ -794,7 +794,7 @@ func (h *HUD) Render(target d2interface.Surface) error { if h.isZoneTextShown { h.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY) - h.zoneChangeText.Render(target) + h.zoneChangeText.RenderNoError(target) } h.renderHealthTooltip(target) diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index 2616b24e..7f3cf865 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -441,7 +441,7 @@ func (s *skillTree) renderTabCommon(target d2interface.Surface) error { } // available skill points label - s.availSPLabel.Render(target) + s.availSPLabel.RenderNoError(target) return nil } @@ -545,7 +545,7 @@ func (s *skillTree) renderSkillIconLabel(target d2interface.Surface, skill *d2he x := skillIconXOff + skill.SkillColumn*skillIconDistX + skillLabelXOffset y := skillIconYOff + skill.SkillRow*skillIconDistY + skillLabelYOffset s.skillLvlLabel.SetPosition(x, y) - s.skillLvlLabel.Render(target) + s.skillLvlLabel.RenderNoError(target) } func (s *skillTree) renderSkillIcons(target d2interface.Surface, tab int) error { From 77cd538c2f8995845280fcbcc606624eeff74dbc Mon Sep 17 00:00:00 2001 From: juander Date: Mon, 9 Nov 2020 18:04:56 +0100 Subject: [PATCH 7/9] d2ui/sprite: Refactor Render() to RenderNoError() this allows us to create a Render() method that implements the Widget interface without killing us with linter warnings. --- d2core/d2ui/frame.go | 2 +- d2core/d2ui/sprite.go | 11 ++++++++++- d2core/d2ui/textbox.go | 2 +- d2game/d2gamescreen/main_menu.go | 8 ++++---- d2game/d2gamescreen/select_hero_class.go | 4 ++-- d2game/d2player/help_overlay.go | 2 +- d2game/d2player/hero_stats_panel.go | 2 +- d2game/d2player/hud.go | 24 ++++++++++++------------ d2game/d2player/inventory.go | 2 +- d2game/d2player/inventory_grid.go | 2 +- d2game/d2player/mini_panel.go | 4 ++-- d2game/d2player/skill_select_panel.go | 2 +- d2game/d2player/skilltree.go | 4 ++-- 13 files changed, 39 insertions(+), 30 deletions(-) diff --git a/d2core/d2ui/frame.go b/d2core/d2ui/frame.go index 735db3b0..095d9acd 100644 --- a/d2core/d2ui/frame.go +++ b/d2core/d2ui/frame.go @@ -227,7 +227,7 @@ func (u *UIFrame) renderFramePiece(sfc d2interface.Surface, x, y, idx int) error u.frame.SetPosition(x, y) - u.frame.Render(sfc) + u.frame.RenderNoError(sfc) return nil } diff --git a/d2core/d2ui/sprite.go b/d2core/d2ui/sprite.go index fd28bafb..e9c3cdf6 100644 --- a/d2core/d2ui/sprite.go +++ b/d2core/d2ui/sprite.go @@ -11,6 +11,9 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" ) +// static check that Sprite implements Widget +var _ Widget = &Sprite{} + // Sprite is a positioned visual object. type Sprite struct { *BaseWidget @@ -38,7 +41,13 @@ func (ui *UIManager) NewSprite(animationPath, palettePath string) (*Sprite, erro } // Render renders the sprite on the given surface -func (s *Sprite) Render(target d2interface.Surface) { +func (s *Sprite) Render(target d2interface.Surface) error { + s.RenderNoError(target) + return nil +} + +// RenderNoError renders the sprite on the given surface +func (s *Sprite) RenderNoError(target d2interface.Surface) { _, frameHeight := s.animation.GetCurrentFrameSize() target.PushTranslation(s.x, s.y-frameHeight) diff --git a/d2core/d2ui/textbox.go b/d2core/d2ui/textbox.go index a63327d2..de914f6c 100644 --- a/d2core/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -61,7 +61,7 @@ func (v *TextBox) Render(target d2interface.Surface) error { return nil } - v.bgSprite.Render(target) + v.bgSprite.RenderNoError(target) v.textLabel.RenderNoError(target) if (time.Now().UnixNano()/1e6)&(1<<8) > 0 { diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index b722a5d1..1e424b2d 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -452,10 +452,10 @@ func (v *MainMenu) renderBackgrounds(screen d2interface.Surface) { func (v *MainMenu) renderLogos(screen d2interface.Surface) { switch v.screenMode { case ScreenModeTrademark, ScreenModeMainMenu, ScreenModeMultiplayer: - v.diabloLogoLeftBack.Render(screen) - v.diabloLogoRightBack.Render(screen) - v.diabloLogoLeft.Render(screen) - v.diabloLogoRight.Render(screen) + v.diabloLogoLeftBack.RenderNoError(screen) + v.diabloLogoRightBack.RenderNoError(screen) + v.diabloLogoLeft.RenderNoError(screen) + v.diabloLogoRight.RenderNoError(screen) } } diff --git a/d2game/d2gamescreen/select_hero_class.go b/d2game/d2gamescreen/select_hero_class.go index 678c0df1..56ee9fb3 100644 --- a/d2game/d2gamescreen/select_hero_class.go +++ b/d2game/d2gamescreen/select_hero_class.go @@ -536,7 +536,7 @@ func (v *SelectHeroClass) Render(screen d2interface.Surface) { } } - v.campfire.Render(screen) + v.campfire.RenderNoError(screen) if v.heroNameTextbox.GetVisible() { v.heroNameLabel.RenderNoError(screen) @@ -739,7 +739,7 @@ func setSpriteToFirstFrame(sprite *d2ui.Sprite) { func drawSprite(sprite *d2ui.Sprite, target d2interface.Surface) { if sprite != nil { - sprite.Render(target) + sprite.RenderNoError(target) } } diff --git a/d2game/d2player/help_overlay.go b/d2game/d2player/help_overlay.go index 496d9e98..2608e266 100644 --- a/d2game/d2player/help_overlay.go +++ b/d2game/d2player/help_overlay.go @@ -620,7 +620,7 @@ func (h *HelpOverlay) Render(target d2interface.Surface) error { } for _, f := range h.frames { - f.Render(target) + f.RenderNoError(target) } for _, t := range h.text { diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index b4599910..4c0ddf0c 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -242,7 +242,7 @@ func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) err s.panel.SetPosition(currentX-w, currentY+h) } - s.panel.Render(target) + s.panel.RenderNoError(target) } return nil diff --git a/d2game/d2player/hud.go b/d2game/d2player/hud.go index 538ee6e1..a8f3b1d2 100644 --- a/d2game/d2player/hud.go +++ b/d2game/d2player/hud.go @@ -391,7 +391,7 @@ func (h *HUD) renderManaGlobe(x, _ int, target d2interface.Surface) error { h.mainPanel.SetPosition(x, height) - h.mainPanel.Render(target) + h.mainPanel.RenderNoError(target) // Mana status bar manaPercent := float64(h.hero.Stats.Mana) / float64(h.hero.Stats.MaxMana) @@ -413,8 +413,8 @@ func (h *HUD) renderManaGlobe(x, _ int, target d2interface.Surface) error { h.globeSprite.SetPosition(x+rightGlobeOffsetX, height+rightGlobeOffsetY) - h.globeSprite.Render(target) - h.globeSprite.Render(target) + h.globeSprite.RenderNoError(target) + h.globeSprite.RenderNoError(target) return nil } @@ -438,7 +438,7 @@ func (h *HUD) renderHealthGlobe(x, y int, target d2interface.Surface) error { } h.globeSprite.SetPosition(x+globeSpriteOffsetX, y+globeSpriteOffsetY) - h.globeSprite.Render(target) + h.globeSprite.RenderNoError(target) return nil } @@ -449,7 +449,7 @@ func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error { } h.mainPanel.SetPosition(x, y) - h.mainPanel.Render(target) + h.mainPanel.RenderNoError(target) return nil } @@ -466,7 +466,7 @@ func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) error { } h.leftSkillResource.SkillIcon.SetPosition(x, y) - h.leftSkillResource.SkillIcon.Render(target) + h.leftSkillResource.SkillIcon.RenderNoError(target) return nil } @@ -485,7 +485,7 @@ func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) error { } h.rightSkillResource.SkillIcon.SetPosition(x, height) - h.rightSkillResource.SkillIcon.Render(target) + h.rightSkillResource.SkillIcon.RenderNoError(target) return nil } @@ -496,7 +496,7 @@ func (h *HUD) renderNewStatsButton(x, y int, target d2interface.Surface) error { } h.mainPanel.SetPosition(x, y) - h.mainPanel.Render(target) + h.mainPanel.RenderNoError(target) return nil } @@ -507,7 +507,7 @@ func (h *HUD) renderStamina(x, y int, target d2interface.Surface) error { } h.mainPanel.SetPosition(x, y) - h.mainPanel.Render(target) + h.mainPanel.RenderNoError(target) return nil } @@ -558,7 +558,7 @@ func (h *HUD) renderMiniPanel(target d2interface.Surface) error { buttonX, buttonY := (width>>1)+miniPanelButtonOffsetX, height+miniPanelButtonOffsetY h.menuButton.SetPosition(buttonX, buttonY) - h.menuButton.Render(target) + h.menuButton.RenderNoError(target) h.miniPanel.Render(target) miniPanelButtons := map[actionableType]string{ @@ -612,7 +612,7 @@ func (h *HUD) renderPotions(x, _ int, target d2interface.Surface) error { } h.mainPanel.SetPosition(x, height) - h.mainPanel.Render(target) + h.mainPanel.RenderNoError(target) return nil } @@ -625,7 +625,7 @@ func (h *HUD) renderNewSkillsButton(x, _ int, target d2interface.Surface) error } h.mainPanel.SetPosition(x, height) - h.mainPanel.Render(target) + h.mainPanel.RenderNoError(target) return nil } diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index 56e7fa75..4b970b8a 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -196,7 +196,7 @@ func (g *Inventory) renderFrame(target d2interface.Surface) error { w, h := g.panel.GetCurrentFrameSize() g.panel.SetPosition(x, y+h) - g.panel.Render(target) + g.panel.RenderNoError(target) switch frame { case frameInventoryTopLeft: diff --git a/d2game/d2player/inventory_grid.go b/d2game/d2player/inventory_grid.go index 2c30a648..771c49b2 100644 --- a/d2game/d2player/inventory_grid.go +++ b/d2game/d2player/inventory_grid.go @@ -234,7 +234,7 @@ func (g *ItemGrid) renderItem(item InventoryItem, target d2interface.Surface, x, if itemSprite != nil { itemSprite.SetPosition(x, y) itemSprite.GetCurrentFrameSize() - itemSprite.Render(target) + itemSprite.RenderNoError(target) } } diff --git a/d2game/d2player/mini_panel.go b/d2game/d2player/mini_panel.go index b2be6eb9..f69dd07a 100644 --- a/d2game/d2player/mini_panel.go +++ b/d2game/d2player/mini_panel.go @@ -104,7 +104,7 @@ func (m *miniPanel) Render(target d2interface.Surface) { m.container.SetPosition(x, y) - m.container.Render(target) + m.container.RenderNoError(target) buttonWidth, _ := m.button.GetCurrentFrameSize() buttonWidth++ @@ -122,7 +122,7 @@ func (m *miniPanel) Render(target d2interface.Surface) { x, y := halfW+offsetX, height+buttonOffsetY m.button.SetPosition(x, y) - m.button.Render(target) + m.button.RenderNoError(target) j += 2 } diff --git a/d2game/d2player/skill_select_panel.go b/d2game/d2player/skill_select_panel.go index 3aa6f5a9..cb7507fa 100644 --- a/d2game/d2player/skill_select_panel.go +++ b/d2game/d2player/skill_select_panel.go @@ -242,7 +242,7 @@ func (s *SkillPanel) createSkillListImage(skillsListRow *SkillListRow) (d2interf } surface.PushTranslation(idx*skillIconWidth, 50) - skillSprite.Render(surface) + skillSprite.RenderNoError(surface) surface.Pop() } diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index 7f3cf865..e8d39553 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -387,7 +387,7 @@ func (s *skillTree) renderPanelSegment( return err } - s.resources.skillPanel.Render(target) + s.resources.skillPanel.RenderNoError(target) return nil } @@ -531,7 +531,7 @@ func (s *skillTree) renderSkillIcon(target d2interface.Surface, skill *d2hero.He defer target.Pop() } - skillIcon.Render(target) + skillIcon.RenderNoError(target) return nil } From 83acaefea2e626188f95157cde11ffbe3584be78 Mon Sep 17 00:00:00 2001 From: juander Date: Mon, 9 Nov 2020 15:03:00 +0100 Subject: [PATCH 8/9] d2player/game_controls: learnskills cmd not always creating new skill objects when we ran the command before we would always throw away old skill objects and create a new one. This is nasty if we have a pointer to the old object. By throwing it away we have to update all these pointers. Now we rather increase the skillpoint, if the hero already has this skill. --- d2game/d2player/game_controls.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 4b6d0317..084afc61 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -1014,18 +1014,23 @@ func (g *GameControls) bindLearnSkillsCommand(term d2interface.Terminal) error { continue } - skill, skillErr := g.heroState.CreateHeroSkill(1, skillDetailRecord.Skill) - if skill == nil { - continue - } + if skill, ok := g.hero.Skills[skillDetailRecord.ID]; ok { + skill.SkillPoints++; + learnedSkillsCount++ + } else { + skill, skillErr := g.heroState.CreateHeroSkill(1, skillDetailRecord.Skill) + if skill == nil { + continue + } - learnedSkillsCount++ + learnedSkillsCount++ - g.hero.Skills[skill.ID] = skill + g.hero.Skills[skill.ID] = skill - if skillErr != nil { - err = skillErr - break + if skillErr != nil { + err = skillErr + break + } } } From 622bc832d379d750052e08b9518298d91162a488 Mon Sep 17 00:00:00 2001 From: juander Date: Mon, 9 Nov 2020 18:09:46 +0100 Subject: [PATCH 9/9] d2player/skilltree: Move every element to widgets the uiManager now handles every element of the ui, so we don't need to render elements manually in game_controls. Now we can also use widget_groups to simplify handling the opening/closing of the panel. --- d2core/d2ui/widget.go | 4 + d2game/d2player/game_controls.go | 9 +- d2game/d2player/skillicon.go | 99 +++++++++++++++++ d2game/d2player/skilltree.go | 178 ++++++++++--------------------- 4 files changed, 162 insertions(+), 128 deletions(-) create mode 100644 d2game/d2player/skillicon.go diff --git a/d2core/d2ui/widget.go b/d2core/d2ui/widget.go index 2c23959e..d53fa359 100644 --- a/d2core/d2ui/widget.go +++ b/d2core/d2ui/widget.go @@ -7,6 +7,10 @@ type RenderPriority int const ( // RenderPriorityBackground is the first element drawn RenderPriorityBackground RenderPriority = iota + // RenderPrioritySkilltree is the priority for the skilltree + RenderPrioritySkilltree + // RenderPrioritySkilltreeIcon is the priority for the skilltree icons + RenderPrioritySkilltreeIcon // RenderPriorityForeground is the last element drawn RenderPriorityForeground ) diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 084afc61..37c0c8f0 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -370,7 +370,7 @@ func NewGameControls( inputListener: inputListener, mapRenderer: mapRenderer, inventory: NewInventory(asset, ui, inventoryRecord), - skilltree: newSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager), + skilltree: newSkillTree(hero.Skills, hero.Class, asset, ui), heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats), HelpOverlay: helpOverlay, hud: hud, @@ -773,11 +773,6 @@ func (g *GameControls) renderPanels(target d2interface.Surface) error { g.heroStatsPanel.Render(target) g.inventory.Render(target) - err := g.skilltree.Render(target) - if err != nil { - log.Println(err) - } - return nil } @@ -1015,7 +1010,7 @@ func (g *GameControls) bindLearnSkillsCommand(term d2interface.Terminal) error { } if skill, ok := g.hero.Skills[skillDetailRecord.ID]; ok { - skill.SkillPoints++; + skill.SkillPoints++ learnedSkillsCount++ } else { skill, skillErr := g.heroState.CreateHeroSkill(1, skillDetailRecord.Skill) diff --git a/d2game/d2player/skillicon.go b/d2game/d2player/skillicon.go new file mode 100644 index 00000000..c8bb0551 --- /dev/null +++ b/d2game/d2player/skillicon.go @@ -0,0 +1,99 @@ +package d2player + +import ( + "strconv" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" +) + +const ( + skillLabelXOffset = 49 + skillLabelYOffset = -4 + + skillIconXOff = 346 + skillIconYOff = 59 + skillIconDistX = 69 + skillIconDistY = 68 +) + +type skillIcon struct { + *d2ui.BaseWidget + lvlLabel *d2ui.Label + sprite *d2ui.Sprite + skill *d2hero.HeroSkill +} + +func newSkillIcon(ui *d2ui.UIManager, baseSprite *d2ui.Sprite, skill *d2hero.HeroSkill) *skillIcon { + base := d2ui.NewBaseWidget(ui) + label := ui.NewLabel(d2resource.Font16, d2resource.PaletteSky) + + x := skillIconXOff + skill.SkillColumn*skillIconDistX + y := skillIconYOff + skill.SkillRow*skillIconDistY + + res := &skillIcon{ + BaseWidget: base, + sprite: baseSprite, + skill: skill, + lvlLabel: label, + } + + res.SetPosition(x, y) + + return res +} + +func (si *skillIcon) SetVisible(visible bool) { + si.BaseWidget.SetVisible(visible) + si.lvlLabel.SetVisible(visible) +} + +func (si *skillIcon) renderSprite(target d2interface.Surface) error { + x, y := si.GetPosition() + + if err := si.sprite.SetCurrentFrame(si.skill.IconCel); err != nil { + return err + } + + if si.skill.SkillPoints == 0 { + target.PushSaturation(skillIconGreySat) + defer target.Pop() + + target.PushBrightness(skillIconGreyBright) + defer target.Pop() + } + + si.sprite.SetPosition(x, y) + + if err := si.sprite.Render(target); err != nil { + return err + } + + return nil +} + +func (si *skillIcon) renderSpriteLabel(target d2interface.Surface) error { + if si.skill.SkillPoints == 0 { + return nil + } + + x, y := si.GetPosition() + si.lvlLabel.SetText(strconv.Itoa(si.skill.SkillPoints)) + si.lvlLabel.SetPosition(x+skillLabelXOffset, y+skillLabelYOffset) + + return si.lvlLabel.Render(target) +} + +func (si *skillIcon) Render(target d2interface.Surface) error { + if err := si.renderSprite(target); err != nil { + return err + } + + return si.renderSpriteLabel(target) +} + +func (si *skillIcon) Advance(elapsed float64) error { + return nil +} diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index e8d39553..b44e7303 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -3,7 +3,6 @@ package d2player import ( "fmt" "log" - "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -23,14 +22,6 @@ const ( availSPLabelX = 677 availSPLabelY = 72 - skillIconXOff = 346 - skillIconYOff = 59 - skillIconDistX = 69 - skillIconDistY = 68 - - skillLabelXOffset = 49 - skillLabelYOffset = -4 - skillCloseButtonXLeft = 416 skillCloseButtonXMiddle = 501 skillCloseButtonXRight = 572 @@ -83,55 +74,50 @@ type skillTreeTab struct { func (st *skillTreeTab) createButton(uiManager *d2ui.UIManager, x, y int) { st.button = uiManager.NewButton(d2ui.ButtonTypeSkillTreeTab, st.buttonText) - st.button.SetVisible(false) st.button.SetPosition(x, y) } type skillTreeHeroTypeResources struct { - skillIcon *d2ui.Sprite + skillSprite *d2ui.Sprite skillIconPath string skillPanel *d2ui.Sprite skillPanelPath string } type skillTree struct { - resources *skillTreeHeroTypeResources - asset *d2asset.AssetManager - renderer d2interface.Renderer - guiManager *d2gui.GuiManager - uiManager *d2ui.UIManager - layout *d2gui.Layout - skills map[int]*d2hero.HeroSkill - heroClass d2enum.Hero - frame *d2ui.UIFrame - availSPLabel *d2ui.Label - skillLvlLabel *d2ui.Label - closeButton *d2ui.Button - tab [numTabs]*skillTreeTab - isOpen bool - originX int - originY int - selectedTab int - onCloseCb func() + resources *skillTreeHeroTypeResources + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + skills map[int]*d2hero.HeroSkill + skillIcons []*skillIcon + heroClass d2enum.Hero + frame *d2ui.UIFrame + availSPLabel *d2ui.Label + closeButton *d2ui.Button + tab [numTabs]*skillTreeTab + isOpen bool + originX int + originY int + selectedTab int + onCloseCb func() + panelGroup *d2ui.WidgetGroup + iconGroup *d2ui.WidgetGroup + panel *d2ui.CustomWidget } func newSkillTree( skills map[int]*d2hero.HeroSkill, heroClass d2enum.Hero, asset *d2asset.AssetManager, - renderer d2interface.Renderer, ui *d2ui.UIManager, - guiManager *d2gui.GuiManager, ) *skillTree { st := &skillTree{ - skills: skills, - heroClass: heroClass, - asset: asset, - renderer: renderer, - uiManager: ui, - guiManager: guiManager, - originX: skillTreePanelX, - originY: skillTreePanelY, + skills: skills, + heroClass: heroClass, + asset: asset, + uiManager: ui, + originX: skillTreePanelX, + originY: skillTreePanelY, tab: [numTabs]*skillTreeTab{ {}, {}, @@ -143,16 +129,32 @@ func newSkillTree( } func (s *skillTree) load() { + s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPrioritySkilltree) + s.iconGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPrioritySkilltreeIcon) + + s.panel = s.uiManager.NewCustomWidget(s.Render) + s.panelGroup.AddWidget(s.panel) + s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight) + s.panelGroup.AddWidget(s.frame) + s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") s.closeButton.SetVisible(false) s.closeButton.OnActivated(func() { s.Close() }) - - s.skillLvlLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) + s.panelGroup.AddWidget(s.closeButton) s.setHeroTypeResourcePath() s.loadForHeroType() + + for _, skill := range s.skills { + si := newSkillIcon(s.uiManager, s.resources.skillSprite, skill) + s.skillIcons = append(s.skillIcons, si) + s.iconGroup.AddWidget(si) + } + + s.panelGroup.SetVisible(false) s.setTab(0) + s.iconGroup.SetVisible(false) } func (s *skillTree) loadForHeroType() { @@ -168,21 +170,25 @@ func (s *skillTree) loadForHeroType() { log.Print(err) } - s.resources.skillIcon = si + s.resources.skillSprite = si s.tab[firstTab].createButton(s.uiManager, tabButtonX, tabButton0Y) s.tab[firstTab].button.OnActivated(func() { s.setTab(firstTab) }) + s.panelGroup.AddWidget(s.tab[firstTab].button) s.tab[secondTab].createButton(s.uiManager, tabButtonX, tabButton1Y) s.tab[secondTab].button.OnActivated(func() { s.setTab(secondTab) }) + s.panelGroup.AddWidget(s.tab[secondTab].button) s.tab[thirdTab].createButton(s.uiManager, tabButtonX, tabButton2Y) s.tab[thirdTab].button.OnActivated(func() { s.setTab(thirdTab) }) + s.panelGroup.AddWidget(s.tab[thirdTab].button) s.availSPLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY) s.availSPLabel.Alignment = d2gui.HorizontalAlignCenter s.availSPLabel.SetText(s.makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3")) + s.panelGroup.AddWidget(s.availSPLabel) } type heroTabData struct { @@ -340,12 +346,9 @@ func (s *skillTree) Toggle() { // Close the skill tree func (s *skillTree) Close() { s.isOpen = false - s.guiManager.SetLayout(nil) - s.closeButton.SetVisible(false) - for i := 0; i < numTabs; i++ { - s.tab[i].button.SetVisible(false) - } + s.panelGroup.SetVisible(false) + s.iconGroup.SetVisible(false) s.onCloseCb() } @@ -353,17 +356,11 @@ func (s *skillTree) Close() { // Open the skill tree func (s *skillTree) Open() { s.isOpen = true - if s.layout == nil { - s.layout = d2gui.CreateLayout(s.renderer, d2gui.PositionTypeHorizontal, s.asset) - } - s.closeButton.SetVisible(true) + s.panelGroup.SetVisible(true) - for i := 0; i < numTabs; i++ { - s.tab[i].button.SetVisible(true) - } - - s.guiManager.SetLayout(s.layout) + // we only want to enable the icons of our current tab again + s.setTab(s.selectedTab) } func (s *skillTree) IsOpen() bool { @@ -378,6 +375,10 @@ func (s *skillTree) SetOnCloseCb(cb func()) { func (s *skillTree) setTab(tab int) { s.selectedTab = tab s.closeButton.SetPosition(s.tab[tab].closeButtonPosX, skillCloseButtonY) + + for _, si := range s.skillIcons { + si.SetVisible(si.skill.SkillPage == tab+1) + } } func (s *skillTree) renderPanelSegment( @@ -512,69 +513,8 @@ func (s *skillTree) renderTab(target d2interface.Surface, tab int) error { return nil } -func (s *skillTree) renderSkillIcon(target d2interface.Surface, skill *d2hero.HeroSkill) error { - skillIcon := s.resources.skillIcon - if err := skillIcon.SetCurrentFrame(skill.IconCel); err != nil { - return err - } - - x := skillIconXOff + skill.SkillColumn*skillIconDistX - y := skillIconYOff + skill.SkillRow*skillIconDistY - - skillIcon.SetPosition(x, y) - - if skill.SkillPoints == 0 { - target.PushSaturation(skillIconGreySat) - defer target.Pop() - - target.PushBrightness(skillIconGreyBright) - defer target.Pop() - } - - skillIcon.RenderNoError(target) - - return nil -} - -func (s *skillTree) renderSkillIconLabel(target d2interface.Surface, skill *d2hero.HeroSkill) { - if skill.SkillPoints == 0 { - return - } - - s.skillLvlLabel.SetText(strconv.Itoa(skill.SkillPoints)) - x := skillIconXOff + skill.SkillColumn*skillIconDistX + skillLabelXOffset - y := skillIconYOff + skill.SkillRow*skillIconDistY + skillLabelYOffset - s.skillLvlLabel.SetPosition(x, y) - s.skillLvlLabel.RenderNoError(target) -} - -func (s *skillTree) renderSkillIcons(target d2interface.Surface, tab int) error { - for idx := range s.skills { - skill := s.skills[idx] - if skill.SkillPage != tab+1 { - continue - } - - if err := s.renderSkillIcon(target, skill); err != nil { - return err - } - - s.renderSkillIconLabel(target, skill) - } - - return nil -} - // Render the skill tree panel func (s *skillTree) Render(target d2interface.Surface) error { - if !s.isOpen { - return nil - } - - if err := s.frame.Render(target); err != nil { - return err - } - if err := s.renderTabCommon(target); err != nil { return err } @@ -583,9 +523,5 @@ func (s *skillTree) Render(target d2interface.Surface) error { return err } - if err := s.renderSkillIcons(target, s.selectedTab); err != nil { - return err - } - return nil }