Add media components 346-365; input.SlideBase, guarded Pad
An audio player with transport, scrubber and volume; an equalizer and a visualizer; a playlist; a cassette, cartridge and record that turn while they play and hold where they stop; piano keys, a step sequencer and a tracker you can type into; a waveform, a 16:9 video frame, an image compare, a gallery, a filmstrip, a record button and an FM tuner. input exports SlideBase, the undrawn rail its sliders share; the gallery treats exported Bases as building blocks, not components. pixel's micro font gains '#' for notes like C#4. pixel.Pad and the slide rail read input only while their base is enabled, so Inert or a fresh Disabled stops a drag or a key already sent. AGENTS records why a drawn container keeps the DISABLED description: MyGo paints a disabled subtree at half opacity.
This commit is contained in:
39 files changed
+3066
-10
No files matched your search
@@ -0,0 +1,227 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/media"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
var mediaEntries = []Entry{
|
||||
{346, "AudioPlayer", "音乐播放器", "media", "A whole deck in one box. Stop rewinds to the top.", demoAudioPlayer},
|
||||
{347, "Transport", "播放控制", "media", "Back, play or pause, stop, skip: the four buttons every deck agrees on.", demoTransport},
|
||||
{348, "Scrubber", "进度拖动", "media", "Drag to seek, or nudge with arrows. Time at both ends.", demoScrubber},
|
||||
{349, "Volume", "音量", "media", "Eight bars; click one or use arrows. The speaker mutes.", demoVolume},
|
||||
{350, "Equalizer", "均衡器", "media", "Five faders, ±12 dB. Bass boost at your own risk.", demoEqualizer},
|
||||
{351, "Visualizer", "频谱", "media", "Bars bounce out from the middle, hottest at the tips.", demoVisualizer},
|
||||
{352, "Playlist", "播放列表", "media", "♪ marks the track that plays. Enter plays the one you pick.", demoPlaylist},
|
||||
{353, "Cassette", "磁带", "media", "Reels turn while it plays; watch the tape cross over.", demoCassette},
|
||||
{354, "Cartridge", "卡带", "media", "A cart with a sticker. Blow on the contacts before use.", demoCartridge},
|
||||
{355, "Vinyl", "黑胶", "media", "Turns in eight frames; the arm swings onto the groove.", demoVinyl},
|
||||
}
|
||||
|
||||
type mediaDemo struct {
|
||||
player media.Player
|
||||
track, pick int
|
||||
deck string
|
||||
seek time.Duration
|
||||
level int
|
||||
muted bool
|
||||
gains []float64
|
||||
tape, spin, video bool
|
||||
tapeAt, videoAt time.Duration
|
||||
notes []string
|
||||
beat bool
|
||||
pattern [][16]bool
|
||||
rows []media.Row
|
||||
row, chosen int
|
||||
split, mhz float64
|
||||
rec bool
|
||||
ran, taped, filmed time.Time
|
||||
}
|
||||
|
||||
var tracks = []media.Track{
|
||||
{Title: "Overworld Theme", Artist: "The Square Waves", Length: 2*time.Minute + 47*time.Second},
|
||||
{Title: "Boss Battle", Artist: "Chip Wizards", Length: 3*time.Minute + 12*time.Second},
|
||||
{Title: "Save Point Lullaby", Artist: "Triangle Wave", Length: 1*time.Minute + 58*time.Second},
|
||||
{Title: "Credits Roll", Artist: "Pulse & Noise", Length: 4*time.Minute + 5*time.Second},
|
||||
}
|
||||
|
||||
func mediaState(c *ui.Context) *mediaDemo {
|
||||
return ui.Local(c.Root(), demoKey("media"), func() mediaDemo {
|
||||
return mediaDemo{
|
||||
player: media.Player{Level: 6}, pick: 1, deck: "STOPPED", seek: 72 * time.Second, level: 5,
|
||||
gains: []float64{6, 2, 0, 3, 5},
|
||||
pattern: [][16]bool{beat("x...x...x...x..."), beat("....x.......x..."), beat("x.x.x.x.x.x.x.xx"), beat("............x...")},
|
||||
rows: melody(), split: 0.5, mhz: 98.3,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// beat reads a pattern: x hits, . rests.
|
||||
func beat(s string) [16]bool {
|
||||
var b [16]bool
|
||||
for i, r := range s {
|
||||
b[i] = r == 'x'
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// spin moves *at while playing, as an audio engine would.
|
||||
func spin(c *ui.Context, last *time.Time, playing bool, at *time.Duration, length time.Duration) {
|
||||
k := pixel.K(c)
|
||||
if playing && !last.IsZero() {
|
||||
*at = (*at + k.Now().Sub(*last)) % length
|
||||
}
|
||||
*last = time.Time{}
|
||||
if playing {
|
||||
*last = k.Now()
|
||||
k.Tick(c, 4)
|
||||
}
|
||||
}
|
||||
|
||||
func demoAudioPlayer(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
t := tracks[s.track]
|
||||
spin(c, &s.ran, s.player.Playing, &s.player.At, t.Length)
|
||||
ui.Box(c).Width(k.D(240)).Children(func() {
|
||||
res := media.AudioPlayer(c, "Now playing", t, &s.player, media.AudioPlayerOptions{})
|
||||
switch {
|
||||
case res.Previous:
|
||||
s.track = (s.track + len(tracks) - 1) % len(tracks)
|
||||
case res.Next:
|
||||
s.track = (s.track + 1) % len(tracks)
|
||||
}
|
||||
if res.Previous || res.Next {
|
||||
s.player.At = 0
|
||||
log("♪ " + tracks[s.track].Title)
|
||||
}
|
||||
if res.Changed {
|
||||
log(fmt.Sprintf("playing %v at %s, volume %d, muted %v", s.player.Playing, s.player.At.Truncate(time.Second), s.player.Level, s.player.Muted))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoTransport(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
playing := s.deck == "PLAYING"
|
||||
res := media.Transport(c, "Deck", &playing, media.TransportOptions{})
|
||||
switch {
|
||||
case res.Stopped:
|
||||
s.deck = "STOPPED"
|
||||
case res.Changed && playing:
|
||||
s.deck = "PLAYING"
|
||||
case res.Changed:
|
||||
s.deck = "PAUSED"
|
||||
case res.Previous:
|
||||
log("PREVIOUS")
|
||||
case res.Next:
|
||||
log("NEXT")
|
||||
}
|
||||
if res.Changed || res.Stopped {
|
||||
log(s.deck)
|
||||
}
|
||||
pixel.Text(c, s.deck, pixel.TextOptions{Slot: pixel.Faint})
|
||||
})
|
||||
}
|
||||
|
||||
func demoScrubber(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
if media.Scrubber(c, "Seek", &s.seek, tracks[1].Length, media.ScrubberOptions{Width: 120}).Changed {
|
||||
log("seek " + s.seek.String())
|
||||
}
|
||||
}
|
||||
|
||||
func demoVolume(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
if media.Volume(c, "Volume", &s.level, &s.muted, media.VolumeOptions{}).Changed {
|
||||
log(fmt.Sprintf("volume %d of 8, muted %v", s.level, s.muted))
|
||||
}
|
||||
}
|
||||
|
||||
func demoEqualizer(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
bands := []string{"60", "250", "1K", "4K", "12K"}
|
||||
if b := media.Equalizer(c, "Equalizer", bands, s.gains, media.EqualizerOptions{}).Band; b >= 0 {
|
||||
log(fmt.Sprintf("%s Hz %+.1f dB", bands[b], s.gains[b]))
|
||||
}
|
||||
}
|
||||
|
||||
func demoVisualizer(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
// A made-up song: each bar sways at its own pace.
|
||||
t := 0.0
|
||||
if !k.Reduce() {
|
||||
t = float64(k.Tick(c, 12)) / 12
|
||||
}
|
||||
levels := make([]float64, 24)
|
||||
for i := range levels {
|
||||
f := float64(i)
|
||||
levels[i] = (0.55 + 0.45*math.Sin(t*(2+f/5)+f)) * (1 - f/32)
|
||||
}
|
||||
media.Visualizer(c, "Visualizer", levels, media.VisualizerOptions{Height: 40})
|
||||
}
|
||||
|
||||
func demoPlaylist(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
res := media.Playlist(c, "Playlist", tracks, s.track, &s.pick, media.PlaylistOptions{Height: 80})
|
||||
if res.Submitted {
|
||||
s.track, s.player.At = s.pick, 0
|
||||
log("♪ " + tracks[s.track].Title)
|
||||
} else if res.Changed {
|
||||
log("picked " + tracks[s.pick].Title)
|
||||
}
|
||||
}
|
||||
|
||||
func demoCassette(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
const side = 45 * time.Second
|
||||
spin(c, &s.taped, s.tape, &s.tapeAt, side)
|
||||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||||
media.Cassette(c, "Mixtape, side A", media.CassetteOptions{Title: "Mixtape 86", Playing: s.tape, Progress: float64(s.tapeAt) / float64(side)})
|
||||
if input.Toggle(c, "PLAY", &s.tape, input.ToggleOptions{}).Changed {
|
||||
log(fmt.Sprintf("tape playing %v", s.tape))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoCartridge(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
ui.Row(c).Gap(k.D(8)).Children(func() {
|
||||
media.Cartridge(c, "Pixel Quest cartridge", media.CartridgeOptions{Title: "Pixel Quest", Art: scene(pixel.Warn, pixel.Info, pixel.Good, 8, 4)})
|
||||
media.Cartridge(c, "Night Run cartridge", media.CartridgeOptions{Title: "Night Run 2", Art: scene(pixel.Lit, pixel.Magic, pixel.Shade, 26, 3)})
|
||||
})
|
||||
}
|
||||
|
||||
func demoVinyl(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||||
media.Vinyl(c, "Record", media.VinylOptions{Playing: s.spin})
|
||||
if input.Toggle(c, "PLAY", &s.spin, input.ToggleOptions{}).Changed {
|
||||
log(fmt.Sprintf("record playing %v", s.spin))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// scene is hills under a sky, sun at x, y.
|
||||
func scene(sun, sky, hill pixel.Slot, x, y int) *pixel.Sprite {
|
||||
return pixel.SpriteOf(32, 18, func(i, j int) (pixel.Slot, bool) {
|
||||
switch {
|
||||
case (i-x)*(i-x)+(j-y)*(j-y) <= 5:
|
||||
return sun, true
|
||||
case float64(j) > 12+2*math.Sin(float64(i)/4):
|
||||
return hill, true
|
||||
}
|
||||
return sky, true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/media"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
var studioEntries = []Entry{
|
||||
{356, "PianoKeys", "钢琴键", "media", "Click, or type A–K. W E T Y U play the black keys.", demoPianoKeys},
|
||||
{357, "StepSequencer", "步进音序器", "media", "Four tracks, sixteen steps. Press play and nod along.", demoStepSequencer},
|
||||
{358, "Tracker", "音轨编辑", "media", "Type A–K to write a note, Delete for a rest. Beats every four rows.", demoTracker},
|
||||
{359, "Waveform", "波形", "media", "Every sample a line, up or down from zero.", demoWaveform},
|
||||
{360, "VideoFrame", "视频框", "media", "A 16:9 screen with play and a scrubber. The slime does the rest.", demoVideoFrame},
|
||||
{361, "ImageCompare", "对比滑块", "media", "Drag the grip: sketch on the left, paint on the right.", demoImageCompare},
|
||||
{362, "Gallery", "画廊", "media", "Arrows walk the thumbnails; the big picture follows.", demoGallery},
|
||||
{363, "Filmstrip", "胶片条", "media", "A sprite's frames on film, sprocket holes and all.", demoFilmstrip},
|
||||
{364, "RecordButton", "录制按钮", "media", "The dot blinks while it records, and the time runs.", demoRecordButton},
|
||||
{365, "Tuner", "调频器", "media", "Drag the needle across FM. A few stations are on the air.", demoTuner},
|
||||
}
|
||||
|
||||
func demoPianoKeys(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
for _, n := range media.PianoKeys(c, "Piano", media.PianoKeysOptions{}).Struck {
|
||||
s.notes = append(s.notes, n)
|
||||
log("♪ " + n)
|
||||
}
|
||||
played := s.notes[max(0, len(s.notes)-8):]
|
||||
pixel.Text(c, "♪ "+strings.Join(played, " "), pixel.TextOptions{Slot: pixel.Faint})
|
||||
})
|
||||
}
|
||||
|
||||
func demoStepSequencer(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
tracks := []string{"KICK", "SNARE", "HAT", "CLAP"}
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
if media.StepSequencer(c, "Beat", tracks, s.pattern, media.StepSequencerOptions{Playing: s.beat}).Changed {
|
||||
log("pattern changed")
|
||||
}
|
||||
if input.Toggle(c, "PLAY", &s.beat, input.ToggleOptions{}).Changed {
|
||||
log(fmt.Sprintf("beat playing %v", s.beat))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// melody is a pattern with a tune in it.
|
||||
func melody() []media.Row {
|
||||
rows := make([]media.Row, 16)
|
||||
for i, n := range map[int]string{0: "C-4", 2: "E-4", 4: "G-4", 6: "E-4", 8: "F-4", 10: "A-4", 12: "G-4", 14: "C-5"} {
|
||||
rows[i] = media.Row{Note: n, Instrument: 1 + i/8, Volume: 64 - i*2}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func demoTracker(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
if media.Tracker(c, "Pattern", s.rows, &s.row, media.TrackerOptions{}).Changed {
|
||||
log(fmt.Sprintf("pattern edited at row %02d", s.row))
|
||||
}
|
||||
}
|
||||
|
||||
func demoWaveform(c *ui.Context, log func(string)) {
|
||||
// A plucked string: a fast wiggle fading out.
|
||||
samples := make([]float64, 72)
|
||||
for i := range samples {
|
||||
f := float64(i)
|
||||
samples[i] = math.Sin(f*0.7) * math.Exp(-f/24)
|
||||
}
|
||||
media.Waveform(c, "Pluck", samples, media.WaveformOptions{Height: 40})
|
||||
}
|
||||
|
||||
func demoVideoFrame(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
const length = 20 * time.Second
|
||||
spin(c, &s.filmed, s.video, &s.videoAt, length)
|
||||
at := s.videoAt
|
||||
screen := func() {
|
||||
ui.Box(c).Size(k.D(160), k.D(90)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
g.Rect(0, 0, g.W, g.H, pixel.Info)
|
||||
g.Rect(0, 72, g.W, 18, pixel.Good)
|
||||
g.Dither(0, 72, g.W, 18, pixel.Good, pixel.Shade, 4)
|
||||
// The slime hops right with the film's own clock.
|
||||
hop := int(at / (time.Second / 4))
|
||||
x := int(float64(at) / float64(length) * 128)
|
||||
g.Sprite(x, 48-hop%2*4, slime, hop%2, pixel.Ink, 2)
|
||||
})
|
||||
}
|
||||
res := media.VideoFrame(c, "Slime documentary", &s.video, &s.videoAt, length, media.VideoFrameOptions{}, screen)
|
||||
if res.Changed {
|
||||
log(fmt.Sprintf("video playing %v at %s", s.video, s.videoAt.Truncate(time.Second)))
|
||||
}
|
||||
}
|
||||
|
||||
func demoImageCompare(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
after := scene(pixel.Warn, pixel.Info, pixel.Good, 24, 5)
|
||||
// The sketch is the painting in one faint ink.
|
||||
before := pixel.SpriteOf(after.W, after.H, func(x, y int) (pixel.Slot, bool) {
|
||||
if sl, _ := after.At(0, x, y, pixel.Ink); sl == pixel.Info {
|
||||
return pixel.Lit, true
|
||||
}
|
||||
return pixel.Faint, true
|
||||
})
|
||||
if media.ImageCompare(c, "Sketch and painting", before, after, &s.split, media.ImageCompareOptions{Scale: 4}).Changed {
|
||||
log(fmt.Sprintf("split at %.0f%%", s.split*100))
|
||||
}
|
||||
}
|
||||
|
||||
var views = []struct {
|
||||
name string
|
||||
pic *pixel.Sprite
|
||||
}{
|
||||
{"Morning", scene(pixel.Warn, pixel.AccentLit, pixel.Good, 4, 10)},
|
||||
{"Noon", scene(pixel.Warn, pixel.Info, pixel.Good, 16, 3)},
|
||||
{"Dusk", scene(pixel.Bad, pixel.Accent, pixel.Magic, 27, 11)},
|
||||
{"Night", scene(pixel.Lit, pixel.Well, pixel.Shade, 22, 4)},
|
||||
}
|
||||
|
||||
func demoGallery(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
pics, names := make([]*pixel.Sprite, len(views)), make([]string, len(views))
|
||||
for i, v := range views {
|
||||
pics[i], names[i] = v.pic, v.name
|
||||
}
|
||||
if media.Gallery(c, "Views", pics, names, &s.chosen, media.GalleryOptions{}).Changed {
|
||||
log("showing " + names[s.chosen])
|
||||
}
|
||||
}
|
||||
|
||||
var coin = pixel.MustSprite(`
|
||||
..eeee..
|
||||
.eyyyye.
|
||||
eyylyyye
|
||||
eyylyyye
|
||||
eyylyyye
|
||||
eyylyyye
|
||||
.eyyyye.
|
||||
..eeee..
|
||||
|
||||
...ee...
|
||||
..eyye..
|
||||
..eyle..
|
||||
..eyle..
|
||||
..eyle..
|
||||
..eyle..
|
||||
..eyye..
|
||||
...ee...
|
||||
|
||||
...ee...
|
||||
...ee...
|
||||
...ee...
|
||||
...ee...
|
||||
...ee...
|
||||
...ee...
|
||||
...ee...
|
||||
...ee...
|
||||
|
||||
...ee...
|
||||
..eyye..
|
||||
..elye..
|
||||
..elye..
|
||||
..elye..
|
||||
..elye..
|
||||
..eyye..
|
||||
...ee...
|
||||
`)
|
||||
|
||||
func demoFilmstrip(c *ui.Context, log func(string)) {
|
||||
media.Filmstrip(c, "Coin spin", coin, media.FilmstripOptions{Scale: 3})
|
||||
}
|
||||
|
||||
func demoRecordButton(c *ui.Context, log func(string)) {
|
||||
s := mediaState(c)
|
||||
if media.RecordButton(c, "Record", &s.rec, media.RecordButtonOptions{}).Changed {
|
||||
log(fmt.Sprintf("recording %v", s.rec))
|
||||
}
|
||||
}
|
||||
|
||||
var stations = map[float64]string{88.1: "PIXEL FM", 95.5: "CHIPTUNE RADIO", 101.1: "8-BIT HITS", 104.3: "BOSS BATTLE FM"}
|
||||
|
||||
func demoTuner(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := mediaState(c)
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
if media.Tuner(c, "Radio", &s.mhz, media.TunerOptions{}).Changed {
|
||||
log(fmt.Sprintf("%.1f MHz", s.mhz))
|
||||
}
|
||||
on := "· · · static · · ·"
|
||||
if name, ok := stations[s.mhz]; ok {
|
||||
on = "♪ " + name
|
||||
}
|
||||
pixel.Text(c, on, pixel.TextOptions{Slot: pixel.Faint})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
var letters = map[string]ui.Key{"A": ui.KeyA, "Right": ui.KeyRight, "Up": ui.KeyUp, "Enter": ui.KeyEnter}
|
||||
|
||||
func TestMediaDemosLogTheirActions(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
path string
|
||||
steps []string
|
||||
}{
|
||||
{"/c/346-audioplayer", []string{"PLAY", "NEXT"}}, {"/c/347-transport", []string{"PLAY", "STOP", "PREVIOUS"}},
|
||||
{"/c/348-scrubber", []string{"Seek"}}, {"/c/349-volume", []string{"MUTE"}}, {"/c/350-equalizer", []string{"1K", "key:Up"}},
|
||||
{"/c/352-playlist", []string{"Save Point Lullaby, Triangle Wave", "key:Enter"}}, {"/c/353-cassette", []string{"PLAY"}},
|
||||
{"/c/355-vinyl", []string{"PLAY"}}, {"/c/356-pianokeys", []string{"Piano", "key:A"}}, {"/c/357-stepsequencer", []string{"Beat", "PLAY"}},
|
||||
{"/c/358-tracker", []string{"Pattern", "key:A"}}, {"/c/360-videoframe", []string{"PLAY"}},
|
||||
{"/c/361-imagecompare", []string{"Sketch and painting", "key:Right"}}, {"/c/362-gallery", []string{"Views"}},
|
||||
{"/c/364-recordbutton", []string{"Record, 0:00"}}, {"/c/365-tuner", []string{"Radio"}},
|
||||
} {
|
||||
a, tt := open(tc.path)
|
||||
slug := tc.path[3:]
|
||||
for _, step := range tc.steps {
|
||||
before := len(a.logs[slug])
|
||||
if k, ok := strings.CutPrefix(step, "key:"); ok {
|
||||
tt.Key(0, letters[k])
|
||||
} else {
|
||||
click(t, tt, step)
|
||||
}
|
||||
// Clicking a field only to focus it logs nothing.
|
||||
if len(a.logs[slug]) <= before && step != "1K" && step != "Pattern" && step != "Sketch and painting" {
|
||||
t.Errorf("%s: %s logged nothing: %q", slug, step, a.logs[slug])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -56,7 +56,7 @@ var Categories = []Category{
|
||||
{"tabletop", "Tabletop", "桌游", icon.Crown},
|
||||
}
|
||||
|
||||
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries)
|
||||
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries)
|
||||
|
||||
func Entries() []Entry { return entries }
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestRegistry(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Every exported component function appears in a demo.
|
||||
// Every exported component has a demo; Bases are not components.
|
||||
func TestEveryComponentHasADemo(t *testing.T) {
|
||||
var demos strings.Builder
|
||||
for _, e := range Entries() {
|
||||
@@ -58,7 +58,7 @@ func TestEveryComponentHasADemo(t *testing.T) {
|
||||
}
|
||||
for _, d := range file.Decls {
|
||||
fn, ok := d.(*ast.FuncDecl)
|
||||
if !ok || fn.Recv != nil || !fn.Name.IsExported() || fn.Type.Params.NumFields() == 0 {
|
||||
if !ok || fn.Recv != nil || !fn.Name.IsExported() || strings.HasSuffix(fn.Name.Name, "Base") || fn.Type.Params.NumFields() == 0 {
|
||||
continue
|
||||
}
|
||||
if p, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr); ok {
|
||||
|
||||
Reference in new issue
Block a user