Files
sanspie f22242e5aa
ci / release (push) Successful in 5m5s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 4m41s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 7m14s
Stage 11g2–11g3: registry texts, CLI and the site in Russian (D348)
The remaining editor strings and undo step names; component, field, enum, setting, diagnostic and Script API texts under schema.*, enum.*, setting.*, diag.* and api.*; the launcher, the in-game Settings View with a Language setting, the CLI and aego schema show --lang. Every documentation page has a .ru.md translation; the site has a full Russian tree with hreflang, a language switch and ru/llms.txt; aego docs check --lang ru. New doc.tutorial and doc.recipe icons. Fix: an animator restored by a rewind prepares its controller before the replayed step.
2026-10-25 15:31:22 +03:00

128 lines
4.2 KiB
Go

package editor
import (
"strconv"
"time"
"aego/core/ids"
"aego/core/mathx"
"aego/editor/core/context"
"aego/engine/i18n"
"aego/modules/audio"
"aego/scene"
"aego/ui/core"
"aego/ui/widgets"
)
func twoButtons(ui *core.Context) (mathx.Rect, mathx.Rect) {
row := ui.Cursor().Next(0)
w := (row.W - 4) / 2
return mathx.Rect{X: row.X, Y: row.Y, W: w, H: row.H}, mathx.Rect{X: row.X + w + 4, Y: row.Y, W: w, H: row.H}
}
func seconds(v float32) string {
return strconv.FormatFloat(float64(v), 'f', 1, 32)
}
func progress(ui *core.Context, pos, length float32, on bool) {
th := &ui.Theme
bar := ui.Cursor().Next(6)
ui.RoundRect(bar, 3, th.Widget)
if length > 0 {
k := mathx.Clamp(pos/length, 0, 1)
col := th.AccentDim
if on {
col = th.Accent
}
ui.RoundRect(mathx.Rect{X: bar.X, Y: bar.Y, W: bar.W * k, H: bar.H}, 3, col)
}
text := seconds(pos) + " s"
if length > 0 {
text += i18n.Tr("i18n.audio.of_length_s", " of {length} s", i18n.Str("length", seconds(length)))
}
widgets.LabelDim(ui, text)
}
func (p *plugin) sourceInspector(ctx *context.Editor, nodes []ids.NodeID, def func()) {
def()
d := ctx.Doc()
if d == nil || len(nodes) != 1 {
return
}
ui := ctx.UI
h := d.Scene().Handle(nodes[0])
if svc := audio.Of(p.world()); svc != nil {
p.playControls(ui, svc, h)
return
}
if c, ok := scene.Get[audio.AudioSource2D](d.Scene(), h); ok {
p.previewControls(ui, d.Scene(), c)
}
}
func (p *plugin) playControls(ui *core.Context, svc *audio.Service, h scene.NodeHandle) {
play, stop := twoButtons(ui)
if widgets.ButtonRect(ui, ui.ID("audio.play"), play, i18n.Tr("i18n.audio.play", "Play")) {
svc.PlaySource(h)
}
if widgets.ButtonRect(ui, ui.ID("audio.stop"), stop, i18n.Tr("i18n.audio.stop", "Stop")) {
svc.StopSource(h, 0)
}
if c, ok := scene.Get[audio.AudioSource2D](svc.Scene(), h); ok {
progress(ui, c.Position, svc.SourceLength(h), c.Playing)
}
}
func (p *plugin) previewControls(ui *core.Context, s *scene.Scene, c *audio.AudioSource2D) {
mod := p.module()
if mod == nil {
return
}
if c.Clip.IsZero() {
if !c.Event.IsZero() {
widgets.LabelDim(ui, i18n.Tr("i18n.audio.events_are_heard_in_play", "Events are heard in Play; Preview plays Clip"))
}
return
}
play, stop := twoButtons(ui)
if widgets.ButtonRect(ui, ui.ID("audio.preview"), play, i18n.Tr("i18n.audio.preview", "Preview")) {
p.notify(mod.Preview(s.Resolver(), c.Clip.ID, c.Loop))
}
if widgets.ButtonRect(ui, ui.ID("audio.unpreview"), stop, i18n.Tr("i18n.audio.stop", "Stop")) {
mod.StopPreview()
}
clip, err := mod.Clip(s.Resolver(), c.Clip.ID)
if err != nil {
widgets.LabelWrapped(ui, err.Error(), true)
return
}
pos, on := mod.PreviewPosition()
if mod.PreviewClip() != c.Clip.ID {
pos, on = 0, false
}
progress(ui, pos, clip.Seconds, on)
}
func ms(d time.Duration) float64 {
return float64(d) / float64(time.Millisecond)
}
func (p *plugin) profile(ed *context.Editor) {
ui := ed.UI
mod := p.module()
if mod == nil || mod.Mixer() == nil {
widgets.LabelDim(ui, i18n.Tr("i18n.audio.the_audio_output_is_not", "The audio output is not open"))
return
}
st, c, spec := mod.Stats(), mod.Counts(), mod.Spec()
world := 0
if svc := audio.Of(p.world()); svc != nil {
world = svc.Voices()
}
widgets.Label(ui, i18n.Tr("i18n.audio.stats.output", "Output {rate} Hz, {frames} frames per block", i18n.Int("rate", int64(spec.SampleRate)), i18n.Int("frames", int64(spec.Frames))))
widgets.Label(ui, i18n.Tr("i18n.audio.stats.voices", "Voices {all}, in this world {world}", i18n.Int("all", int64(st.Voices)), i18n.Int("world", int64(world))))
widgets.Label(ui, i18n.Tr("i18n.audio.stats.mix", "Mix {max} ms max, {avg} ms average", i18n.Float("max", float64(ms(st.MixMax)), 2), i18n.Float("avg", float64(ms(st.MixAvg)), 2)))
widgets.Label(ui, i18n.Tr("i18n.audio.stats.underruns", "Underruns {underruns}, dropped commands {dropped}, blocks {blocks}", i18n.Int("underruns", int64(st.Underruns)), i18n.Int("dropped", int64(st.Dropped)), i18n.Int("blocks", int64(st.Blocks))))
widgets.Label(ui, i18n.Tr("i18n.audio.stats.voices_end", "Finished {finished}, stolen {stolen}, rejected {rejected}, starved {starved}", i18n.Int("finished", int64(c.Finished)), i18n.Int("stolen", int64(c.Stolen)), i18n.Int("rejected", int64(c.Rejected)), i18n.Int("starved", int64(c.Starved))))
}