mirror of
https://github.com/BigJk/end_of_eden.git
synced 2026-02-06 10:48:09 +00:00
feat: experimental animation viewer
This commit is contained in:
parent
dfaf395bfa
commit
cbaf16f764
81
ui/components/gifviewer/gifviewer.go
Normal file
81
ui/components/gifviewer/gifviewer.go
Normal file
@ -0,0 +1,81 @@
|
||||
package gifviewer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/image"
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GifAnimationFinished string
|
||||
|
||||
type GifAnimationFrame string
|
||||
|
||||
type Model struct {
|
||||
ui.MenuBase
|
||||
|
||||
id string
|
||||
parent tea.Model
|
||||
|
||||
fps int
|
||||
lastFrame int64
|
||||
elapsedMs int64
|
||||
frames []string
|
||||
}
|
||||
|
||||
func New(parent tea.Model, file string, fps int, width int, height int) (tea.Model, error) {
|
||||
var options []image.Option
|
||||
|
||||
if width > 0 && height == 0 {
|
||||
options = append(options, image.WithMaxWidth(width))
|
||||
} else if width > 0 && height > 0 {
|
||||
options = append(options, image.WithResize(width, height))
|
||||
} else {
|
||||
options = append(options, image.WithMaxWidth(100))
|
||||
}
|
||||
|
||||
frames, err := image.FetchAnimation(file, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Model{id: fmt.Sprint(rand.Int()), parent: parent, fps: fps, frames: frames, lastFrame: time.Now().UnixMilli()}, nil
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.Size = msg
|
||||
case GifAnimationFrame:
|
||||
if string(msg) != m.id {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
m.elapsedMs += time.Now().UnixMilli() - m.lastFrame
|
||||
m.lastFrame = time.Now().UnixMilli()
|
||||
|
||||
if m.Frame() == len(m.frames)-1 {
|
||||
return m, func() tea.Msg {
|
||||
return GifAnimationFinished(m.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.Tick(time.Second/time.Duration(m.fps), func(t time.Time) tea.Msg {
|
||||
return GifAnimationFrame(m.id)
|
||||
})
|
||||
}
|
||||
|
||||
func (m Model) Frame() int {
|
||||
return int(m.elapsedMs / (1000 / int64(m.fps)) % int64(len(m.frames)))
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return m.frames[m.Frame()]
|
||||
}
|
||||
80
ui/components/loader/loader.go
Normal file
80
ui/components/loader/loader.go
Normal file
@ -0,0 +1,80 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
"github.com/BigJk/end_of_eden/ui/style"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoaderTick string
|
||||
|
||||
var loaderFrames = strings.Split("◐◓◑◒", "")
|
||||
|
||||
type Model struct {
|
||||
ui.MenuBase
|
||||
|
||||
parent tea.Model
|
||||
text chan string
|
||||
done chan bool
|
||||
|
||||
currentMessage string
|
||||
lastFrame int64
|
||||
elapsedMs int64
|
||||
}
|
||||
|
||||
func New(parent tea.Model, loadingMessage string) (Model, chan bool, chan string) {
|
||||
m := Model{
|
||||
parent: parent,
|
||||
text: make(chan string, 1),
|
||||
done: make(chan bool, 1),
|
||||
currentMessage: loadingMessage,
|
||||
lastFrame: time.Now().UnixMilli(),
|
||||
}
|
||||
return m, m.done, m.text
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if len(m.done) > 0 {
|
||||
<-m.done
|
||||
return m.parent, nil
|
||||
}
|
||||
|
||||
if len(m.text) > 0 {
|
||||
m.currentMessage = <-m.text
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.Size = msg
|
||||
case LoaderTick:
|
||||
m.elapsedMs += time.Now().UnixMilli() - m.lastFrame
|
||||
m.lastFrame = time.Now().UnixMilli()
|
||||
}
|
||||
|
||||
return m, tea.Tick(time.Second/5, func(t time.Time) tea.Msg {
|
||||
return LoaderTick("")
|
||||
})
|
||||
}
|
||||
|
||||
func (m Model) Frame() int {
|
||||
return int(m.elapsedMs / (1000 / 10) % int64(len(loaderFrames)))
|
||||
}
|
||||
|
||||
func (m Model) loadingIndicator() string {
|
||||
width := 20.0
|
||||
leftPad := ((1 + math.Sin(float64(m.elapsedMs)/1000)) / 2) * width
|
||||
rightPad := width - leftPad
|
||||
return "[ " + strings.Repeat(" ", int(leftPad)) + style.RedText.Render("=") + strings.Repeat(" ", int(rightPad)) + " ]"
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return lipgloss.Place(m.Size.Width, m.Size.Height, lipgloss.Center, lipgloss.Center, lipgloss.JoinVertical(lipgloss.Center, style.RedText.Render(ui.Title), "", m.currentMessage, "", m.loadingIndicator()))
|
||||
}
|
||||
39
ui/components/waitfor/waitfor.go
Normal file
39
ui/components/waitfor/waitfor.go
Normal file
@ -0,0 +1,39 @@
|
||||
package waitfor
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
model tea.Model
|
||||
cond func(msg tea.Msg) bool
|
||||
}
|
||||
|
||||
func New(model tea.Model, cond func(msg tea.Msg) bool) tea.Model {
|
||||
return Model{
|
||||
model: model,
|
||||
cond: cond,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.cond(msg) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
model, modelCmd := m.model.Update(msg)
|
||||
if model == nil {
|
||||
return nil, nil
|
||||
}
|
||||
m.model = model
|
||||
|
||||
return m, modelCmd
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return m.model.View()
|
||||
}
|
||||
@ -20,9 +20,8 @@ var (
|
||||
type Model struct {
|
||||
ui.MenuBase
|
||||
|
||||
zones *zone.Manager
|
||||
lastMouse tea.MouseMsg
|
||||
parent tea.Model
|
||||
zones *zone.Manager
|
||||
parent tea.Model
|
||||
}
|
||||
|
||||
func New(parent tea.Model, zones *zone.Manager) Model {
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/BigJk/end_of_eden/ui/root"
|
||||
"github.com/BigJk/end_of_eden/ui/style"
|
||||
"github.com/BigJk/end_of_eden/util"
|
||||
"github.com/BigJk/imeji"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
@ -185,7 +184,7 @@ func (m Model) eventUpdateContent() Model {
|
||||
}
|
||||
res = string(ansRes)
|
||||
} else {
|
||||
imgRes, err := image.Fetch(file, imeji.WithMaxWidth(100))
|
||||
imgRes, err := image.Fetch(file, image.WithMaxWidth(100))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -427,7 +427,7 @@ func (m Model) fightStatusBottom() string {
|
||||
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(style.BaseWhite)).Padding(0, 4, 0, 4).Render(fmt.Sprintf("Deck: %d", len(fight.Deck))),
|
||||
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#FFFF00")).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Used: %d", len(fight.Used))),
|
||||
lipgloss.NewStyle().Bold(true).Foreground(style.BaseRed).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Exhausted: %d", len(fight.Exhausted))),
|
||||
lipgloss.NewStyle().Bold(true).Foreground(style.BaseGreen).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Action Points: %d", fight.CurrentPoints)),
|
||||
lipgloss.NewStyle().Bold(true).Foreground(style.BaseGreen).Padding(0, 4, 0, 0).Render(fmt.Sprintf("Action Points: (%d) %s", fight.CurrentPoints, strings.Repeat("• ", fight.CurrentPoints))),
|
||||
m.zones.Mark(ZonePlayerInspect, components.StatusEffects(m.Session, m.Session.GetPlayer())),
|
||||
),
|
||||
),
|
||||
|
||||
70
ui/menus/intro/intro.go
Normal file
70
ui/menus/intro/intro.go
Normal file
@ -0,0 +1,70 @@
|
||||
package intro
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
"github.com/BigJk/end_of_eden/ui/components/gifviewer"
|
||||
"github.com/BigJk/end_of_eden/ui/style"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var frameStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.ThickBorder(), true).
|
||||
BorderForeground(style.BaseRedDarker).
|
||||
Padding(0, 1)
|
||||
|
||||
type Model struct {
|
||||
ui.MenuBase
|
||||
|
||||
parent tea.Model
|
||||
gif tea.Model
|
||||
}
|
||||
|
||||
func New(parent tea.Model) Model {
|
||||
return Model{
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.gif == nil {
|
||||
gif, err := gifviewer.New(m, "intro.gif", 10, 100, 0)
|
||||
if err != nil {
|
||||
return m.parent, nil
|
||||
}
|
||||
m.gif = gif
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.Size = msg
|
||||
case tea.KeyMsg:
|
||||
if msg.Type == tea.KeyEscape {
|
||||
return m.parent, nil
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.gif, cmd = m.gif.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
if m.gif == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
frame := m.gif.View()
|
||||
frameFramed := frameStyle.Render(
|
||||
lipgloss.NewStyle().MaxWidth(100 - 4).Render(frame[:len(frame)-1]))
|
||||
|
||||
return lipgloss.Place(m.Size.Width, m.Size.Height, lipgloss.Center, lipgloss.Center, lipgloss.JoinVertical(
|
||||
lipgloss.Center,
|
||||
frameFramed,
|
||||
frameStyle.Render(lipgloss.NewStyle().Width(100-4).Render(ui.About)),
|
||||
))
|
||||
}
|
||||
@ -7,13 +7,14 @@ import (
|
||||
"github.com/BigJk/end_of_eden/image"
|
||||
"github.com/BigJk/end_of_eden/settings"
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
"github.com/BigJk/end_of_eden/ui/components/loader"
|
||||
"github.com/BigJk/end_of_eden/ui/menus/about"
|
||||
"github.com/BigJk/end_of_eden/ui/menus/gameview"
|
||||
"github.com/BigJk/end_of_eden/ui/menus/intro"
|
||||
"github.com/BigJk/end_of_eden/ui/menus/mods"
|
||||
uiset "github.com/BigJk/end_of_eden/ui/menus/settings"
|
||||
"github.com/BigJk/end_of_eden/ui/root"
|
||||
"github.com/BigJk/end_of_eden/ui/style"
|
||||
"github.com/BigJk/imeji"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
zone "github.com/lrstanley/bubblezone"
|
||||
@ -34,10 +35,11 @@ type Model struct {
|
||||
|
||||
settingValues []uiset.Value
|
||||
settingSaver uiset.Saver
|
||||
didLoad bool
|
||||
}
|
||||
|
||||
func NewModel(zones *zone.Manager, settings settings.Settings, values []uiset.Value, saver uiset.Saver) Model {
|
||||
img, _ := image.Fetch("title.png", imeji.WithResize(180, 9))
|
||||
img, _ := image.Fetch("title.png", image.WithResize(180, 9))
|
||||
|
||||
audio.PlayMusic("planet_mining")
|
||||
|
||||
@ -58,6 +60,19 @@ func (m Model) Init() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if !m.didLoad {
|
||||
m.didLoad = true
|
||||
|
||||
if m.settings.GetBool("experimental") {
|
||||
l, done, _ := loader.New(intro.New(m), "Initial loading")
|
||||
go func() {
|
||||
_, _ = image.FetchAnimation("intro.gif", image.WithMaxWidth(100))
|
||||
done <- true
|
||||
}()
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.Size = msg
|
||||
|
||||
@ -8,6 +8,4 @@ var Title = `▄▄▄ . ▐ ▄ ·▄▄▄▄ ·▄▄▄ ▄▄
|
||||
▀▀▀ ▀▀ █▪▀▀▀▀▀• ▀█▄▀▪▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀ █▪`
|
||||
|
||||
// About is the about text.
|
||||
var About = `About End Of Eden
|
||||
|
||||
Welcome to a world 500 years in the future, ravaged by climate change and nuclear wars. The remaining humans have become few and far between, replaced by mutated and plant-based creatures. In this gonzo-fantasy setting, you find yourself awakening from cryo sleep in an underground facility, long forgotten and alone. With all other cryosleep capsules broken, it's up to you to navigate this strange and dangerous world and uncover the secrets that led to your isolation...`
|
||||
var About = `Welcome to a world 500 years in the future, ravaged by climate change and nuclear wars. The remaining humans have become few and far between, replaced by mutated and plant-based creatures. In this gonzo-fantasy setting, you find yourself awakening from cryo sleep in an underground facility, long forgotten and alone. With all other cryosleep capsules broken, it's up to you to navigate this strange and dangerous world and uncover the secrets that led to your isolation...`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user