package main import ( "encoding/json" "fmt" "os" "sync" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/sahilm/fuzzy" ) type Gitmoji struct { Emoji string Code string Description string Name string } type GitmojiData struct { Gitmojis []Gitmoji } type GitmojiEntry struct { Gitmoji Icon fyne.Resource } type keyedEntry struct { widget.Entry onKeyTyped func(keyEvent *fyne.KeyEvent) bool } func (e *keyedEntry) TypedKey(keyEvent *fyne.KeyEvent) { if e.onKeyTyped == nil || !e.onKeyTyped(keyEvent) { e.Entry.TypedKey(keyEvent) } } func newKeyedEntry() *keyedEntry { e := &keyedEntry{} e.ExtendBaseWidget(e) return e } func main() { gitmojiData, err := loadData() if err != nil { // TODO show dialog panic(err) } a := app.New() a.Settings().SetTheme(theme.LightTheme()) w := a.NewWindow("Gitmoji Picker") w.CenterOnScreen() allEntries := make([]*GitmojiEntry, len(gitmojiData.Gitmojis)) allTerms := make([]string, len(gitmojiData.Gitmojis)) currentId := 0 for i, gitmoji := range gitmojiData.Gitmojis { res, err := fyne.LoadResourceFromPath("data/images/" + gitmoji.Name + ".png") if err != nil { panic(err) } allEntries[i] = &GitmojiEntry{ Gitmoji: gitmoji, Icon: res, } allTerms[i] = gitmoji.Name + " " + gitmoji.Description } buildFilteredList := func(filter string) []*GitmojiEntry { r := fuzzy.Find(filter, allTerms) result := make([]*GitmojiEntry, len(r)) for i := range r { result[i] = allEntries[r[i].Index] } return result } var m sync.Mutex currentEntries := allEntries e := newKeyedEntry() l := widget.NewList( func() int { return len(currentEntries) }, func() fyne.CanvasObject { tn := canvas.NewText("Dummy", theme.TextColor()) tn.TextStyle.Bold = true td := canvas.NewText("Dummy", theme.TextColor()) td.TextSize = theme.TextSize() * 8 / 10 return container.NewHBox( widget.NewIcon(allEntries[0].Icon), container.NewVBox(tn, td), ) }, func(id widget.ListItemID, object fyne.CanvasObject) { m.Lock() entry := currentEntries[id] m.Unlock() hb := object.(*fyne.Container) hb.Objects[0].(*widget.Icon).SetResource(entry.Icon) vb := hb.Objects[1].(*fyne.Container) vb.Objects[0].(*canvas.Text).Text = entry.Name vb.Objects[1].(*canvas.Text).Text = entry.Description vb.Objects[0].(*canvas.Text).Refresh() vb.Objects[1].(*canvas.Text).Refresh() }, ) l.OnSelected = func(id widget.ListItemID) { currentId = id } e.OnChanged = func(s string) { m.Lock() if s == "" { currentEntries = allEntries } else { currentEntries = buildFilteredList(s) } if len(currentEntries) > 0 { currentId = 0 } else { currentId = -1 } m.Unlock() l.Select(currentId) l.Refresh() } l.Select(currentId) w.SetContent(container.NewBorder(e, nil, nil, nil, l)) w.Resize(fyne.NewSize(300, 300)) e.Show() e.onKeyTyped = func(event *fyne.KeyEvent) bool { switch event.Name { case fyne.KeyDown: m.Lock() if currentId < len(currentEntries)-1 { currentId++ } m.Unlock() l.Select(currentId) return true case fyne.KeyUp: m.Lock() if currentId > 0 { currentId-- } m.Unlock() l.Select(currentId) return true case fyne.KeyEscape: w.Close() return true case fyne.KeyReturn, fyne.KeyEnter: m.Lock() defer m.Unlock() if currentId > -1 { w.Clipboard().SetContent(currentEntries[currentId].Emoji) w.Close() return true } } return false } w.Canvas().Focus(e) w.ShowAndRun() } func loadData() (*GitmojiData, error) { f, err := os.Open("data/gitmojis.json") if err != nil { return nil, fmt.Errorf("cannot load gitmoji file: %w", err) } defer f.Close() var result GitmojiData if err := json.NewDecoder(f).Decode(&result); err != nil { return nil, fmt.Errorf("cannot load gitmoji file: %w", err) } return &result, nil }