117 lines
3.8 KiB
Go
117 lines
3.8 KiB
Go
package mujica
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"mujicaui/icons"
|
|
"mujicaui/internal"
|
|
"mujicaui/theme"
|
|
)
|
|
|
|
// StripFrame is one thumbnail of a VideoThumbnailStrip.
|
|
type StripFrame struct {
|
|
// Image is a bitmap or SVG; nil leaves it empty.
|
|
Image ui.ImageSource
|
|
At time.Duration
|
|
}
|
|
|
|
// VideoThumbnailStripOptions configure a VideoThumbnailStrip.
|
|
type VideoThumbnailStripOptions struct {
|
|
// FrameWidth is each cell's width; zero means 96.
|
|
FrameWidth float32
|
|
Disabled bool
|
|
}
|
|
|
|
const stripGap = 4
|
|
|
|
type stripKey struct{}
|
|
|
|
// VideoThumbnailStrip shows the frames fitting around *current.
|
|
func VideoThumbnailStrip(c *ui.Context, current *int, frames []StripFrame, opts VideoThumbnailStripOptions) ChoiceResult {
|
|
if len(frames) == 0 || *current < 0 || *current >= len(frames) {
|
|
panic("mujica: VideoThumbnailStrip current out of range")
|
|
}
|
|
if opts.FrameWidth < 0 {
|
|
panic("mujica: VideoThumbnailStrip FrameWidth must not be negative")
|
|
}
|
|
fw := opts.FrameWidth
|
|
if fw == 0 {
|
|
fw = 96
|
|
}
|
|
fh := fw * 9 / 16
|
|
k := Tokens(c)
|
|
old := *current
|
|
row := ui.Row(c).Gap(Density(c).Unit()).AlignItems(ui.Center).Disabled(opts.Disabled)
|
|
row.Children(func() {
|
|
prev := iconAction(c, icons.Must("chevron-left"), msg(c, "video_thumbnail_strip.previous")).Disabled(*current == 0)
|
|
view := ui.Row(c).Grow(1).MinWidth(fw).Height(fh + 4).Gap(stripGap).Clip().Focusable().
|
|
Role(ui.RoleGroup).Label(msg(c, "video_thumbnail_strip.label")).
|
|
Description(fmt.Sprintf(msg(c, "video_thumbnail_strip.position"), *current+1, len(frames), mediaClock(frames[*current].At)))
|
|
internal.Ring(view, k.Focus, theme.ControlRadius)
|
|
next := iconAction(c, icons.Must("chevron-right"), msg(c, "video_thumbnail_strip.next")).Disabled(*current == len(frames)-1)
|
|
live := !row.IsDisabled()
|
|
switch {
|
|
case live && (prev.Clicked() || view.Shortcut(0, ui.KeyLeft)):
|
|
*current = max(0, *current-1)
|
|
case live && (next.Clicked() || view.Shortcut(0, ui.KeyRight)):
|
|
*current = min(len(frames)-1, *current+1)
|
|
case live && view.Shortcut(0, ui.KeyHome):
|
|
*current = 0
|
|
case live && view.Shortcut(0, ui.KeyEnd):
|
|
*current = len(frames) - 1
|
|
}
|
|
// Only frames that fit are built, around *current.
|
|
w := view.Bounds().W
|
|
if w <= 0 {
|
|
// The count needs the width: lay out, then rebuild.
|
|
c.AnimationFrame()
|
|
}
|
|
n := max(1, int((w+stripGap)/(fw+stripGap)))
|
|
start := ui.Local(view, stripKey{}, func() int { return 0 })
|
|
*start = max(0, min(*start, *current, len(frames)-n))
|
|
if *current >= *start+n {
|
|
*start = *current - n + 1
|
|
}
|
|
view.Children(func() {
|
|
for i := *start; i < min(len(frames), *start+n); i++ {
|
|
if stripCell(c, k, frames[i], i, i == *current, fw, fh) && live {
|
|
*current = i
|
|
}
|
|
}
|
|
})
|
|
})
|
|
changed := *current != old
|
|
if changed {
|
|
slog.Debug("mujica: video_strip jump", "index", *current)
|
|
}
|
|
return ChoiceResult{Element: row, changed: changed}
|
|
}
|
|
|
|
// stripCell draws one frame and reports a click on it.
|
|
func stripCell(c *ui.Context, k theme.Tokens, f StripFrame, i int, on bool, fw, fh float32) bool {
|
|
cell := ui.Box(c).Key(i).Size(fw, fh).Shrink(0).Radius(theme.ControlRadius).Clip().Background(videoBlack).
|
|
Cursor(ui.CursorPointer).AlignSelf(ui.Center)
|
|
edge, width := k.Border, theme.BorderWidth
|
|
if on {
|
|
edge, width = k.AccentText, 2
|
|
}
|
|
cell.Border(width, edge)
|
|
cell.Children(func() {
|
|
if f.Image != nil {
|
|
ui.Image(c, f.Image).Fit(ui.Cover).Size(fw, fh)
|
|
}
|
|
ui.Text(c, mediaClock(f.At)).Absolute().Left(4).Bottom(3).FontSize(FontSize(c, theme.CaptionSize)).
|
|
TextColor(ui.Hex("#F2EBDD")).Background(ui.RGBA(0, 0, 0, 0.6)).FontFeatures("tnum")
|
|
if on {
|
|
ui.Box(c).Absolute().Top(3).Right(3).Size(10, 10).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
p.FillPath(internal.Diamond(internal.Snap(p, r.X+5), internal.Snap(p, r.Y+5), 10), k.AccentText)
|
|
})
|
|
}
|
|
})
|
|
return cell.Clicked()
|
|
}
|