Media: components 341-360
This commit is contained in:
111 files changed
+6831
-1
No files matched your search
@@ -1,3 +1,118 @@
|
||||
package main
|
||||
|
||||
var mediaPages = []page{}
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
mujica "mujicaui"
|
||||
"mujicaui/theme"
|
||||
)
|
||||
|
||||
const mediaCategory = "Media"
|
||||
|
||||
var mediaPages = []page{
|
||||
{ID: "341", Name: "ImageCropper", ZhName: "图片裁剪", Category: mediaCategory, Demo: imageCropperDemo, Code: imageCropperCode,
|
||||
Keys: "方向键移动裁剪框;Shift+方向键缩放;R / Shift+R 向右 / 向左旋转。"},
|
||||
{ID: "342", Name: "ImageAnnotator", ZhName: "图片标注", Category: mediaCategory, Demo: imageAnnotatorDemo, Code: imageAnnotatorCode,
|
||||
Keys: "[ / ] 切换选中标注;方向键移动;Delete 删除;Escape 取消选择。"},
|
||||
{ID: "343", Name: "ImageThumbnail", ZhName: "缩略图", Category: mediaCategory, Demo: imageThumbnailDemo, Code: imageThumbnailCode,
|
||||
Keys: "Tab 聚焦;Enter / Space 激活。"},
|
||||
{ID: "344", Name: "Lightbox", ZhName: "灯箱", Category: mediaCategory, Demo: lightboxDemo, Code: lightboxCode,
|
||||
Keys: "← / → 切换;Home / End 首尾;Escape 关闭并恢复焦点。"},
|
||||
{ID: "345", Name: "VideoPlayer", ZhName: "视频播放器", Category: mediaCategory, Demo: videoPlayerDemo, Code: videoPlayerCode,
|
||||
Keys: "画面聚焦时 Space / K 播放暂停,← / → 跳转 5 秒,F 全屏,C 字幕。"},
|
||||
{ID: "346", Name: "VideoScrubber", ZhName: "视频时间轴", Category: mediaCategory, Demo: videoScrubberDemo, Code: videoScrubberCode,
|
||||
Keys: "← / → 跳转一个步长;Home / End 开头结尾。"},
|
||||
{ID: "347", Name: "VideoThumbnailStrip", ZhName: "缩略帧条", Category: mediaCategory, Demo: videoStripDemo, Code: videoStripCode,
|
||||
Keys: "Tab 聚焦帧条;← / → 上下帧;Home / End 首尾帧。"},
|
||||
{ID: "348", Name: "AudioPlayer", ZhName: "音频播放器", Category: mediaCategory, Demo: audioPlayerDemo, Code: audioPlayerCode,
|
||||
Keys: "Space 播放或暂停;进度条聚焦时 ← / → 跳转 5 秒。"},
|
||||
{ID: "349", Name: "AudioWaveform", ZhName: "音频波形", Category: mediaCategory, Demo: audioWaveformDemo, Code: audioWaveformCode,
|
||||
Keys: "← / → 跳转 5 秒;Shift+← / → 调整选区;Escape 清除选区。"},
|
||||
{ID: "350", Name: "AudioSpectrum", ZhName: "音频频谱", Category: mediaCategory, Demo: audioSpectrumDemo, Code: audioSpectrumCode,
|
||||
Keys: "Tab 聚焦;← / → 逐个读出频带;Home / End 首尾。"},
|
||||
{ID: "351", Name: "VolumeControl", ZhName: "音量控制", Category: mediaCategory, Demo: volumeDemo, Code: volumeCode,
|
||||
Keys: "← / → 音量 ±5%;Home / End;M 静音。"},
|
||||
{ID: "352", Name: "PlaybackSpeedControl", ZhName: "倍速选择", Category: mediaCategory, Demo: speedDemo, Code: speedCode,
|
||||
Keys: "Enter / Space 打开;↑ / ↓ 移动;Enter 确认;Escape 关闭。"},
|
||||
{ID: "353", Name: "MediaControls", ZhName: "媒体控制", Category: mediaCategory, Demo: mediaControlsDemo, Code: mediaControlsCode,
|
||||
Keys: "Tab 进入;← / → 在按钮间移动;Enter / Space 按下。"},
|
||||
{ID: "354", Name: "Playlist", ZhName: "播放列表", Category: mediaCategory, Demo: playlistDemo, Code: playlistCode,
|
||||
Keys: "↑ / ↓ 选择;Enter 播放;Alt+↑ / ↓ 移动;Delete 移除;可拖动排序。"},
|
||||
{ID: "355", Name: "SubtitleEditor", ZhName: "字幕编辑器", Category: mediaCategory, Demo: subtitleDemo, Code: subtitleCode,
|
||||
Keys: "Tab 在一行的开始、结束、文本、删除间移动;时间格式 m:ss.mmm。"},
|
||||
{ID: "356", Name: "ScreenRecorderControls", ZhName: "录屏控制", Category: mediaCategory, Demo: recorderDemo, Code: recorderCode,
|
||||
Keys: "Tab 在按钮间移动;Enter / Space 按下。"},
|
||||
{ID: "357", Name: "CameraPreview", ZhName: "摄像头预览", Category: mediaCategory, Demo: cameraDemo, Code: cameraCode,
|
||||
Keys: "Tab 聚焦预览;M 切换镜像。"},
|
||||
{ID: "358", Name: "MicLevelMeter", ZhName: "麦克风电平", Category: mediaCategory, Demo: micDemo, Code: micCode,
|
||||
Keys: "不获取焦点;读数随描述朗读。"},
|
||||
{ID: "359", Name: "DeviceSelector", ZhName: "设备选择", Category: mediaCategory, Demo: deviceDemo, Code: deviceCode,
|
||||
Keys: "Tab 在选择框间移动;Enter 打开;输入字母跳转。"},
|
||||
{ID: "360", Name: "ImageCompare", ZhName: "前后对比", Category: mediaCategory, Demo: compareDemo, Code: compareCode,
|
||||
Keys: "← / → 移动 1%;Home / End;可拖动。"},
|
||||
}
|
||||
|
||||
// mediaSection titles a part of a demo.
|
||||
func mediaSection(c *ui.Context, title string, fn func()) {
|
||||
ui.Column(c).Gap(8).FillWidth().Children(func() {
|
||||
ui.Text(c, title).FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(mujica.Tokens(c).TextMuted)
|
||||
fn()
|
||||
})
|
||||
}
|
||||
|
||||
// mediaCheck is a parameter checkbox that logs its changes.
|
||||
func mediaCheck(c *ui.Context, log func(string), v *bool, label string) {
|
||||
before := *v
|
||||
mujica.Checkbox(c, v, label, mujica.CheckboxOptions{})
|
||||
if *v != before {
|
||||
log(fmt.Sprintf("%s = %v", label, *v))
|
||||
}
|
||||
}
|
||||
|
||||
// mediaPicture draws a w×h court scene in variant hues.
|
||||
func mediaPicture(w, h, variant int) *image.RGBA {
|
||||
walls := []color.RGBA{{36, 28, 32, 255}, {242, 235, 221, 255}, {73, 42, 54, 255}}
|
||||
arch := []color.RGBA{{125, 32, 52, 255}, {173, 145, 100, 255}, {53, 94, 128, 255}}
|
||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
cx := float64(w) / 2
|
||||
for y := range h {
|
||||
for x := range w {
|
||||
c := walls[variant%3]
|
||||
dx, dy := (float64(x)-cx)/(float64(w)*0.28), (float64(y)-float64(h)*0.55)/(float64(h)*0.5)
|
||||
if dy > 0 && math.Abs(dx) < 1 || dx*dx+dy*dy < 1 {
|
||||
c = arch[variant%3]
|
||||
}
|
||||
if x%(w/6) < w/40 || y > h*7/8 {
|
||||
c = color.RGBA{189, 161, 114, 255}
|
||||
}
|
||||
if math.Abs(float64(x)-cx)/float64(w)+math.Abs(float64(y)-float64(h)*0.45)/float64(h) < 0.06 {
|
||||
c = color.RGBA{251, 247, 239, 255}
|
||||
}
|
||||
img.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
var (
|
||||
mediaPhotos = []*image.RGBA{mediaPicture(480, 270, 0), mediaPicture(480, 270, 1), mediaPicture(360, 360, 2)}
|
||||
mediaBitmaps = func() []*ui.Bitmap {
|
||||
out := make([]*ui.Bitmap, len(mediaPhotos))
|
||||
for i, p := range mediaPhotos {
|
||||
out[i] = ui.NewBitmap(p)
|
||||
}
|
||||
return out
|
||||
}()
|
||||
)
|
||||
|
||||
// mediaClockLabel writes d as m:ss for event logs.
|
||||
func mediaClockLabel(d time.Duration) string {
|
||||
s := int(d / time.Second)
|
||||
return fmt.Sprintf("%d:%02d", s/60, s%60)
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
// mediaClockState simulates the caller's playback: position advances while playing.
|
||||
type mediaClockState struct {
|
||||
pos, dur time.Duration
|
||||
playing bool
|
||||
rate float64
|
||||
last time.Time
|
||||
}
|
||||
|
||||
// tick advances the position; frames are asked only while playing.
|
||||
func (m *mediaClockState) tick(c *ui.Context) {
|
||||
now := c.Now()
|
||||
if m.playing {
|
||||
if !m.last.IsZero() {
|
||||
m.pos += time.Duration(float64(now.Sub(m.last)) * m.rate)
|
||||
}
|
||||
if m.pos >= m.dur {
|
||||
m.pos, m.playing = m.dur, false
|
||||
} else {
|
||||
c.AnimationFrame()
|
||||
}
|
||||
}
|
||||
m.last = now
|
||||
}
|
||||
|
||||
func videoPlayerDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
clock mediaClockState
|
||||
subs, full, pip, noFrame bool
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "345", func() state {
|
||||
return state{clock: mediaClockState{dur: 3 * time.Minute, rate: 1}, subs: true}
|
||||
})
|
||||
s.clock.tick(c)
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).Children(func() {
|
||||
mediaCheck(c, log, &s.noFrame, "No frame")
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
})
|
||||
})
|
||||
cues := []string{"The curtain rises.", "Welcome to the masquerade.", "Ave Mujica."}
|
||||
var frame ui.ImageSource = mediaBitmaps[int(s.clock.pos/(20*time.Second))%3]
|
||||
if s.noFrame {
|
||||
frame = nil
|
||||
}
|
||||
st := mujica.VideoState{Frame: frame, Position: s.clock.pos, Duration: s.clock.dur, Playing: s.clock.playing,
|
||||
Rate: s.clock.rate, Buffered: []mujica.MediaRange{{Start: 0, End: s.clock.pos + 40*time.Second}},
|
||||
Subtitle: cues[int(s.clock.pos/(10*time.Second))%3], SubtitlesOn: s.subs, Fullscreen: s.full, PiP: s.pip}
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(640).Children(func() {
|
||||
mujica.VideoPlayer(c, st, mujica.VideoPlayerOptions{
|
||||
OnPlay: func() { s.clock.playing = true; log("play") },
|
||||
OnPause: func() { s.clock.playing = false; log("pause") },
|
||||
OnSeek: func(d time.Duration) { s.clock.pos = d; log("seek " + mediaClockLabel(d)) },
|
||||
OnRate: func(r float64) { s.clock.rate = r; log(fmt.Sprintf("rate %v×", r)) },
|
||||
OnSubtitles: func(on bool) { s.subs = on; log(fmt.Sprintf("subtitles %v", on)) },
|
||||
OnFullscreen: func(on bool) { s.full = on; log(fmt.Sprintf("full screen requested %v", on)) },
|
||||
OnPiP: func(on bool) { s.pip = on; log(fmt.Sprintf("picture in picture requested %v", on)) },
|
||||
Disabled: s.disabled,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func videoScrubberDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
pos time.Duration
|
||||
preview, disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "346", func() state { return state{pos: 40 * time.Second, preview: true} })
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).Children(func() {
|
||||
mediaCheck(c, log, &s.preview, "Preview slot")
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
})
|
||||
})
|
||||
opts := mujica.VideoScrubberOptions{Duration: 3 * time.Minute, Disabled: s.disabled,
|
||||
Buffered: []mujica.MediaRange{{Start: 0, End: 90 * time.Second}, {Start: 120 * time.Second, End: 140 * time.Second}}}
|
||||
if s.preview {
|
||||
opts.Preview = func(at time.Duration) {
|
||||
ui.Image(c, mediaBitmaps[int(at/(time.Minute))%3]).Fit(ui.Cover).Fill()
|
||||
}
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(560).Padding(110, 0, 0, 0).Children(func() {
|
||||
if mujica.VideoScrubber(c, &s.pos, opts).Changed() {
|
||||
log("seek " + mediaClockLabel(s.pos))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func audioPlayerDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
clock mediaClockState
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "348", func() state { return state{clock: mediaClockState{dur: 4*time.Minute + 5*time.Second, rate: 1}} })
|
||||
s.clock.tick(c)
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(560).Children(func() {
|
||||
mujica.AudioPlayer(c, mujica.AudioState{Title: "Ave Mujica — Overture", Position: s.clock.pos, Duration: s.clock.dur, Playing: s.clock.playing},
|
||||
mujica.AudioPlayerOptions{
|
||||
OnPlay: func() { s.clock.playing = true; log("play") },
|
||||
OnPause: func() { s.clock.playing = false; log("pause") },
|
||||
OnSeek: func(d time.Duration) { s.clock.pos = d; log("seek " + mediaClockLabel(d)) },
|
||||
Disabled: s.disabled,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// mediaSamples is a synthetic voice-like recording, made once.
|
||||
var mediaSamples = func() []float32 {
|
||||
s := make([]float32, 120000)
|
||||
for i := range s {
|
||||
env := 0.25 + 0.7*math.Abs(math.Sin(float64(i)/9000))*math.Abs(math.Sin(float64(i)/2100))
|
||||
s[i] = float32(env * math.Sin(float64(i)/7))
|
||||
}
|
||||
return s
|
||||
}()
|
||||
|
||||
func audioWaveformDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
pos time.Duration
|
||||
sel mujica.MediaRange
|
||||
status int
|
||||
selecting bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "349", func() state { return state{pos: 20 * time.Second, selecting: true} })
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
|
||||
before := s.status
|
||||
mujica.SegmentedControl(c, &s.status, []mujica.Segment{{Label: "Ready"}, {Label: "Loading"}, {Label: "Failed"}, {Label: "Empty"}}, mujica.SegmentedControlOptions{Label: "Status"})
|
||||
if s.status != before {
|
||||
log(fmt.Sprintf("status %d", s.status))
|
||||
}
|
||||
mediaCheck(c, log, &s.selecting, "Selectable")
|
||||
})
|
||||
})
|
||||
opts := mujica.AudioWaveformOptions{Duration: time.Minute, Position: s.pos, OnSeek: func(d time.Duration) { s.pos = d; log("seek " + mediaClockLabel(d)) }}
|
||||
if s.selecting {
|
||||
opts.Selection = &s.sel
|
||||
}
|
||||
data := mediaSamples
|
||||
switch s.status {
|
||||
case 1:
|
||||
opts.Status, data = mujica.DataLoading, nil
|
||||
case 2:
|
||||
opts.Status, opts.Error, data = mujica.DataFailed, "The recording could not be decoded.", nil
|
||||
case 3:
|
||||
data = nil
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
if mujica.AudioWaveform(c, data, opts).Changed() {
|
||||
log(fmt.Sprintf("selection %s – %s", mediaClockLabel(s.sel.Start), mediaClockLabel(s.sel.End)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func audioSpectrumDemo(c *ui.Context, log func(string)) {
|
||||
type state struct{ live bool }
|
||||
s := ui.Local(c.Root(), "350", func() state { return state{live: true} })
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.live, "Live input") })
|
||||
freqs := []string{"32 Hz", "63 Hz", "125 Hz", "250 Hz", "500 Hz", "1 kHz", "2 kHz", "4 kHz", "8 kHz", "16 kHz"}
|
||||
bands := make([]float32, len(freqs))
|
||||
t := float64(c.Now().UnixMilli()) / 1000
|
||||
for i := range bands {
|
||||
bands[i] = float32(0.2 + 0.6*math.Abs(math.Sin(float64(i)*0.9+t*(1+float64(i)*0.2))))
|
||||
}
|
||||
if s.live {
|
||||
// The demo is its own analyser; the component only draws.
|
||||
c.AnimationFrame()
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
mujica.AudioSpectrum(c, bands, mujica.AudioSpectrumOptions{BandLabel: func(i int) string { return freqs[i] }})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
// Media page snippets, each its compiled Example's body.
|
||||
const (
|
||||
imageCropperCode = `crop := mujica.ImageCrop{} // empty: starts fitted to the image
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.ImageCropper(c, examplePhoto, &crop, mujica.ImageCropperOptions{Aspect: 1}).Changed() {
|
||||
_ = crop.Rect // in pixels of the photo turned by crop.Rotation
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
imageAnnotatorCode = `photo := ui.NewBitmap(examplePhoto)
|
||||
var notes []mujica.Annotation
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.ImageAnnotator(c, photo, ¬es, mujica.ImageAnnotatorOptions{}).Changed() {
|
||||
data, _ := mujica.ExportAnnotations(notes)
|
||||
_ = data // save the JSON
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
imageThumbnailCode = `photo := ui.NewBitmap(examplePhoto)
|
||||
selected := false
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.ImageThumbnail(c, photo, mujica.ImageThumbnailOptions{Alt: "Grand hall", Aspect: 4.0 / 3, Selected: selected}).Changed() {
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
lightboxCode = `images := []mujica.ViewerImage{{Source: ui.NewBitmap(examplePhoto), Alt: "The grand hall"}}
|
||||
open, index := false, 0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.Button(c, "View photos", mujica.ButtonOptions{}).Clicked() {
|
||||
open = true
|
||||
}
|
||||
mujica.Lightbox(c, &open, &index, images)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
videoPlayerCode = `st := mujica.VideoState{Duration: 4 * time.Minute, Rate: 1}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.VideoPlayer(c, st, mujica.VideoPlayerOptions{
|
||||
OnPlay: func() { st.Playing = true }, // start your decoder
|
||||
OnPause: func() { st.Playing = false }, // pause it
|
||||
OnSeek: func(d time.Duration) { st.Position = d },
|
||||
OnRate: func(r float64) { st.Rate = r },
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
videoScrubberCode = `pos := 30 * time.Second
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.VideoScrubber(c, &pos, mujica.VideoScrubberOptions{
|
||||
Duration: 2 * time.Minute,
|
||||
Buffered: []mujica.MediaRange{{Start: 0, End: time.Minute}},
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
videoStripCode = `frames := []mujica.StripFrame{{At: 0}, {At: 10 * time.Second}, {At: 20 * time.Second}}
|
||||
current := 0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.VideoThumbnailStrip(c, ¤t, frames, mujica.VideoThumbnailStripOptions{}).Changed() {
|
||||
_ = frames[current].At // seek the player there
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
audioPlayerCode = `st := mujica.AudioState{Title: "Overture", Duration: 3 * time.Minute}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.AudioPlayer(c, st, mujica.AudioPlayerOptions{
|
||||
OnPlay: func() { st.Playing = true },
|
||||
OnPause: func() { st.Playing = false },
|
||||
OnSeek: func(d time.Duration) { st.Position = d },
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
audioWaveformCode = `samples := make([]float32, 48000) // from your decoder, -1 to 1
|
||||
pos, sel := time.Duration(0), mujica.MediaRange{}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.AudioWaveform(c, samples, mujica.AudioWaveformOptions{
|
||||
Duration: time.Second, Position: pos, Selection: &sel,
|
||||
OnSeek: func(d time.Duration) { pos = d },
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
audioSpectrumCode = `bands := []float32{0.2, 0.6, 0.9, 0.4, 0.1} // set by your analyser, then c.Invalidate()
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.AudioSpectrum(c, bands, mujica.AudioSpectrumOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
volumeCode = `volume, muted := 0.8, false
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.VolumeControl(c, &volume, &muted, mujica.VolumeControlOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
speedCode = `rate := 1.0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.PlaybackSpeedControl(c, &rate, mujica.PlaybackSpeedControlOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
mediaControlsCode = `st := mujica.MediaControlsState{}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.MediaControls(c, st, mujica.MediaControlsOptions{
|
||||
OnPrevious: func() {}, OnNext: func() {},
|
||||
OnPlay: func() { st.Playing = true }, OnPause: func() { st.Playing = false },
|
||||
OnLoop: func(m mujica.LoopMode) { st.Loop = m }, OnShuffle: func(on bool) { st.Shuffle = on },
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
playlistCode = `items := []mujica.PlaylistItem{{ID: "a", Title: "Overture"}, {ID: "b", Title: "Masquerade"}}
|
||||
current := "a"
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if r := mujica.Playlist(c, &items, mujica.PlaylistOptions{Current: current}); r.Played != "" {
|
||||
current = r.Played
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
subtitleCode = `cues := []mujica.SubtitleCue{{Start: 0, End: 2 * time.Second, Text: "The curtain rises."}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.SubtitleEditor(c, &cues, mujica.SubtitleEditorOptions{})
|
||||
if len(mujica.CheckSubtitles(cues)) > 0 {
|
||||
_ = cues // block saving until the issues are fixed
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
recorderCode = `st := mujica.RecorderState{}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.ScreenRecorderControls(c, st, mujica.ScreenRecorderControlsOptions{
|
||||
OnStart: func() { st.Status = mujica.RecorderRecording },
|
||||
OnPause: func() { st.Status = mujica.RecorderPaused },
|
||||
OnResume: func() { st.Status = mujica.RecorderRecording },
|
||||
OnStop: func() { st = mujica.RecorderState{} },
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
cameraCode = `frame := mujica.CameraFrame{Image: examplePhoto, Seq: 1} // a new Seq for each captured frame
|
||||
mirror := true
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.CameraPreview(c, frame, &mirror, mujica.CameraPreviewOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
micCode = `level := float32(0.4) // the input's peak, 0 to 1 of full scale
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.MicLevelMeter(c, level, mujica.MicLevelMeterOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
deviceCode = `devices := mujica.MediaDevices{
|
||||
Microphones: []mujica.MediaDevice{{ID: "mic-1", Name: "Built-in Microphone"}},
|
||||
Speakers: []mujica.MediaDevice{{ID: "out-1", Name: "Headphones"}},
|
||||
}
|
||||
sel := mujica.DeviceSelection{Microphone: "mic-1"}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.DeviceSelector(c, &sel, devices, mujica.DeviceSelectorOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
compareCode = `before, after := ui.NewBitmap(examplePhoto), ui.NewBitmap(examplePhoto)
|
||||
split := 0.5
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.ImageCompare(c, before, after, &split, mujica.ImageCompareOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`
|
||||
)
|
||||
@@ -0,0 +1,225 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func volumeDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
volume float64
|
||||
muted, disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "351", func() state { return state{volume: 0.7} })
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
if mujica.VolumeControl(c, &s.volume, &s.muted, mujica.VolumeControlOptions{Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("volume %.0f%%, muted %v", s.volume*100, s.muted))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func speedDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
rate float64
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "352", func() state { return state{rate: 1} })
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
if mujica.PlaybackSpeedControl(c, &s.rate, mujica.PlaybackSpeedControlOptions{Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("rate %v×", s.rate))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func mediaControlsDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
st mujica.MediaControlsState
|
||||
track int
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "353", func() state { return state{} })
|
||||
tracks := []string{"Ave Mujica", "Imprisoned XII", "KiLLKiSS", "Symbol I: △"}
|
||||
s.st.NoPrevious, s.st.NoNext = s.track == 0, s.track == len(tracks)-1
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Text(c, fmt.Sprintf("Now: %s (%d of %d)", tracks[s.track], s.track+1, len(tracks)))
|
||||
mujica.MediaControls(c, s.st, mujica.MediaControlsOptions{
|
||||
OnPrevious: func() { s.track--; log("previous") },
|
||||
OnNext: func() { s.track++; log("next") },
|
||||
OnPlay: func() { s.st.Playing = true; log("play") },
|
||||
OnPause: func() { s.st.Playing = false; log("pause") },
|
||||
OnLoop: func(m mujica.LoopMode) { s.st.Loop = m; log(fmt.Sprintf("loop mode %d", m)) },
|
||||
OnShuffle: func(on bool) { s.st.Shuffle = on; log(fmt.Sprintf("shuffle %v", on)) },
|
||||
Disabled: s.disabled,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func playlistDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
items []mujica.PlaylistItem
|
||||
current string
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "354", func() state {
|
||||
titles := []string{"Ave Mujica", "Imprisoned XII", "KiLLKiSS", "Symbol I: △", "Choir 'S' Choir", "Georgette Me, Georgette You"}
|
||||
items := make([]mujica.PlaylistItem, len(titles))
|
||||
for i, t := range titles {
|
||||
items[i] = mujica.PlaylistItem{ID: fmt.Sprint("t", i), Title: t, Subtitle: fmt.Sprintf("Act %d", i+1), Duration: time.Duration(200+i*17) * time.Second}
|
||||
}
|
||||
return state{items: items, current: "t1"}
|
||||
})
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.Button(c, "Add track", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
id := fmt.Sprint("t", len(s.items)+int(time.Now().UnixNano()%1000000))
|
||||
s.items = append(s.items, mujica.PlaylistItem{ID: id, Title: "Encore " + id, Duration: 3 * time.Minute})
|
||||
log("added " + id)
|
||||
}
|
||||
})
|
||||
})
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(520).Children(func() {
|
||||
r := mujica.Playlist(c, &s.items, mujica.PlaylistOptions{Current: s.current, Disabled: s.disabled})
|
||||
switch {
|
||||
case r.Played != "":
|
||||
s.current = r.Played
|
||||
log("play " + r.Played)
|
||||
case r.Removed != "":
|
||||
log("removed " + r.Removed)
|
||||
case r.Moved:
|
||||
log("reordered")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func subtitleDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
cues []mujica.SubtitleCue
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "355", func() state {
|
||||
return state{cues: []mujica.SubtitleCue{
|
||||
{Start: 0, End: 2500 * time.Millisecond, Text: "The curtain rises."},
|
||||
{Start: 2 * time.Second, End: 5 * time.Second, Text: "Welcome to the masquerade."},
|
||||
{Start: 6 * time.Second, End: 9 * time.Second, Text: "Ave Mujica."},
|
||||
}}
|
||||
})
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(720).Children(func() {
|
||||
if mujica.SubtitleEditor(c, &s.cues, mujica.SubtitleEditorOptions{Height: 260, Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("%d cues, %d issues", len(s.cues), len(mujica.CheckSubtitles(s.cues))))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func recorderDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
st mujica.RecorderState
|
||||
since time.Time
|
||||
disabled bool
|
||||
accumulated time.Duration
|
||||
}
|
||||
s := ui.Local(c.Root(), "356", func() state { return state{} })
|
||||
if s.st.Status == mujica.RecorderRecording {
|
||||
s.st.Elapsed = s.accumulated + c.Now().Sub(s.since)
|
||||
// The clock changes each second; ask for that frame only.
|
||||
c.After(time.Second - s.st.Elapsed%time.Second)
|
||||
}
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
mujica.ScreenRecorderControls(c, s.st, mujica.ScreenRecorderControlsOptions{
|
||||
OnStart: func() {
|
||||
s.st, s.since, s.accumulated = mujica.RecorderState{Status: mujica.RecorderRecording}, c.Now(), 0
|
||||
log("start")
|
||||
},
|
||||
OnPause: func() { s.st.Status, s.accumulated = mujica.RecorderPaused, s.st.Elapsed; log("pause") },
|
||||
OnResume: func() { s.st.Status, s.since = mujica.RecorderRecording, c.Now(); log("resume") },
|
||||
OnStop: func() { log("stop at " + mediaClockLabel(s.st.Elapsed)); s.st = mujica.RecorderState{} },
|
||||
Disabled: s.disabled,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func cameraDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
mirror, signal, disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "357", func() state { return state{mirror: true, signal: true} })
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).Children(func() {
|
||||
mediaCheck(c, log, &s.signal, "Signal")
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
})
|
||||
})
|
||||
frame := mujica.CameraFrame{}
|
||||
if s.signal {
|
||||
frame = mujica.CameraFrame{Image: mediaPhotos[2], Seq: 1}
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(480).Children(func() {
|
||||
if mujica.CameraPreview(c, frame, &s.mirror, mujica.CameraPreviewOptions{Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("mirror %v", s.mirror))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func micDemo(c *ui.Context, log func(string)) {
|
||||
type state struct{ live bool }
|
||||
s := ui.Local(c.Root(), "358", func() state { return state{live: true} })
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.live, "Live input") })
|
||||
level := float32(0.3)
|
||||
if s.live {
|
||||
t := float64(c.Now().UnixMilli()) / 1000
|
||||
level = float32(math.Min(1, 0.15+0.8*math.Abs(math.Sin(t*1.3)*math.Sin(t*0.37))))
|
||||
c.AnimationFrame()
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(420).Children(func() { mujica.MicLevelMeter(c, level, mujica.MicLevelMeterOptions{}) })
|
||||
})
|
||||
}
|
||||
|
||||
func deviceDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
sel mujica.DeviceSelection
|
||||
noCamera, disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "359", func() state { return state{sel: mujica.DeviceSelection{Microphone: "m1", Speaker: "s2"}} })
|
||||
devices := mujica.MediaDevices{
|
||||
Microphones: []mujica.MediaDevice{{ID: "m1", Name: "Built-in Microphone"}, {ID: "m2", Name: "Studio Condenser"}},
|
||||
Cameras: []mujica.MediaDevice{{ID: "c1", Name: "FaceTime HD Camera"}},
|
||||
Speakers: []mujica.MediaDevice{{ID: "s1", Name: "Built-in Speakers"}, {ID: "s2", Name: "Headphones"}},
|
||||
}
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).Children(func() {
|
||||
before := s.noCamera
|
||||
mediaCheck(c, log, &s.noCamera, "No cameras")
|
||||
if s.noCamera != before {
|
||||
s.sel.Camera = ""
|
||||
}
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
})
|
||||
})
|
||||
if s.noCamera {
|
||||
devices.Cameras = []mujica.MediaDevice{}
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(480).Children(func() {
|
||||
if mujica.DeviceSelector(c, &s.sel, devices, mujica.DeviceSelectorOptions{Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("microphone %q, camera %q, speaker %q", s.sel.Microphone, s.sel.Camera, s.sel.Speaker))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func imageCropperDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
crop mujica.ImageCrop
|
||||
aspect int
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "341", func() state { return state{} })
|
||||
aspects := []float64{0, 1, 16.0 / 9, 4.0 / 3}
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
|
||||
before := s.aspect
|
||||
mujica.SegmentedControl(c, &s.aspect, []mujica.Segment{{Label: "Free"}, {Label: "1:1"}, {Label: "16:9"}, {Label: "4:3"}}, mujica.SegmentedControlOptions{Label: "Aspect"})
|
||||
if s.aspect != before {
|
||||
s.crop.Rect = image.Rectangle{} // refit to the new ratio
|
||||
log(fmt.Sprintf("aspect = %v", aspects[s.aspect]))
|
||||
}
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
})
|
||||
})
|
||||
mediaSection(c, "Live", func() {
|
||||
if mujica.ImageCropper(c, mediaPhotos[0], &s.crop, mujica.ImageCropperOptions{Aspect: aspects[s.aspect], Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("crop %v rotated %d°", s.crop.Rect, s.crop.Rotation))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func imageAnnotatorDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
notes []mujica.Annotation
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "342", func() state {
|
||||
return state{notes: []mujica.Annotation{{Kind: mujica.AnnotateRect, From: image.Pt(150, 60), To: image.Pt(330, 230)}}}
|
||||
})
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.Button(c, "Export JSON", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
data, err := mujica.ExportAnnotations(s.notes)
|
||||
if err != nil {
|
||||
log("export failed: " + err.Error())
|
||||
} else {
|
||||
c.WriteClipboard(string(data))
|
||||
log(fmt.Sprintf("exported %d bytes to the clipboard", len(data)))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
mediaSection(c, "Live", func() {
|
||||
if mujica.ImageAnnotator(c, mediaBitmaps[1], &s.notes, mujica.ImageAnnotatorOptions{Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("annotations: %d", len(s.notes)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func imageThumbnailDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
selected int
|
||||
loading, failed, disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "343", func() state { return state{} })
|
||||
mediaSection(c, "Parameters", func() {
|
||||
ui.Row(c).Gap(16).Children(func() {
|
||||
mediaCheck(c, log, &s.loading, "Loading")
|
||||
mediaCheck(c, log, &s.failed, "Failed")
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
})
|
||||
})
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Row(c).Gap(12).Wrap().Children(func() {
|
||||
for i, b := range mediaBitmaps {
|
||||
var err error
|
||||
if s.failed && i == 2 {
|
||||
err = errors.New("the file could not be read")
|
||||
}
|
||||
o := mujica.ImageThumbnailOptions{Alt: fmt.Sprintf("Scene %d", i+1), Aspect: 4.0 / 3, Width: 120,
|
||||
Selected: s.selected == i, Loading: s.loading && i == 1, Err: err, Disabled: s.disabled}
|
||||
if mujica.ImageThumbnail(c, b, o).Changed() {
|
||||
s.selected = i
|
||||
log(fmt.Sprintf("selected scene %d", i+1))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func lightboxDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
open bool
|
||||
index int
|
||||
}
|
||||
s := ui.Local(c.Root(), "344", func() state { return state{} })
|
||||
images := []mujica.ViewerImage{
|
||||
{Source: mediaBitmaps[0], Alt: "The grand hall at dusk"},
|
||||
{Source: mediaBitmaps[1], Alt: "The ivory gallery"},
|
||||
{Source: mediaBitmaps[2], Alt: "The blue chapel"},
|
||||
}
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Row(c).Gap(12).Children(func() {
|
||||
for i, im := range images {
|
||||
if mujica.ImageThumbnail(c, im.Source, mujica.ImageThumbnailOptions{Alt: im.Alt, Aspect: 4.0 / 3}).Changed() {
|
||||
s.open, s.index = true, i
|
||||
log(fmt.Sprintf("open at %d", i+1))
|
||||
}
|
||||
}
|
||||
})
|
||||
was := s.open
|
||||
if mujica.Lightbox(c, &s.open, &s.index, images).Changed() {
|
||||
log(fmt.Sprintf("image %d", s.index+1))
|
||||
}
|
||||
if was && !s.open {
|
||||
log("closed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func videoStripDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
current int
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "347", func() state { return state{} })
|
||||
frames := make([]mujica.StripFrame, 240)
|
||||
for i := range frames {
|
||||
frames[i] = mujica.StripFrame{Image: mediaBitmaps[i%3], At: time.Duration(i) * 5 * time.Second}
|
||||
}
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
if mujica.VideoThumbnailStrip(c, &s.current, frames, mujica.VideoThumbnailStripOptions{Disabled: s.disabled}).Changed() {
|
||||
log("jump to " + mediaClockLabel(frames[s.current].At))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func compareDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
split float64
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "360", func() state { return state{split: 0.5} })
|
||||
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
|
||||
mediaSection(c, "Live", func() {
|
||||
ui.Column(c).MaxWidth(560).Children(func() {
|
||||
if mujica.ImageCompare(c, mediaBitmaps[0], mediaBitmaps[1], &s.split, mujica.ImageCompareOptions{Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("split %.0f%%", s.split*100))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user