package attachment import ( "errors" "io" "github.com/diamondburned/cchat-gtk/internal/gts" "github.com/diamondburned/cchat-gtk/internal/ui/primitives" "github.com/gotk3/gotk3/gtk" "github.com/gotk3/gotk3/pango" ) type MessageUploader struct { *gtk.Grid } // NewMessageUploader creates a new MessageUploader. It returns nil if there are // no files. func NewMessageUploader(files []File) *MessageUploader { m := &MessageUploader{} m.Grid, _ = gtk.GridNew() m.Grid.SetHExpand(true) m.Grid.SetColumnSpacing(4) m.Grid.SetRowSpacing(2) m.Grid.SetRowHomogeneous(true) primitives.AddClass(m.Grid, "upload-progress") for i, file := range files { var pbar = NewProgressBar(file) m.Grid.Attach(pbar.Name, 0, i, 1, 1) m.Grid.Attach(pbar.PBar, 1, i, 1, 1) } return m } type ProgressBar struct { PBar *gtk.ProgressBar Name *gtk.Label } func NewProgressBar(file File) *ProgressBar { bar, _ := gtk.ProgressBarNew() bar.SetVAlign(gtk.ALIGN_CENTER) bar.Show() name, _ := gtk.LabelNew(file.Name) name.SetMaxWidthChars(45) name.SetSingleLineMode(true) name.SetEllipsize(pango.ELLIPSIZE_MIDDLE) name.SetXAlign(1) name.Show() // Override the upload read callback. file.Prog.u = func(fraction float64) { gts.ExecAsync(func() { if fraction == -1 { // Pulse the bar around, as we don't know the total bytes. bar.Pulse() } else { // We know the progress, so use the percentage. bar.SetFraction(fraction) } }) } return &ProgressBar{bar, name} } // Progress wraps around a ReadCloser and implements a progress state for a // reader. type Progress struct { u func(float64) // read callback, arg is percentage r io.Reader s float64 // total, const n uint64 // cumulative } // NewProgress creates a new upload progress state. func NewProgress(r io.Reader, size int64) *Progress { return &Progress{ r: r, s: float64(size), n: 0, } } // frac returns the current percentage, or -1 is there is no total. func (p *Progress) frac() float64 { if p.s > 0 { return float64(p.n) / p.s } return -1 } func (p *Progress) Read(b []byte) (int, error) { // Read and cumulate total bytes read if there are no errors or if the error // is not fatal (EOF). n, err := p.r.Read(b) if err == nil || errors.Is(err, io.EOF) { p.n += uint64(n) } else { // If we have an unexpected error, then we should reset the bytes read // to 0. p.n = 0 } if p.u != nil { p.u(p.frac()) } return n, err }