diff --git a/pkg/internal/config/const.go b/pkg/internal/config/const.go new file mode 100644 index 0000000..d912156 --- /dev/null +++ b/pkg/internal/config/const.go @@ -0,0 +1 @@ +package config diff --git a/pkg/internal/land/object_interactable.go b/pkg/internal/land/object_interactable.go index c1f60ba..48df4e2 100644 --- a/pkg/internal/land/object_interactable.go +++ b/pkg/internal/land/object_interactable.go @@ -1,7 +1,15 @@ package land +type UserEventType = int + +const ( + UserEventMouseDown = UserEventType(iota) + UserEventMouseUp + UserEventMouseMove +) + type InteractableObject interface { GetPosition() Vector2D GetSize() Vector2D - OnClick(mousePos Vector2D) + OnEvent(event UserEventType, args ...any) } diff --git a/pkg/internal/land/object_ui.go b/pkg/internal/land/object_ui.go index ae121d8..eea7506 100644 --- a/pkg/internal/land/object_ui.go +++ b/pkg/internal/land/object_ui.go @@ -2,20 +2,20 @@ package land import "github.com/veandco/go-sdl2/sdl" -type BaseUIObject struct { +type BaseWidget struct { BaseObject Position Vector2D Size Vector2D } -func (p *BaseUIObject) Draw(pen *sdl.Renderer) { +func (p *BaseWidget) Draw(pen *sdl.Renderer) { } -func (p *BaseUIObject) GetPosition() Vector2D { +func (p *BaseWidget) GetPosition() Vector2D { return p.Position } -func (p *BaseUIObject) GetSize() Vector2D { +func (p *BaseWidget) GetSize() Vector2D { return p.Size } diff --git a/pkg/internal/land/renderer/curve.go b/pkg/internal/land/renderer/curve.go new file mode 100644 index 0000000..e341f22 --- /dev/null +++ b/pkg/internal/land/renderer/curve.go @@ -0,0 +1,10 @@ +package renderer + +import ( + "github.com/veandco/go-sdl2/gfx" + "github.com/veandco/go-sdl2/sdl" +) + +func FillRoundedRect(pen *sdl.Renderer, rect *sdl.Rect, color sdl.Color, radius int32) { + gfx.RoundedRectangleColor(pen, rect.X, rect.Y, rect.X+rect.W, rect.Y+rect.H, radius, color) +} diff --git a/pkg/internal/land/root.go b/pkg/internal/land/root.go index 364950b..510849b 100644 --- a/pkg/internal/land/root.go +++ b/pkg/internal/land/root.go @@ -51,18 +51,28 @@ func (p *RootObject) ForEachChildren(cb func(child Object)) { func (p *RootObject) HandleUserEvent(event sdl.Event) { switch event := event.(type) { case *sdl.MouseButtonEvent: - if event.Type == sdl.MOUSEBUTTONDOWN { - x, y := float64(event.X), float64(event.Y) - vec := Vector2D{x, y} - p.ForEachChildren(func(child Object) { - if interChild, ok := child.(InteractableObject); ok { - isOverlap := IsOverlapWithPoint(child.(PositionedObject), vec) - if isOverlap { - interChild.OnClick(vec) + x, y := float64(event.X), float64(event.Y) + vec := Vector2D{x, y} + p.ForEachChildren(func(child Object) { + if interChild, ok := child.(InteractableObject); ok { + if IsOverlapWithPoint(child.(PositionedObject), vec) { + if event.Type == sdl.MOUSEBUTTONDOWN { + interChild.OnEvent(UserEventMouseDown, vec) + } else if event.Type == sdl.MOUSEBUTTONUP { + interChild.OnEvent(UserEventMouseUp, vec) } } - }) - } + } + }) + case *sdl.MouseMotionEvent: + x, y := float64(event.X), float64(event.Y) + vec := Vector2D{x, y} + p.ForEachChildren(func(child Object) { + if interChild, ok := child.(InteractableObject); ok { + isOverlap := IsOverlapWithPoint(child.(PositionedObject), vec) + interChild.OnEvent(UserEventMouseMove, vec, isOverlap) + } + }) } } diff --git a/pkg/internal/land/ui/button.go b/pkg/internal/land/ui/button.go index a301674..72bc250 100644 --- a/pkg/internal/land/ui/button.go +++ b/pkg/internal/land/ui/button.go @@ -2,36 +2,51 @@ package ui import ( "git.solsynth.dev/highland/codingland/pkg/internal/land" + "git.solsynth.dev/highland/codingland/pkg/internal/land/renderer" "github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/ttf" "log" ) -type ButtonObject struct { - land.BaseUIObject +type ButtonWidget struct { + land.BaseWidget - Label string - Font *ttf.Font - OnClickEvent func(mousePos land.Vector2D) + Label string + Font *ttf.Font + OnClick func(mousePos land.Vector2D) + + isHovering bool + isClicking bool } -func (p *ButtonObject) Draw(pen *sdl.Renderer) { +func (p *ButtonWidget) Draw(pen *sdl.Renderer) { + var borderColor, bgColor sdl.Color + if p.isClicking { + borderColor = sdl.Color{R: 200, G: 0, B: 0, A: 255} + bgColor = sdl.Color{R: 255, G: 0, B: 0, A: 255} + } else if p.isHovering { + borderColor = sdl.Color{R: 0, G: 200, B: 0, A: 255} + bgColor = sdl.Color{R: 0, G: 255, B: 0, A: 255} + } else { + borderColor = sdl.Color{R: 255, G: 255, B: 255, A: 255} + bgColor = sdl.Color{R: 0, G: 0, B: 0, A: 255} + } + var borderWidth int32 = 2 - pen.SetDrawColor(255, 255, 255, 255) - pen.FillRect(&sdl.Rect{ + pen.SetDrawColor(borderColor.R, borderColor.G, borderColor.B, borderColor.A) + renderer.FillRoundedRect(pen, &sdl.Rect{ X: int32(p.Position.X), Y: int32(p.Position.Y), W: int32(p.Size.X), H: int32(p.Size.Y), - }) - pen.SetDrawColor(0, 0, 0, 255) - pen.FillRect(&sdl.Rect{ + }, borderColor, 16) + renderer.FillRoundedRect(pen, &sdl.Rect{ X: int32(p.Position.X) + borderWidth/2, Y: int32(p.Position.Y) + borderWidth/2, W: int32(p.Size.X) - borderWidth, H: int32(p.Size.Y) - borderWidth, - }) + }, bgColor, 16) surface, err := p.Font.RenderUTF8Blended(p.Label, sdl.Color{R: 255, G: 255, B: 255, A: 255}) if err != nil { @@ -47,15 +62,22 @@ func (p *ButtonObject) Draw(pen *sdl.Renderer) { } defer texture.Destroy() - textRect := sdl.Rect{ - X: int32(p.BaseUIObject.Position.X), - Y: int32(p.BaseUIObject.Position.Y), - W: surface.W, - H: surface.H, - } + textWidth := surface.W + textHeight := surface.H + textX := int32(p.BaseWidget.Position.X) + (int32(p.BaseWidget.Size.X)-textWidth)/2 + textY := int32(p.BaseWidget.Position.Y) + (int32(p.BaseWidget.Size.Y)-textHeight)/2 + textRect := sdl.Rect{X: textX, Y: textY, W: textWidth, H: textHeight} pen.Copy(texture, nil, &textRect) } -func (p *ButtonObject) OnClick(mousePos land.Vector2D) { - p.OnClickEvent(mousePos) +func (p *ButtonWidget) OnEvent(event land.UserEventType, args ...any) { + switch event { + case land.UserEventMouseDown: + p.isClicking = true + p.OnClick(args[0].(land.Vector2D)) + case land.UserEventMouseUp: + p.isClicking = false + case land.UserEventMouseMove: + p.isHovering = args[1].(bool) + } } diff --git a/pkg/internal/land/vector2d.go b/pkg/internal/land/vector2d.go index f414ddd..2c01c9d 100644 --- a/pkg/internal/land/vector2d.go +++ b/pkg/internal/land/vector2d.go @@ -1,6 +1,8 @@ package land -import "fmt" +import ( + "fmt" +) type Vector2D struct { X, Y float64 diff --git a/pkg/internal/tiles/tile.go b/pkg/internal/tiles/tile.go index 7a743c6..1b8ea92 100644 --- a/pkg/internal/tiles/tile.go +++ b/pkg/internal/tiles/tile.go @@ -40,6 +40,8 @@ func (p *Tile) GetSize() land.Vector2D { func (p *Tile) OnCollide(other land.CollidableObject) { } -func (p *Tile) OnClick(mousePos land.Vector2D) { - log.Printf("user clicked tile: %s", mousePos.String()) +func (p *Tile) OnEvent(event land.UserEventType, args ...any) { + if event == land.UserEventMouseDown { + log.Printf("user clicked tile: %s", args[0].(land.Vector2D).String()) + } } diff --git a/pkg/main.go b/pkg/main.go index 31a93c4..a682656 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -45,26 +45,15 @@ func main() { } defer renderer.Destroy() - sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1") - - font, err := ttf.OpenFont("assets/fonts/NotoSans.ttf", 12) + font, err := ttf.OpenFont("assets/fonts/NotoSans.ttf", 14) if err != nil { log.Fatalf("Failed to open font: %s", err) + } else { + font.SetStyle(ttf.STYLE_BOLD) } defer font.Close() root := land.NewRootObject() - root.AddChild(&ui.ButtonObject{ - BaseUIObject: land.BaseUIObject{ - Position: land.Vector2D{X: windowWidth/2 - 60, Y: windowHeight/2 - 25}, - Size: land.Vector2D{X: 120, Y: 50}, - }, - Label: "Hello, World!", - Font: font, - OnClickEvent: func(_ land.Vector2D) { - sdl.ShowSimpleMessageBox(sdl.MESSAGEBOX_INFORMATION, "You clicked me!", "你惊扰了古希腊掌控按钮的神", window) - }, - }) root.AddChild(&entities.Player{ Position: land.Vector2D{X: windowWidth/2 - 25, Y: windowHeight/2 - 25}, Size: land.Vector2D{X: 50, Y: 50}, @@ -73,6 +62,17 @@ func main() { Position: land.Vector2D{X: 50, Y: 50}, Size: land.Vector2D{X: 50, Y: 50}, }) + root.AddChild(&ui.ButtonWidget{ + BaseWidget: land.BaseWidget{ + Position: land.Vector2D{X: windowWidth/2 - 60, Y: windowHeight/2 - 25}, + Size: land.Vector2D{X: 120, Y: 50}, + }, + Label: "Hello, World!", + Font: font, + OnClick: func(_ land.Vector2D) { + log.Println("你惊扰了古希腊掌控按钮的神") + }, + }) // 10ms delay use be 100tps average go root.RunEventLoop(10 * time.Millisecond) @@ -83,13 +83,15 @@ func main() { switch event.(type) { case *sdl.QuitEvent: running = false - case *sdl.MouseButtonEvent: + default: root.HandleUserEvent(event) } } + // Draw the entire object tree root.Draw(renderer) + // Show on the screen renderer.Present() } }