diff --git a/cmd/tins2021/app.go b/cmd/tins2021/app.go index 7dd7ccd..3140f06 100644 --- a/cmd/tins2021/app.go +++ b/cmd/tins2021/app.go @@ -1,9 +1,6 @@ package main import ( - "image" - - "opslag.de/schobers/geom" "opslag.de/schobers/tins2021" "opslag.de/schobers/zntg/play" "opslag.de/schobers/zntg/ui" @@ -14,7 +11,7 @@ type app struct { settings *tins2021.Settings - intro *introView + context *appContext } type fontDescriptor struct { @@ -41,6 +38,7 @@ func (a *app) Init(ctx ui.Context) error { if err := a.loadFonts(ctx, fontDescriptor{"debug", "fonts/FiraMono-Regular.ttf", 12}, fontDescriptor{"default", "fonts/escheresk.ttf", 32}, + fontDescriptor{"menu", "fonts/escheresk.ttf", 60}, fontDescriptor{"small", "fonts/escheresk.ttf", 16}, fontDescriptor{"title", "fonts/escher.ttf", 80}, ); err != nil { @@ -58,8 +56,12 @@ func (a *app) Init(ctx ui.Context) error { ctx.Overlays().AddOnTop(fpsOverlayName, &play.FPS{Align: ui.AlignRight}, false) - a.intro = newIntroView(ctx) - a.Content = a.intro + a.context = &appContext{ + setView: func(control ui.Control) { + a.Content = control + }, + } + a.context.ShowMainMenu(ctx) return nil } @@ -75,8 +77,6 @@ func (a *app) Handle(ctx ui.Context, e ui.Event) bool { // a.settings.Window.Size = &size case *ui.KeyDownEvent: switch e.Key { - case ui.KeyEscape: - ctx.Quit() case ui.KeyF3: ctx.Overlays().Toggle(ui.DefaultDebugOverlay) ctx.Overlays().Toggle(fpsOverlayName) @@ -89,52 +89,3 @@ func (a *app) Render(ctx ui.Context) { ctx.Renderer().Clear(ctx.Style().Palette.Background) a.Proxy.Render(ctx) } - -type introView struct { - ui.StackPanel -} - -type Scene struct { - ui.ControlBase - - Sprites []Sprite -} - -func (s *Scene) Render(ctx ui.Context) { - renderer := ctx.Renderer() - for _, sprite := range s.Sprites { - renderer.DrawTexturePoint(sprite.Texture, sprite.Position) - } -} - -type Sprite struct { - Position geom.PointF32 - Texture ui.Texture -} - -func newSpriteImage(ctx ui.Context, im image.Image) Sprite { - texture, err := ctx.Textures().CreateTextureGo("cube_1", im, false) - if err != nil { - panic("couldn't create texture") - } - return Sprite{geom.ZeroPtF32, texture} -} - -func (s Sprite) Destroy() { s.Texture.Destroy() } - -func newIntroView(ctx ui.Context) *introView { - view := &introView{} - - level := tins2021.NewLevel() - level.Randomize(100, 10) - - view.Children = []ui.Control{ - label("QBITTER", "title"), - paragraph("Welcome at Qbitter!\n"+ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ, abcdefghijklmnopqrstuvwxyz\n"+ - "", - "default"), - newLevelControl(ctx, level), - } - return view -} diff --git a/cmd/tins2021/appcontext.go b/cmd/tins2021/appcontext.go new file mode 100644 index 0000000..c6490b4 --- /dev/null +++ b/cmd/tins2021/appcontext.go @@ -0,0 +1,28 @@ +package main + +import ( + "opslag.de/schobers/tins2021" + "opslag.de/schobers/zntg/ui" +) + +type appContext struct { + setView func(ui.Control) +} + +func (app *appContext) Play(ctx ui.Context) { + level := tins2021.NewLevel() + level.Randomize(100, 10) + + app.show(newLevelControl(ctx, level)) +} + +func (app *appContext) show(control ui.Control) { + app.setView(control) +} + +func (app *appContext) ShowCredits(ctx ui.Context) {} +func (app *appContext) ShowInfo(ctx ui.Context) {} + +func (app *appContext) ShowMainMenu(ctx ui.Context) { + app.show(newMainMenu(app, ctx)) +} diff --git a/cmd/tins2021/mainmenu.go b/cmd/tins2021/mainmenu.go new file mode 100644 index 0000000..072f5bf --- /dev/null +++ b/cmd/tins2021/mainmenu.go @@ -0,0 +1,54 @@ +package main + +import ( + "opslag.de/schobers/geom" + "opslag.de/schobers/zntg/ui" +) + +type center struct { + ui.Proxy +} + +func Center(control ui.Control) ui.Control { + return ¢er{Proxy: ui.Proxy{Content: control}} +} + +func (c *center) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) { + availableWidth := bounds.Dx() + availableHeight := bounds.Dy() + desired := c.Proxy.DesiredSize(ctx, bounds.Size()) + if geom.IsNaN32(desired.X) || desired.X > availableWidth { + desired.X = availableWidth + } + if geom.IsNaN32(desired.Y) || desired.Y > availableHeight { + desired.Y = availableHeight + } + inset := bounds.Size().Sub(desired).Mul(.5) + c.Proxy.Arrange(ctx, geom.RectF32(bounds.Min.X+inset.X, bounds.Min.Y+inset.Y, bounds.Max.X-inset.X, bounds.Max.Y-inset.Y), offset, parent) +} + +type mainMenu struct { + ui.StackPanel +} + +func newMainMenu(app *appContext, ctx ui.Context) ui.Control { + menu := NewMenu() + // menu.Add("Info", func(ctx ui.Context) { app.ShowInfo(ctx) }) + menu.Add("Play", func(ctx ui.Context) { + app.Play(ctx) + }) + menu.Add("Credits", func(ctx ui.Context) { app.ShowCredits(ctx) }) + menu.Add("Quit", func(ctx ui.Context) { ctx.Quit() }) + + return Center(&mainMenu{ + StackPanel: ui.StackPanel{ + ContainerBase: ui.ContainerBase{ + Children: []ui.Control{ + label("QBITTER", "title"), + menu, + }, + }, + Orientation: ui.OrientationVertical, + }, + }) +} diff --git a/cmd/tins2021/menu.go b/cmd/tins2021/menu.go new file mode 100644 index 0000000..cc82411 --- /dev/null +++ b/cmd/tins2021/menu.go @@ -0,0 +1,128 @@ +package main + +import ( + "opslag.de/schobers/zntg/ui" +) + +type Menu struct { + ui.StackPanel + + OnEscape func(ui.Context) + + active int + buttons []*MenuButton +} + +func NewMenu() *Menu { + return &Menu{ + StackPanel: ui.StackPanel{ + Orientation: ui.OrientationVertical, + }, + } +} + +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, click func(ui.Context)) { + button := NewMenuButton(text, click) + if len(m.buttons) == 0 { + button.Over = true + } + m.buttons = append(m.buttons, button) + m.AddChild(button) +} + +func (m *Menu) Handle(ctx ui.Context, e ui.Event) bool { + if m.StackPanel.Handle(ctx, e) { + return true + } + + if len(m.buttons) == 0 { + return false + } + + switch e := e.(type) { + case *ui.KeyDownEvent: + switch e.Key { + case ui.KeyEscape: + if onEscape := m.OnEscape; onEscape != nil { + onEscape(ctx) + } + case ui.KeyDown: + m.updateActiveButton((m.active + 1) % len(m.buttons)) + case ui.KeyUp: + m.updateActiveButton((m.active + len(m.buttons) - 1) % len(m.buttons)) + case ui.KeyEnter: + m.buttons[m.active].InvokeClick(ctx) + } + case *ui.MouseMoveEvent: + for i, button := range m.buttons { + if button.IsOver() { + m.updateActiveButton(i) + break + } + } + m.updateActiveButton(m.active) + } + return false +} + +func (m *Menu) updateActiveButton(active int) { + m.active = active + for i, btn := range m.buttons { + btn.Over = i == m.active + } +} + +type MenuButton struct { + ui.Label + + Over bool + Click func(ui.Context) +} + +func NewMenuButton(text string, click func(ui.Context)) *MenuButton { + b := &MenuButton{ + Label: ui.Label{ + Text: text, + }, + Click: click, + } + b.ControlClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) { + b.InvokeClick(ctx) + }) + return b +} + +func (b *MenuButton) Handle(ctx ui.Context, e ui.Event) bool { + if b.ControlBase.Handle(ctx, e) { + return true + } + if b.IsOver() { + b.Over = true + } + return false +} + +func (b *MenuButton) InvokeClick(ctx ui.Context) { + if b.Click != nil { + b.Click(ctx) + } +} + +func (b *MenuButton) Render(ctx ui.Context) { + font := b.ActualFont(ctx) + textWidth := font.WidthOf(b.Text) + bounds := b.Bounds() + left := .5 * (bounds.Dx() - textWidth) + color := ctx.Style().Palette.Text + if b.Over { + color = ctx.Style().Palette.Primary + } + ctx.Renderer().Text(font, bounds.Min.Add2D(left, 0), color, b.Text) +} diff --git a/cmd/tins2021/tins2021.go b/cmd/tins2021/tins2021.go index f31644e..a0d7645 100644 --- a/cmd/tins2021/tins2021.go +++ b/cmd/tins2021/tins2021.go @@ -79,7 +79,7 @@ func run() error { style.Palette = &ui.Palette{ Background: zntg.MustHexColor(`#494949`), Disabled: zntg.MustHexColor(`#DEDEDE`), - Primary: zntg.MustHexColor(`#356DAD`), + Primary: zntg.MustHexColor(tins2021.Orange), PrimaryDark: zntg.MustHexColor(`#15569F`), PrimaryLight: zntg.MustHexColor(`#ABCAED`), Secondary: zntg.MustHexColor(`#4AC69A`),