Add GTK application implementation
This commit is contained in:
parent
d1bb505e0a
commit
b7c0307f6b
29 changed files with 2564 additions and 266 deletions
563
internal/gui/librarypanel.go
Normal file
563
internal/gui/librarypanel.go
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/diamondburned/gotk4-adwaita/pkg/adw"
|
||||
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"suruatoel.xyz/mcg/data"
|
||||
"suruatoel.xyz/mcg/internal/client"
|
||||
"suruatoel.xyz/mcg/internal/common"
|
||||
)
|
||||
|
||||
type libraryPanel struct {
|
||||
*adw.Bin
|
||||
toolbar *gtk.Box
|
||||
selectButton *gtk.ToggleButton
|
||||
actionbarRevealer *gtk.Revealer
|
||||
toolbarPopover *gtk.Popover
|
||||
searchBar *gtk.SearchBar
|
||||
searchEntry *gtk.SearchEntry
|
||||
sortButtons map[string]*gtk.CheckButton
|
||||
sortDescButton *gtk.CheckButton
|
||||
gridScale *gtk.Scale
|
||||
stack *gtk.Stack
|
||||
progressBox *gtk.Box
|
||||
progressBar *gtk.ProgressBar
|
||||
scroll *gtk.ScrolledWindow
|
||||
libraryGrid *gtk.GridView
|
||||
gridFactory *pictureItemFactory[string]
|
||||
libraryModel *gtk.StringList
|
||||
selectionMulti *gtk.MultiSelection
|
||||
selectionSingle *gtk.SingleSelection
|
||||
|
||||
headerbarStandalone *albumHeaderbar
|
||||
libraryStack *gtk.Stack
|
||||
panelNormal *gtk.Box
|
||||
panelStandalone *gtk.Box
|
||||
standaloneStack *gtk.Stack
|
||||
standaloneSpinner *gtk.Spinner
|
||||
standaloneScroll *gtk.ScrolledWindow
|
||||
standaloneImage *gtk.Image
|
||||
standalonePlayButton *gtk.Button
|
||||
standaloneQueueButton *gtk.Button
|
||||
selectionQueueButton *gtk.Button
|
||||
selectionCancelButton *gtk.Button
|
||||
|
||||
coverLoader *CoverLoader
|
||||
albums map[string]*common.Album
|
||||
pixbufs map[string]*gdkpixbuf.Pixbuf
|
||||
sortField string
|
||||
sortDesc bool
|
||||
itemSize int
|
||||
itemSizeCallback ItemSizeCallback
|
||||
|
||||
libraryLock sync.Mutex
|
||||
libraryWait sync.WaitGroup
|
||||
|
||||
standaloneAlbum *common.Album
|
||||
standalonePixbuf *gdkpixbuf.Pixbuf
|
||||
standaloneCallback func(bool)
|
||||
}
|
||||
|
||||
type ItemSizeCallback func(itemSize int)
|
||||
|
||||
func newLibraryPanel(config Configuration) *libraryPanel {
|
||||
panel := libraryPanel{
|
||||
albums: make(map[string]*common.Album),
|
||||
pixbufs: make(map[string]*gdkpixbuf.Pixbuf),
|
||||
sortField: "year",
|
||||
sortDesc: false,
|
||||
libraryLock: sync.Mutex{},
|
||||
libraryWait: sync.WaitGroup{},
|
||||
}
|
||||
panel.loadWidgets(config)
|
||||
panel.loadDefaultValues(config)
|
||||
panel.connectSignals()
|
||||
|
||||
return &panel
|
||||
}
|
||||
|
||||
func (p *libraryPanel) loadWidgets(config Configuration) {
|
||||
builder := gtk.NewBuilderFromString(data.LibraryPanelXML)
|
||||
|
||||
p.toolbar = builder.GetObject("toolbar").Cast().(*gtk.Box)
|
||||
p.selectButton = builder.GetObject("select_button").Cast().(*gtk.ToggleButton)
|
||||
p.actionbarRevealer = builder.GetObject("actionbar_revealer").Cast().(*gtk.Revealer)
|
||||
p.toolbarPopover = builder.GetObject("toolbar_popover").Cast().(*gtk.Popover)
|
||||
|
||||
p.searchBar = builder.GetObject("filter_bar").Cast().(*gtk.SearchBar)
|
||||
p.searchEntry = builder.GetObject("filter_entry").Cast().(*gtk.SearchEntry)
|
||||
p.sortButtons = make(map[string]*gtk.CheckButton)
|
||||
p.sortButtons["artist"] = builder.GetObject("sort_artist").Cast().(*gtk.CheckButton)
|
||||
p.sortButtons["title"] = builder.GetObject("sort_title").Cast().(*gtk.CheckButton)
|
||||
p.sortButtons["year"] = builder.GetObject("sort_year").Cast().(*gtk.CheckButton)
|
||||
p.sortButtons["modified"] = builder.GetObject("sort_modified").Cast().(*gtk.CheckButton)
|
||||
p.sortDescButton = builder.GetObject("toolbar_sort_order_button").Cast().(*gtk.CheckButton)
|
||||
p.gridScale = builder.GetObject("grid_scale").Cast().(*gtk.Scale)
|
||||
|
||||
p.Bin = builder.GetObject("McgLibraryPanel").Cast().(*adw.Bin)
|
||||
p.stack = builder.GetObject("stack").Cast().(*gtk.Stack)
|
||||
p.progressBox = builder.GetObject("progress_box").Cast().(*gtk.Box)
|
||||
p.progressBar = builder.GetObject("progress_bar").Cast().(*gtk.ProgressBar)
|
||||
p.scroll = builder.GetObject("scroll").Cast().(*gtk.ScrolledWindow)
|
||||
p.libraryGrid = builder.GetObject("library_grid").Cast().(*gtk.GridView)
|
||||
p.libraryModel = gtk.NewStringList(nil)
|
||||
p.selectionMulti = gtk.NewMultiSelection(p.libraryModel)
|
||||
p.selectionSingle = gtk.NewSingleSelection(p.libraryModel)
|
||||
p.libraryGrid.SetModel(p.selectionSingle)
|
||||
|
||||
p.gridFactory = newPictureItemFactory[string](p.itemToModelID, p.modelIDToValue)
|
||||
p.libraryGrid.SetFactory(p.gridFactory.ListItemFactory())
|
||||
|
||||
// Headerbar
|
||||
p.headerbarStandalone = newAlbumHeaderbar()
|
||||
p.libraryStack = builder.GetObject("library_stack").Cast().(*gtk.Stack)
|
||||
p.panelNormal = builder.GetObject("panel_normal").Cast().(*gtk.Box)
|
||||
p.panelStandalone = builder.GetObject("panel_standalone").Cast().(*gtk.Box)
|
||||
|
||||
p.standaloneStack = builder.GetObject("standalone_stack").Cast().(*gtk.Stack)
|
||||
p.standaloneSpinner = builder.GetObject("standalone_spinner").Cast().(*gtk.Spinner)
|
||||
p.standaloneScroll = builder.GetObject("standalone_scroll").Cast().(*gtk.ScrolledWindow)
|
||||
p.standaloneImage = builder.GetObject("standalone_image").Cast().(*gtk.Image)
|
||||
p.standalonePlayButton = builder.GetObject("standalone_play_button").Cast().(*gtk.Button)
|
||||
p.standaloneQueueButton = builder.GetObject("standalone_queue_button").Cast().(*gtk.Button)
|
||||
|
||||
// Selection
|
||||
p.selectionQueueButton = builder.GetObject("selection_queue_button").Cast().(*gtk.Button)
|
||||
p.selectionCancelButton = builder.GetObject("selection_cancel_button").Cast().(*gtk.Button)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) itemToModelID(item *glib.Object) string {
|
||||
return item.Cast().(*gtk.StringObject).String()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) modelIDToValue(itemID string) (tooltip string, pixbuf *gdkpixbuf.Pixbuf) {
|
||||
if album, ok := p.albums[itemID]; ok {
|
||||
tooltip = album.Tooltip()
|
||||
}
|
||||
pixbuf = p.pixbufs[itemID]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *libraryPanel) loadDefaultValues(config Configuration) {
|
||||
p.itemSize = config.ItemSize
|
||||
p.gridScale.SetValue(float64(config.ItemSize))
|
||||
|
||||
_, found := p.sortButtons[config.SortField]
|
||||
if found {
|
||||
p.sortField = config.SortField
|
||||
}
|
||||
p.sortDesc = config.SortDesc
|
||||
|
||||
p.sortButtons[p.sortField].SetActive(true)
|
||||
p.sortDescButton.SetActive(p.sortDesc)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) connectSignals() {
|
||||
p.selectButton.ConnectToggled(p.onSelectToggled)
|
||||
p.searchEntry.ConnectSearchChanged(p.onFilterEntryChanged)
|
||||
for field, button := range p.sortButtons {
|
||||
button.ConnectToggled(func() {
|
||||
p.onSortToggled(field, button)
|
||||
})
|
||||
}
|
||||
p.sortDescButton.ConnectToggled(p.onSortDescToggled)
|
||||
|
||||
p.libraryGrid.ConnectActivate(p.onGridClicked)
|
||||
|
||||
p.headerbarStandalone.ConnectClose(p.onHeaderbarStandaloneClose)
|
||||
p.standalonePlayButton.ConnectClicked(p.onStandalonePlayClicked)
|
||||
p.standaloneQueueButton.ConnectClicked(p.onStandaloneQueueClicked)
|
||||
|
||||
p.selectionQueueButton.ConnectClicked(p.onSelectionQueue)
|
||||
p.selectionCancelButton.ConnectClicked(p.onSelectionCancel)
|
||||
|
||||
buttonController := gtk.NewGestureClick()
|
||||
buttonController.ConnectUnpairedRelease(p.onGridScaleReleased)
|
||||
p.gridScale.AddController(buttonController)
|
||||
p.gridScale.ConnectValueChanged(p.onGridScaleChanged)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) ConnectItemSizeChanged(callback ItemSizeCallback) {
|
||||
p.itemSizeCallback = callback
|
||||
}
|
||||
|
||||
func (p *libraryPanel) ConnectStandalone(callback func(bool)) {
|
||||
p.standaloneCallback = callback
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onSelectToggled() {
|
||||
if p.selectButton.Active() {
|
||||
p.actionbarRevealer.SetRevealChild(true)
|
||||
p.libraryGrid.SetModel(p.selectionMulti)
|
||||
p.libraryGrid.SetSingleClickActivate(false)
|
||||
p.libraryGrid.StyleContext().AddClass("selection")
|
||||
} else {
|
||||
p.actionbarRevealer.SetRevealChild(false)
|
||||
p.libraryGrid.SetModel(p.selectionSingle)
|
||||
p.libraryGrid.SetSingleClickActivate(true)
|
||||
p.libraryGrid.StyleContext().RemoveClass("selection")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onFilterEntryChanged() {
|
||||
p.updateGridModel()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onSortToggled(field string, button *gtk.CheckButton) {
|
||||
if !button.Active() {
|
||||
return
|
||||
}
|
||||
|
||||
p.sortField = field
|
||||
p.updateGridModel()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onSortDescToggled() {
|
||||
p.sortDesc = p.sortDescButton.Active()
|
||||
p.updateGridModel()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onGridScaleChanged() {
|
||||
size := math.Floor(p.gridScale.Value())
|
||||
gridRange := p.gridScale.Adjustment()
|
||||
if size < gridRange.Lower() || size > gridRange.Upper() {
|
||||
return
|
||||
}
|
||||
|
||||
go p.setGridSize(int(size))
|
||||
}
|
||||
|
||||
func (p *libraryPanel) setGridSize(size int) {
|
||||
if size == p.itemSize {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range p.libraryGrid.Model().NItems() {
|
||||
itemObj := p.libraryGrid.Model().Item(i)
|
||||
if itemObj != nil {
|
||||
itemStr := itemObj.Cast().(*gtk.StringObject)
|
||||
albumID := itemStr.String()
|
||||
picture, pictureFound := p.gridFactory.Picture(albumID)
|
||||
if pictureFound {
|
||||
pixbuf, pixbufFound := p.pixbufs[albumID]
|
||||
if pixbufFound {
|
||||
if pixbuf != nil {
|
||||
pixbuf = pixbuf.ScaleSimple(size, size, gdkpixbuf.InterpNearest)
|
||||
} else {
|
||||
}
|
||||
glib.IdleAdd(func() {
|
||||
picture.SetPaintable(gdk.NewTextureForPixbuf(pixbuf))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onGridScaleReleased(x float64, y float64, button uint, sequence *gdk.EventSequence) {
|
||||
size := math.Floor(p.gridScale.Value())
|
||||
gridRange := p.gridScale.Adjustment()
|
||||
if size < gridRange.Lower() || size > gridRange.Upper() {
|
||||
return
|
||||
}
|
||||
|
||||
p.itemSize = int(size)
|
||||
p.redraw()
|
||||
glib.IdleAdd(p.toolbarPopover.Popdown)
|
||||
if p.itemSizeCallback != nil {
|
||||
p.itemSizeCallback(p.itemSize)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) redraw() {
|
||||
if len(p.albums) > 0 {
|
||||
p.SetAlbums(p.albums)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onGridClicked(position uint) {
|
||||
// Get selected album
|
||||
itemObj := p.libraryModel.Item(position)
|
||||
if itemObj == nil {
|
||||
return
|
||||
}
|
||||
strObj := itemObj.Cast().(*gtk.StringObject)
|
||||
id := strObj.String()
|
||||
album, found := p.albums[id]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
// Show standalone album
|
||||
if p.libraryGrid.Model().Native() == p.selectionSingle.Native() {
|
||||
p.standaloneAlbum = album
|
||||
p.headerbarStandalone.SetAlbum(album)
|
||||
p.standaloneStack.SetVisibleChild(p.standaloneSpinner)
|
||||
p.standaloneSpinner.Start()
|
||||
p.openStandalone()
|
||||
client.Instance().LoadAlbumart(album)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onHeaderbarStandaloneClose() {
|
||||
p.closeStandalone()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) openStandalone() {
|
||||
p.libraryStack.SetVisibleChild(p.panelStandalone)
|
||||
if p.standaloneCallback != nil {
|
||||
p.standaloneCallback(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) closeStandalone() {
|
||||
p.libraryStack.SetVisibleChild(p.panelNormal)
|
||||
if p.standaloneCallback != nil {
|
||||
p.standaloneCallback(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) setCoverLoader(loader *CoverLoader) {
|
||||
p.coverLoader = loader
|
||||
}
|
||||
|
||||
func (p *libraryPanel) SetAlbumart(album *common.Album, albumart []byte) {
|
||||
if album != p.standaloneAlbum {
|
||||
return
|
||||
}
|
||||
|
||||
p.standalonePixbuf = nil
|
||||
if albumart == nil || len(albumart) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
loader, loaderErr := loadPixbuf(albumart)
|
||||
if loaderErr != nil {
|
||||
log.Println("failed to load albumart for ", album.Title(), ":", loaderErr)
|
||||
return
|
||||
}
|
||||
|
||||
p.standalonePixbuf = loader.Pixbuf()
|
||||
glib.IdleAdd(p.showStandaloneImage)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) showStandaloneImage() {
|
||||
p.resizeStandaloneImage()
|
||||
p.standaloneStack.SetVisibleChild(p.standaloneScroll)
|
||||
p.standaloneSpinner.Stop()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) resizeStandaloneImage() {
|
||||
newWidth, newHeight := p.getStandaloneSize()
|
||||
if p.standalonePixbuf == nil {
|
||||
p.standaloneImage.SetPixelSize(min(newWidth, newHeight))
|
||||
return
|
||||
}
|
||||
|
||||
width, height := p.calculateCoverSize(newWidth, newHeight)
|
||||
if width <= 0 || height <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
scaledPixbuf := p.standalonePixbuf.ScaleSimple(width, height, gdkpixbuf.InterpHyper)
|
||||
p.standaloneImage.SetFromPaintable(gdk.NewTextureForPixbuf(scaledPixbuf))
|
||||
p.standaloneImage.SetPixelSize(min(width, height))
|
||||
}
|
||||
|
||||
func (p *libraryPanel) getStandaloneSize() (int, int) {
|
||||
return p.standaloneStack.Size(gtk.OrientationHorizontal), p.standaloneStack.Size(gtk.OrientationVertical)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) calculateCoverSize(currentWidth, currentHeight int) (width, height int) {
|
||||
ratioWidth := float64(currentWidth) / float64(p.standalonePixbuf.Width())
|
||||
ratioHeight := float64(currentHeight) / float64(p.standalonePixbuf.Height())
|
||||
ratio := min(min(ratioWidth, ratioHeight), 1)
|
||||
|
||||
width = int(math.Floor(float64(p.standalonePixbuf.Width()) * ratio))
|
||||
height = int(math.Floor(float64(p.standalonePixbuf.Height()) * ratio))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onStandalonePlayClicked() {
|
||||
client.Instance().PlayAlbum(p.standaloneAlbum)
|
||||
p.closeStandalone()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onStandaloneQueueClicked() {
|
||||
client.Instance().QueueAlbum(p.standaloneAlbum)
|
||||
p.closeStandalone()
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onSelectionQueue() {
|
||||
albums := p.getSelectedAlbums()
|
||||
if len(albums) > 0 {
|
||||
client.Instance().QueueAlbums(albums)
|
||||
}
|
||||
|
||||
p.selectButton.SetActive(false)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) onSelectionCancel() {
|
||||
p.selectButton.SetActive(false)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) getSelectedAlbums() []*common.Album {
|
||||
albums := []*common.Album{}
|
||||
for i := range p.selectionMulti.NItems() {
|
||||
if p.selectionMulti.IsSelected(i) {
|
||||
itemObj := p.selectionMulti.Item(i)
|
||||
if itemObj != nil {
|
||||
itemStr := itemObj.Cast().(*gtk.StringObject)
|
||||
albumID := itemStr.String()
|
||||
album, found := p.albums[albumID]
|
||||
if found {
|
||||
albums = append(albums, album)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return albums
|
||||
}
|
||||
|
||||
func (p *libraryPanel) UpdateConfig(config *Configuration) {
|
||||
config.SortField = p.sortField
|
||||
config.SortDesc = p.sortDesc
|
||||
config.ItemSize = p.itemSize
|
||||
}
|
||||
|
||||
func (p *libraryPanel) updateGridModel() {
|
||||
albums := p.filterAlbums()
|
||||
p.sortAlbums(albums)
|
||||
p.updateGrid(albums)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) filterAlbums() []*common.Album {
|
||||
var matchingAlbums []*common.Album
|
||||
query := strings.TrimSpace(strings.ToLower(p.searchEntry.Text()))
|
||||
for _, album := range p.albums {
|
||||
if album.IsMatch(query) {
|
||||
matchingAlbums = append(matchingAlbums, album)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingAlbums
|
||||
}
|
||||
|
||||
func (p *libraryPanel) sortAlbums(filtered []*common.Album) {
|
||||
common.SortAlbums(filtered, p.sortField)
|
||||
|
||||
if p.sortDesc {
|
||||
slices.Reverse(filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) updateGrid(albums []*common.Album) {
|
||||
glib.IdleAdd(func() {
|
||||
n := p.libraryModel.NItems()
|
||||
if n > 0 {
|
||||
p.libraryModel.Splice(0, n, nil)
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(albums))
|
||||
for _, item := range albums {
|
||||
ids = append(ids, item.ID())
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
p.libraryModel.Splice(0, 0, ids)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *libraryPanel) Toolbar() gtk.Widgetter {
|
||||
return p.toolbar
|
||||
}
|
||||
|
||||
func (p *libraryPanel) Wait() {
|
||||
p.libraryWait.Wait()
|
||||
}
|
||||
|
||||
type LibraryFactory gtk.ListItemFactory
|
||||
|
||||
func (p *libraryPanel) SetAlbums(albums map[string]*common.Album) {
|
||||
p.libraryWait.Add(1)
|
||||
go p.setAlbums(albums)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) setAlbums(albums map[string]*common.Album) {
|
||||
p.libraryLock.Lock()
|
||||
defer p.libraryLock.Unlock()
|
||||
defer p.libraryWait.Done()
|
||||
|
||||
p.albums = albums
|
||||
glib.IdleAdd(func() {
|
||||
p.stack.SetVisibleChild(p.progressBox)
|
||||
p.progressBar.SetFraction(0.0)
|
||||
})
|
||||
|
||||
// Create loader loader
|
||||
i := 0
|
||||
n := len(albums)
|
||||
p.pixbufs = make(map[string]*gdkpixbuf.Pixbuf)
|
||||
|
||||
for _, album := range albums {
|
||||
if p.coverLoader != nil {
|
||||
p.pixbufs[album.ID()] = p.coverLoader.LoadThumbnail(album)
|
||||
}
|
||||
|
||||
i++
|
||||
glib.IdleAdd(func() {
|
||||
p.progressBar.SetFraction(float64(i) / float64(n))
|
||||
// FIXME: i18n
|
||||
p.progressBar.SetText("Loading images")
|
||||
})
|
||||
}
|
||||
|
||||
p.updateGridModel()
|
||||
glib.IdleAdd(func() {
|
||||
p.stack.SetVisibleChild(p.scroll)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *libraryPanel) SetWidth(width int) {
|
||||
p.resizeStandaloneImage()
|
||||
p.setMarks(width)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) setMarks(width int) {
|
||||
glib.IdleAdd(func() {
|
||||
p.gridScale.ClearMarks()
|
||||
})
|
||||
|
||||
lower := p.gridScale.Adjustment().Lower()
|
||||
upper := p.gridScale.Adjustment().Upper()
|
||||
countMin := int(math.Max(float64(width)/upper, 1))
|
||||
countMax := int(math.Max(float64(width)/lower, 1))
|
||||
for i := countMin; i <= countMax; i++ {
|
||||
pixel := int(float64(width) / float64(i))
|
||||
pixel = pixel - (2 * pixel / 100)
|
||||
glib.IdleAdd(func() {
|
||||
p.gridScale.AddMark(float64(pixel), gtk.PosBottom, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *libraryPanel) ShowSearch() {
|
||||
p.searchBar.SetSearchMode(true)
|
||||
}
|
||||
|
||||
func (p *libraryPanel) HeaderbarStandalone() *albumHeaderbar {
|
||||
return p.headerbarStandalone
|
||||
}
|
||||
Loading…
Reference in a new issue