diff --git a/buyflowerbutton.go b/buyflowerbutton.go index dc5b905..63717b1 100644 --- a/buyflowerbutton.go +++ b/buyflowerbutton.go @@ -21,7 +21,7 @@ type BuyFlowerButton struct { func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, onClick EventContextFn) *BuyFlowerButton { return &BuyFlowerButton{ - IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) { + IconButton: *NewIconButtonConfigure(icon, onClick, func(b *IconButton) { b.IconDisabled = iconDisabled b.IsDisabled = !flower.Unlocked }), diff --git a/cmd/tins2020/tins2020.go b/cmd/tins2020/tins2020.go index 333b7d3..392f4f8 100644 --- a/cmd/tins2020/tins2020.go +++ b/cmd/tins2020/tins2020.go @@ -136,6 +136,8 @@ func run() error { app.Arrange(ctx, tins2020.RectAbs(0, 0, w, h)) ctx.Settings.Window.Size = tins2020.PtPtr(w, h) } + case *sdl.MouseMotionEvent: + ctx.MousePosition = tins2020.Pt(e.X, e.Y) } app.Handle(ctx, event) } diff --git a/context.go b/context.go index 8644241..a65e971 100644 --- a/context.go +++ b/context.go @@ -6,12 +6,13 @@ import ( ) type Context struct { - Renderer *sdl.Renderer - Fonts Fonts - Resources Resources - Textures Textures - Settings Settings - ShouldQuit bool + Renderer *sdl.Renderer + Fonts Fonts + Resources Resources + Textures Textures + Settings Settings + MousePosition Point + ShouldQuit bool } func NewContext(res *rice.Box) (*Context, error) { diff --git a/control.go b/control.go index b7cf6f4..f3f98b3 100644 --- a/control.go +++ b/control.go @@ -22,12 +22,35 @@ func EmptyEvent(fn EventFn) EventContextFn { type ControlBase struct { Bounds Rectangle + FontName string + Foreground sdl.Color + IsDisabled bool IsMouseOver bool OnLeftMouseButtonClick EventContextFn } +func (c *ControlBase) ActualForeground() sdl.Color { + var none sdl.Color + if c.Foreground == none { + return MustHexColor("#ffffff") + } + return c.Foreground +} + +func (c *ControlBase) ActualFont(ctx *Context) *Font { + name := c.ActualFontName() + return ctx.Fonts.Font(name) +} + +func (c *ControlBase) ActualFontName() string { + if len(c.FontName) == 0 { + return "default" + } + return c.FontName +} + func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds } func (b *ControlBase) Init(*Context) error { return nil } diff --git a/gamecontrols.go b/gamecontrols.go index 44ebe1d..e6b8d2e 100644 --- a/gamecontrols.go +++ b/gamecontrols.go @@ -100,44 +100,61 @@ func (c *GameControls) Init(ctx *Context) error { } c.top.Orientation = OrientationHorizontal - c.pause = NewIconButtonConfig("control-pause", EmptyEvent(func() { + c.pause = NewIconButtonConfigure("control-pause", EmptyEvent(func() { c.game.Pause() }), func(b *IconButton) { b.IconDisabled = "control-pause-disabled" + b.Tooltip.Text = "Pause game" }) - c.run = NewIconButtonConfig("control-run", EmptyEvent(func() { + c.run = NewIconButtonConfigure("control-run", EmptyEvent(func() { c.game.Run() }), func(b *IconButton) { b.IconDisabled = "control-run-disabled" + b.Tooltip.Text = "Run game at normal speed" }) - c.runFast = NewIconButtonConfig("control-run-fast", EmptyEvent(func() { + c.runFast = NewIconButtonConfigure("control-run-fast", EmptyEvent(func() { c.game.RunFast() }), func(b *IconButton) { b.IconDisabled = "control-run-fast-disabled" + b.Tooltip.Text = "Run game at fast speed" }) c.speedChanged(c.game.Speed) c.top.Buttons = []Control{c.pause, c.run, c.runFast} c.menu.Background = MustHexColor("#356dad") c.menu.Buttons = []Control{ - NewIconButtonConfig("control-settings", c.dialogs.ShowSettings, func(b *IconButton) { + NewIconButtonConfigure("control-settings", c.dialogs.ShowSettings, func(b *IconButton) { b.IsDisabled = true b.IconDisabled = "#afafaf" }), - NewIconButton("control-save", func(*Context) { c.game.Save() }), - NewIconButton("control-load", func(ctx *Context) { + NewIconButtonConfigure("control-save", func(*Context) { c.game.Save() }, func(b *IconButton) { + b.Tooltip.Text = "Save game (overwrites previous save; no confirmation)" + }), + NewIconButtonConfigure("control-load", func(ctx *Context) { c.game.Load() c.updateFlowerControls(ctx) + }, func(b *IconButton) { + b.Tooltip.Text = "Load last saved game (no confirmation)" }), - NewIconButton("control-new", func(ctx *Context) { + NewIconButtonConfigure("control-new", func(ctx *Context) { c.game.New() c.updateFlowerControls(ctx) + }, func(b *IconButton) { + b.Tooltip.Text = "Start new game (no confirmation)" + }), + NewIconButtonConfigure("control-information", c.dialogs.ShowIntro, func(b *IconButton) { + b.Tooltip.Text = "Show information/intro" }), - NewIconButton("control-information", c.dialogs.ShowIntro), } - c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 }) - c.research = NewIconButtonConfig("control-research", c.dialogs.ShowResearch, func(b *IconButton) { b.IconHeight = 32 }) + c.shovel = NewIconButtonConfigure("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { + b.IconHeight = 32 + b.Tooltip.Text = "Select harvest tool (key: H)" + }) + c.research = NewIconButtonConfigure("control-research", c.dialogs.ShowResearch, func(b *IconButton) { + b.IconHeight = 32 + b.Tooltip.Text = "Conduct research (key: R)" + }) c.otherTools.Buttons = []Control{c.shovel, c.research} c.Container.AddChild(&c.menu) diff --git a/iconbutton.go b/iconbutton.go index 162d906..0481e15 100644 --- a/iconbutton.go +++ b/iconbutton.go @@ -1,5 +1,9 @@ package tins2020 +import ( + "github.com/veandco/go-sdl2/sdl" +) + type HoverEffect int const ( @@ -19,6 +23,7 @@ type IconButton struct { IconActive HoverEffect IconHover HoverEffect + Tooltip Tooltip IsActive bool } @@ -31,7 +36,7 @@ func NewIconButton(icon string, onClick EventContextFn) *IconButton { } } -func NewIconButtonConfig(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton { +func NewIconButtonConfigure(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton { button := NewIconButton(icon, onClick) configure(button) return button @@ -57,6 +62,31 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture { return ctx.Textures.Texture(b.Icon) } +func (b *IconButton) Arrange(ctx *Context, bounds Rectangle) { + b.ControlBase.Arrange(ctx, bounds) + b.Tooltip.Arrange(ctx, bounds) +} + +func (b *IconButton) Handle(ctx *Context, event sdl.Event) bool { + if b.ControlBase.Handle(ctx, event) { + return true + } + if b.Tooltip.Handle(ctx, event) { + return true + } + return false +} + +func (b *IconButton) Init(ctx *Context) error { + if err := b.ControlBase.Init(ctx); err != nil { + return err + } + if err := b.Tooltip.Init(ctx); err != nil { + return err + } + return nil +} + func (b *IconButton) Render(ctx *Context) { iconTexture := b.activeTexture(ctx) @@ -81,6 +111,10 @@ func (b *IconButton) Render(ctx *Context) { ctx.Renderer.FillRect(b.Bounds.SDLPtr()) } iconTexture.SetColor(White) + + if len(b.Tooltip.Text) > 0 && b.IsMouseOver { + b.Tooltip.Render(ctx) + } } type Scale int diff --git a/label.go b/label.go index 9f56397..4cc803c 100644 --- a/label.go +++ b/label.go @@ -2,37 +2,18 @@ package tins2020 import ( "strings" - - "github.com/veandco/go-sdl2/sdl" ) type Label struct { ControlBase - FontColor sdl.Color - FontName string Text string Alignment TextAlignment } -func (l *Label) fontColor() sdl.Color { - var none sdl.Color - if l.FontColor == none { - return MustHexColor("#ffffff") - } - return l.FontColor -} - -func (l *Label) fontName() string { - if len(l.FontName) == 0 { - return "default" - } - return l.FontName -} - func (l *Label) Render(ctx *Context) { - font := ctx.Fonts.Font(l.fontName()) - color := l.fontColor() + font := ctx.Fonts.Font(l.ActualFontName()) + color := l.ActualForeground() bottom := l.Bounds.Y + l.Bounds.H switch l.Alignment { case TextAlignmentCenter: @@ -49,8 +30,8 @@ type Paragraph struct { } func (p *Paragraph) Render(ctx *Context) { - font := ctx.Fonts.Font(p.fontName()) - color := p.fontColor() + font := ctx.Fonts.Font(p.ActualFontName()) + color := p.ActualForeground() fontHeight := int32(font.Height()) lines := strings.Split(p.Text, "\n") diff --git a/largedialog.go b/largedialog.go index e16c2c1..9230064 100644 --- a/largedialog.go +++ b/largedialog.go @@ -62,11 +62,10 @@ func (d *LargeDialog) Arrange(ctx *Context, bounds Rectangle) { } func (d *LargeDialog) Init(ctx *Context) error { - d.title = Label{ - Text: "Botanim", - FontName: "title", - Alignment: TextAlignmentCenter, - } + d.title.Text = "Botanim" + d.title.FontName = "title" + d.title.Alignment = TextAlignmentCenter + d.close = IconButton{ Icon: "control-cancel", IconHover: HoverEffectColor, diff --git a/tooltip.go b/tooltip.go new file mode 100644 index 0000000..88bea7e --- /dev/null +++ b/tooltip.go @@ -0,0 +1,59 @@ +package tins2020 + +import "github.com/veandco/go-sdl2/sdl" + +type Tooltip struct { + ControlBase + + Text string +} + +const tooltipBorderThickness = 1 +const tooltipHorizontalPadding = 4 +const tooltipMouseDistance = 12 + +func (t *Tooltip) Handle(ctx *Context, event sdl.Event) bool { + if len(t.Text) == 0 { + return false + } + + font := ctx.Fonts.Font(t.ActualFontName()) + windowW, windowH, err := ctx.Renderer.GetOutputSize() + if err != nil { + return false + } + + labelW, labelH, err := font.SizeUTF8(t.Text) + if err != nil { + return false + } + + mouse := ctx.MousePosition + width := int32(labelW) + 2*tooltipBorderThickness + 2*tooltipHorizontalPadding + height := int32(labelH) + 2*tooltipBorderThickness + + left := mouse.X + tooltipMouseDistance + top := mouse.Y + tooltipMouseDistance + if left+width > windowW { + left = mouse.X - tooltipMouseDistance - width + } + if top+height > windowH { + top = mouse.Y - tooltipMouseDistance - height + } + + t.Bounds = Rect(left, top, width, height) + return false +} + +func (t *Tooltip) Render(ctx *Context) { + SetDrawColor(ctx.Renderer, Black) + ctx.Renderer.FillRect(t.Bounds.SDLPtr()) + + SetDrawColor(ctx.Renderer, White) + ctx.Renderer.DrawRect(t.Bounds.SDLPtr()) + + font := t.ActualFont(ctx) + + bottomLeft := Pt(t.Bounds.X+tooltipBorderThickness+tooltipHorizontalPadding, t.Bounds.Y+t.Bounds.H-tooltipBorderThickness) + font.RenderCopy(ctx.Renderer, t.Text, bottomLeft, White) +}