diff --git a/alui/menu.go b/alui/menu.go new file mode 100644 index 0000000..21ba853 --- /dev/null +++ b/alui/menu.go @@ -0,0 +1,93 @@ +package alui + +import ( + "opslag.de/schobers/allg5" + "opslag.de/schobers/geom" +) + +type Menu struct { + Proxy + + panel *StackPanel + active int + buttons []*Button +} + +func NewMenu() *Menu { + m := &Menu{} + m.Init() + return m +} + +func (m *Menu) Init() { + m.panel = &StackPanel{Orientation: OrientationVertical} + m.Proxy.Target = m.panel +} + +func (m *Menu) Activate(i int) { + if len(m.buttons) == 0 || i < 0 { + return + } + m.active = i % len(m.buttons) +} + +func (m *Menu) Add(text string, onClick func()) { + idx := len(m.buttons) + button := &Button{Text: text, TextAlign: allg5.AlignCenter} + button.OnClick = onClick + button.OnEnter = func() { + m.updateActiveButton(idx) + } + if idx == 0 { + button.Over = true + } + m.buttons = append(m.buttons, button) + m.panel.Children = append(m.panel.Children, button) +} + +func (m *Menu) Handle(e allg5.Event) { + m.Proxy.Handle(e) + + if len(m.buttons) == 0 { + return + } + + switch e := e.(type) { + case *allg5.KeyCharEvent: + switch e.KeyCode { + case allg5.KeyDown: + m.updateActiveButton((m.active + 1) % len(m.buttons)) + case allg5.KeyUp: + m.updateActiveButton((m.active + len(m.buttons) - 1) % len(m.buttons)) + case allg5.KeyEnter: + if onClick := m.buttons[m.active].OnClick; onClick != nil { + onClick() + } + } + case *allg5.MouseMoveEvent: + for i, btn := range m.buttons { + if btn.Over { + m.updateActiveButton(i) + break + } + } + m.updateActiveButton(m.active) + } +} + +func (m *Menu) Layout(ctx *Context, bounds geom.RectangleF32) {} + +func (m *Menu) Render(ctx *Context, bounds geom.RectangleF32) { + menuHeight := m.Proxy.DesiredSize(ctx).Y + width, center := bounds.Dx(), bounds.Center() + menuBounds := geom.RectF32(.25*width, center.Y-.5*menuHeight, .75*width, center.Y+.5*menuHeight) + m.Proxy.Layout(ctx, menuBounds) + m.Proxy.Render(ctx, menuBounds) +} + +func (m *Menu) updateActiveButton(active int) { + m.active = active + for i, btn := range m.buttons { + btn.Over = i == m.active + } +} diff --git a/alui/proxy.go b/alui/proxy.go new file mode 100644 index 0000000..dd7f338 --- /dev/null +++ b/alui/proxy.go @@ -0,0 +1,44 @@ +package alui + +import ( + "opslag.de/schobers/allg5" + "opslag.de/schobers/geom" +) + +var _ Control = &Proxy{} + +type Proxy struct { + Target Control +} + +func (p *Proxy) Bounds() geom.RectangleF32 { + if p.Target != nil { + return p.Target.Bounds() + } + return geom.RectangleF32{} +} + +func (p *Proxy) DesiredSize(ctx *Context) geom.PointF32 { + if p.Target != nil { + return p.Target.DesiredSize(ctx) + } + return geom.ZeroPtF32 +} + +func (p *Proxy) Handle(e allg5.Event) { + if p.Target != nil { + p.Target.Handle(e) + } +} + +func (p *Proxy) Render(ctx *Context, bounds geom.RectangleF32) { + if p.Target != nil { + p.Target.Render(ctx, bounds) + } +} + +func (p *Proxy) Layout(ctx *Context, bounds geom.RectangleF32) { + if p.Target != nil { + p.Target.Layout(ctx, bounds) + } +} diff --git a/cmd/krampus19/console.go b/cmd/krampus19/console.go index 31ee202..b59b9b2 100644 --- a/cmd/krampus19/console.go +++ b/cmd/krampus19/console.go @@ -25,7 +25,7 @@ func newConsole(cons *gut.Console) *console { } func (c *console) Render(ctx *alui.Context, bounds geom.RectangleF32) { - back := allg5.NewColorAlpha(0, 0, 0, 0x7f) + back := allg5.NewColorAlpha(0, 0, 0, 0xaf) line := allg5.NewColor(0x7f, 0x7f, 0x7f) c.height = geom.Min32(c.height, bounds.Dy()) diff --git a/cmd/krampus19/mainmenu.go b/cmd/krampus19/mainmenu.go index 7d58d73..1a0e69b 100644 --- a/cmd/krampus19/mainmenu.go +++ b/cmd/krampus19/mainmenu.go @@ -1,41 +1,21 @@ package main import ( - "opslag.de/schobers/allg5" - "opslag.de/schobers/geom" "opslag.de/schobers/krampus19/alui" ) type mainMenu struct { - alui.ControlBase + alui.Menu ctx *Context - - buttons alui.StackPanel -} - -func (m *mainMenu) newButton(text string, onClick func()) alui.Control { - button := &alui.Button{Text: text, TextAlign: allg5.AlignCenter} - button.OnClick = onClick - return button } func (m *mainMenu) Enter(ctx *Context) error { m.ctx = ctx - m.buttons.Orientation = alui.OrientationVertical - m.buttons.Children = append(m.buttons.Children, m.newButton("Play", func() { m.ctx.Navigation.playLevel("1") })) - m.buttons.Children = append(m.buttons.Children, m.newButton("Quit", func() { m.ctx.Navigation.quit() })) + m.Init() + m.Add("Play", func() { m.ctx.Navigation.playLevel("1") }) + m.Add("Quit", func() { m.ctx.Navigation.quit() }) return nil } func (m *mainMenu) Leave() {} - -func (m *mainMenu) Handle(e allg5.Event) { m.buttons.Handle(e) } - -func (m *mainMenu) Render(ctx *alui.Context, bounds geom.RectangleF32) { - buttonsHeight := m.buttons.DesiredSize(ctx).Y - width, center := bounds.Dx(), bounds.Center() - buttonsBounds := geom.RectF32(.25*width, center.Y-.5*buttonsHeight, .75*width, center.Y+.5*buttonsHeight) - m.buttons.Layout(ctx, buttonsBounds) - m.buttons.Render(ctx, buttonsBounds) -} diff --git a/cmd/krampus19/playlevel.go b/cmd/krampus19/playlevel.go index b71ff25..87891e8 100644 --- a/cmd/krampus19/playlevel.go +++ b/cmd/krampus19/playlevel.go @@ -15,8 +15,12 @@ import ( type playLevel struct { alui.ControlBase + ctx *Context + + menu *alui.Menu + showMenu bool + name string - ctx *Context offset geom.PointF32 scale float32 keysDown keyPressedState @@ -27,9 +31,8 @@ type playLevel struct { bricks []*entity sunken []*entity steps int - - tick time.Duration - ani gut.Animations + tick time.Duration + ani gut.Animations } type keyPressedState map[allg5.Key]bool @@ -168,6 +171,11 @@ func (l *playLevel) canMove(from, dir geom.Point) bool { func (l *playLevel) Enter(ctx *Context) error { l.ctx = ctx + l.menu = alui.NewMenu() + l.menu.Add("Resume", func() { l.showMenu = false }) + l.menu.Add("Quit to main menu", func() { l.ctx.Navigation.showMainMenu() }) + l.menu.Add("Quit to desktop", func() { l.ctx.Navigation.quit() }) + l.keysDown = keyPressedState{} l.level = l.ctx.Levels[l.name] l.bricks = nil @@ -263,8 +271,28 @@ func (l *playLevel) Handle(e allg5.Event) { switch e := e.(type) { case *allg5.KeyDownEvent: l.keysDown[e.KeyCode] = true + case *allg5.KeyUpEvent: + l.keysDown[e.KeyCode] = false + } + if l.showMenu { + l.menu.Handle(e) + switch e := e.(type) { + case *allg5.KeyDownEvent: + switch e.KeyCode { + case allg5.KeyEscape: + l.showMenu = false + } + } + return + } + + switch e := e.(type) { + case *allg5.KeyDownEvent: switch e.KeyCode { + case allg5.KeyEscape: + l.showMenu = true + l.menu.Activate(0) case l.ctx.Settings.Controls.MoveUp: l.tryPlayerMove(geom.Pt(0, -1), e.KeyCode) case l.ctx.Settings.Controls.MoveRight: @@ -274,8 +302,6 @@ func (l *playLevel) Handle(e allg5.Event) { case l.ctx.Settings.Controls.MoveLeft: l.tryPlayerMove(geom.Pt(-1, 0), e.KeyCode) } - case *allg5.KeyUpEvent: - l.keysDown[e.KeyCode] = false } } @@ -342,4 +368,9 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { font := ctx.Fonts.Get("default") steps := fmt.Sprintf("STEPS: %d", l.steps) ctx.Fonts.DrawAlignFont(font, bounds.Min.X, 24, bounds.Max.X, ctx.Palette.Text, allg5.AlignCenter, steps) + + if l.showMenu { + allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, allg5.NewColorAlpha(0, 0, 0, 0xaf)) + l.menu.Render(ctx, bounds) + } }