stegterm/internal/tui/model.go
2026-09-14 11:00:27 +03:00

144 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package tui реализует единственный экран stegterm — стеганография
// изображений и аудио/SSTV, без таб-бара: тут нечего переключать между
// собой, есть один режим работы, ветвящийся по типу загруженного
// файла (см. stegoKind в stegoview.go).
package tui
import (
"fmt"
"image"
tea "github.com/charmbracelet/bubbletea"
"github.com/r3g1tpr0cs/stegterm/internal/audio"
"github.com/r3g1tpr0cs/stegterm/internal/stego"
)
// --- Типы сообщений — в burterm они жили в общей корневой model.go
// (там таких вкладок было четырнадцать, у каждой свой набор), здесь
// вкладка одна, так что и сообщения естественно живут рядом с ней. ---
type stegoLoadedMsg struct {
img *image.RGBA
path string
format string
size int64
}
type audioLoadedMsg struct {
wav *audio.WAV
path string
size int64
}
type stegoResultMsg struct{ result *image.RGBA }
type stegoExtractedMsg struct{ data []byte }
type stegoSavedMsg struct{ path string }
type stegoErrMsg struct{ err error }
// Model — корневая модель приложения: тонкая обёртка над stegoModel,
// адаптирующая её под интерфейс tea.Model верхнего уровня (stegoModel
// сама по себе не реализует tea.Model — её Update возвращает stegoModel,
// не tea.Model, тот же паттерн "суб-модели", что был в burterm для
// каждой вкладки).
type Model struct {
stego stegoModel
}
// New создаёт начальное состояние.
func New() Model {
return Model{stego: newStegoView()}
}
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.stego.SetSize(msg.Width-2, msg.Height-2)
return m, nil
case tea.KeyMsg:
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
case stegoLoadedMsg:
m.stego.kind = stegoKindImage
m.stego.original = msg.img
m.stego.wav = nil
m.stego.loadedPath = msg.path
m.stego.format = msg.format
m.stego.fileSize = msg.size
m.stego.result = nil
m.stego.kittyEscape = ""
m.stego.extractedText = ""
m.stego.extractedHex = ""
m.stego.errMsg = ""
m.stego.statusMsg = fmt.Sprintf("загружено: %s (%dx%d)", msg.path, msg.img.Bounds().Dx(), msg.img.Bounds().Dy())
return m, nil
case audioLoadedMsg:
m.stego.kind = stegoKindAudio
m.stego.wav = msg.wav
m.stego.original = nil
m.stego.loadedPath = msg.path
m.stego.fileSize = msg.size
m.stego.result = nil
m.stego.kittyEscape = ""
m.stego.extractedText = ""
m.stego.extractedHex = ""
m.stego.errMsg = ""
var dur float64
if msg.wav.SampleRate > 0 {
dur = float64(len(msg.wav.Samples)) / float64(msg.wav.SampleRate)
}
m.stego.statusMsg = fmt.Sprintf("загружено: %s (%d Гц, %.2fс) — ctrl+r считает результат", msg.path, msg.wav.SampleRate, dur)
return m, nil
case stegoResultMsg:
m.stego.result = msg.result
m.stego.extractedText = ""
m.stego.extractedHex = ""
pngData, err := stego.EncodePNG(msg.result)
if err != nil {
m.stego.errMsg = err.Error()
m.stego.kittyEscape = ""
return m, nil
}
m.stego.errMsg = ""
m.stego.kittyEscape = stego.KittyEscape(pngData)
bnd := msg.result.Bounds()
m.stego.statusMsg = fmt.Sprintf("готово: %dx%d (ctrl+v — показать/повторить)", bnd.Dx(), bnd.Dy())
return m, kittyPrintCmd(m.stego.kittyEscape)
case stegoExtractedMsg:
text, hexStr := formatExtracted(msg.data)
m.stego.extractedText = text
m.stego.extractedHex = hexStr
m.stego.result = nil
m.stego.kittyEscape = ""
m.stego.errMsg = ""
m.stego.statusMsg = fmt.Sprintf("извлечено %d байт", len(msg.data))
return m, nil
case stegoSavedMsg:
m.stego.errMsg = ""
m.stego.statusMsg = "сохранено в " + msg.path
return m, nil
case stegoErrMsg:
m.stego.errMsg = msg.err.Error()
return m, nil
}
var cmd tea.Cmd
m.stego, cmd = m.stego.Update(msg)
return m, cmd
}
func (m Model) View() string {
return m.stego.View()
}