240 lines
4.8 KiB
Go
240 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
//go:embed data/gitmojis.json data/images
|
|
var data embed.FS
|
|
|
|
var preferredTheme = flag.String("theme", "", "Specify the theme to be used. dark/light")
|
|
var linger = flag.String("linger", "", "If specified, linger for the given duration (e.g. 10s) before actually closing.")
|
|
|
|
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() {
|
|
flag.Parse()
|
|
|
|
gitmojiData, err := loadData()
|
|
if err != nil {
|
|
// TODO show dialog
|
|
panic(err)
|
|
}
|
|
|
|
a := app.New()
|
|
|
|
switch *preferredTheme {
|
|
case "dark":
|
|
a.Settings().SetTheme(theme.DarkTheme())
|
|
case "light":
|
|
a.Settings().SetTheme(theme.LightTheme())
|
|
}
|
|
|
|
var lingerDuration *time.Duration = nil
|
|
if *linger != "" {
|
|
if d, err := time.ParseDuration(*linger); err != nil {
|
|
log.Fatalf("invalid linger time: %v\n", err)
|
|
} else {
|
|
lingerDuration = &d
|
|
}
|
|
}
|
|
|
|
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 {
|
|
b, err := data.ReadFile("data/images/" + gitmoji.Name + ".png")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
allEntries[i] = &GitmojiEntry{
|
|
Gitmoji: gitmoji,
|
|
Icon: fyne.NewStaticResource(gitmoji.Name, b),
|
|
}
|
|
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.ForegroundColor())
|
|
tn.TextStyle.Bold = true
|
|
|
|
td := canvas.NewText("Dummy", theme.ForegroundColor())
|
|
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()
|
|
},
|
|
)
|
|
|
|
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)
|
|
l.OnSelected = func(id widget.ListItemID) {
|
|
currentId = id
|
|
w.Canvas().Focus(e)
|
|
}
|
|
|
|
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)
|
|
if lingerDuration != nil {
|
|
w.Hide()
|
|
time.Sleep(*lingerDuration)
|
|
}
|
|
w.Close()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
w.Canvas().SetOnTypedKey(func(event *fyne.KeyEvent) {
|
|
e.onKeyTyped(event)
|
|
})
|
|
|
|
w.Canvas().Focus(e)
|
|
|
|
w.ShowAndRun()
|
|
}
|
|
|
|
func loadData() (*GitmojiData, error) {
|
|
b, err := data.ReadFile("data/gitmojis.json")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read gitmoji data: %w", err)
|
|
}
|
|
|
|
var result GitmojiData
|
|
if err := json.Unmarshal(b, &result); err != nil {
|
|
return nil, fmt.Errorf("cannot decode gitmoji data: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|