From 892c5434f513fd07bc4d8563e56091fb4abb3d3f Mon Sep 17 00:00:00 2001 From: Leigh MacDonald Date: Tue, 28 Feb 2023 01:56:10 -0700 Subject: [PATCH] Use common avatar cache. Add shared callback struct. --- application.go | 9 +-- model/consts.go | 2 + model/player.go | 15 ----- pkg/rules/engine.go | 5 +- ui/avatar.go | 31 ++++++++++ ui/chat_game.go | 62 +++++++++++--------- ui/chat_user.go | 7 ++- ui/menu.go | 13 ++-- ui/names.go | 40 ++++++++++--- ui/players.go | 29 ++++----- ui/ui.go | 140 +++++++++++++++++--------------------------- 11 files changed, 180 insertions(+), 173 deletions(-) create mode 100644 ui/avatar.go diff --git a/application.go b/application.go index d329192..90addb0 100644 --- a/application.go +++ b/application.go @@ -544,7 +544,7 @@ func (bd *BD) gameStateTracker(ctx context.Context) { log.Printf("Failed to download avatar [%s]: %v\n", p.AvatarHash, errDownload) continue } - p.SetAvatar(p.AvatarHash, avatar) + bd.gui.SetAvatar(sid64, avatar) queueUpdate = true case update := <-bd.gameStateUpdate: var sourcePlayer *model.Player @@ -758,13 +758,6 @@ func (bd *BD) onUpdateMark(status updateMarkEvent) error { // TODO Use channels for communicating instead func (bd *BD) AttachGui(gui ui.UserInterface) { gui.SetBuildInfo(version, commit, date, builtBy) - gui.SetOnLaunchTF2(func() { - go bd.launchGameAndWait() - }) - gui.SetOnKick(func(ctx context.Context, userId int64, reason model.KickReason) error { - return bd.callVote(ctx, userId, reason) - }) - gui.UpdateAttributes(bd.rules.UniqueTags()) bd.gui = gui } diff --git a/model/consts.go b/model/consts.go index 179a409..525e6f7 100644 --- a/model/consts.go +++ b/model/consts.go @@ -40,6 +40,8 @@ const ( EvtLobby ) +type SteamIDFunc func(sid64 steamid.SID64) + type MarkFunc func(sid64 steamid.SID64, attrs []string) error type KickReason string diff --git a/model/player.go b/model/player.go index c5295c2..9504352 100644 --- a/model/player.go +++ b/model/player.go @@ -2,10 +2,8 @@ package model import ( "fmt" - "fyne.io/fyne/v2" "github.com/leighmacdonald/bd/pkg/rules" "github.com/leighmacdonald/steamid/v2/steamid" - "log" "time" ) @@ -53,7 +51,6 @@ type Player struct { AccountCreatedOn time.Time Visibility ProfileVisibility - Avatar fyne.Resource // TODO store somewhere else so we dont couple ui item to the model AvatarHash string // PlayerBanState @@ -150,17 +147,6 @@ func AvatarUrl(hash string) string { return fmt.Sprintf("%s/%s/%s_full.jpg", baseAvatarUrl, firstN(avatarHash, 2), avatarHash) } -func (ps *Player) SetAvatar(hash string, buf []byte) { - res := fyne.NewStaticResource(fmt.Sprintf("%s.jpg", hash), buf) - if res == nil { - log.Printf("Failed to load avatar\n") - return - } else { - ps.Avatar = res - ps.AvatarHash = rules.HashBytes(buf) - } -} - type PlayerCollection []*Player func (players PlayerCollection) AsAny() []any { @@ -178,7 +164,6 @@ func NewPlayer(sid64 steamid.SID64, name string) *Player { RealName: "", NamePrevious: "", AccountCreatedOn: time.Time{}, - Avatar: nil, AvatarHash: "", CommunityBanned: false, Visibility: ProfileVisibilityPublic, diff --git a/pkg/rules/engine.go b/pkg/rules/engine.go index ae87585..4b5dd24 100644 --- a/pkg/rules/engine.go +++ b/pkg/rules/engine.go @@ -80,7 +80,7 @@ func (e *Engine) Mark(opts MarkOpts) error { Time: int(time.Now().Unix()), PlayerName: opts.Name, }, - SteamID: opts.SteamID, + SteamID: opts.SteamID.String(), Proof: opts.Proof, }) e.Unlock() @@ -167,6 +167,7 @@ func (e *Engine) ImportRules(list *RuleSchema) error { // ImportPlayers loads the provided player list for matching func (e *Engine) ImportPlayers(list *PlayerListSchema) error { var playerAttrs []string + var count int for _, player := range list.Players { var steamID steamid.SID64 // Some entries can be raw number types in addition to strings... @@ -187,6 +188,7 @@ func (e *Engine) ImportPlayers(list *PlayerListSchema) error { } e.registerSteamIDMatcher(newSteamIDMatcher(list.FileInfo.Title, steamID)) playerAttrs = append(playerAttrs, player.Attributes...) + count++ } e.Lock() for _, newTag := range playerAttrs { @@ -203,6 +205,7 @@ func (e *Engine) ImportPlayers(list *PlayerListSchema) error { } e.playerLists = append(e.playerLists, list) e.Unlock() + log.Printf("[%s] Loaded %d players\n", list.FileInfo.Title, count) return nil } diff --git a/ui/avatar.go b/ui/avatar.go new file mode 100644 index 0000000..bd6c9c9 --- /dev/null +++ b/ui/avatar.go @@ -0,0 +1,31 @@ +package ui + +import ( + "fyne.io/fyne/v2" + "github.com/leighmacdonald/steamid/v2/steamid" + "sync" +) + +type avatarCache struct { + *sync.RWMutex + userAvatar map[steamid.SID64]fyne.Resource +} + +func (cache *avatarCache) SetAvatar(sid64 steamid.SID64, data []byte) { + if !sid64.Valid() || data == nil { + return + } + cache.Lock() + cache.userAvatar[sid64] = fyne.NewStaticResource(sid64.String(), data) + cache.Unlock() +} + +func (cache *avatarCache) GetAvatar(sid64 steamid.SID64) fyne.Resource { + cache.RLock() + defer cache.RUnlock() + av, found := cache.userAvatar[sid64] + if found { + return av + } + return resourceDefaultavatarJpg +} diff --git a/ui/chat_game.go b/ui/chat_game.go index baef8e4..6da05fc 100644 --- a/ui/chat_game.go +++ b/ui/chat_game.go @@ -11,7 +11,6 @@ import ( "fyne.io/fyne/v2/widget" "github.com/leighmacdonald/bd/model" "github.com/leighmacdonald/bd/translations" - "github.com/leighmacdonald/steamid/v2/steamid" "github.com/pkg/errors" "log" "sync" @@ -19,35 +18,43 @@ import ( ) type gameChatWindow struct { + window fyne.Window ctx context.Context app fyne.App - window fyne.Window list *widget.List boundList binding.UntypedList - objectMu sync.RWMutex - boundListMu sync.RWMutex + objectMu *sync.RWMutex + boundListMu *sync.RWMutex messageCount binding.Int autoScrollEnabled binding.Bool + avatarCache *avatarCache + cb callBacks } -func newGameChatWindow(ctx context.Context, app fyne.App, kickFunc model.KickFunc, attrs binding.StringList, markFunc model.MarkFunc, - settings *model.Settings, createUserChat func(sid64 steamid.SID64), createNameHistory func(sid64 steamid.SID64)) *gameChatWindow { - chatWindow := app.NewWindow(translations.One(translations.WindowChatHistoryGame)) - chatWindow.Canvas().AddShortcut( +func newGameChatWindow(ctx context.Context, app fyne.App, cb callBacks, attrs binding.StringList, settings *model.Settings, cache *avatarCache) *gameChatWindow { + window := app.NewWindow(translations.One(translations.WindowChatHistoryGame)) + window.Canvas().AddShortcut( &desktop.CustomShortcut{KeyName: fyne.KeyW, Modifier: fyne.KeyModifierControl}, func(shortcut fyne.Shortcut) { - chatWindow.Hide() + window.Hide() }) - - window := gameChatWindow{ + window.SetCloseIntercept(func() { + window.Hide() + }) + gcw := gameChatWindow{ + window: window, ctx: ctx, app: app, - window: chatWindow, boundList: binding.BindUntypedList(&[]interface{}{}), autoScrollEnabled: binding.NewBool(), messageCount: binding.NewInt(), + boundListMu: &sync.RWMutex{}, + objectMu: &sync.RWMutex{}, + avatarCache: cache, + cb: cb, } - if errSet := window.autoScrollEnabled.Set(true); errSet != nil { + + if errSet := gcw.autoScrollEnabled.Set(true); errSet != nil { log.Printf("Failed to set default autoscroll: %v\n", errSet) } @@ -67,7 +74,7 @@ func newGameChatWindow(ctx context.Context, app fyne.App, kickFunc model.KickFun return } um := obj.(model.UserMessage) - window.objectMu.Lock() + gcw.objectMu.Lock() rootContainer := o.(*fyne.Container) timeAndProfileContainer := rootContainer.Objects[1].(*fyne.Container) timeStamp := timeAndProfileContainer.Objects[0].(*widget.Label) @@ -76,9 +83,9 @@ func newGameChatWindow(ctx context.Context, app fyne.App, kickFunc model.KickFun timeStamp.SetText(um.Created.Format(time.Kitchen)) profileButton.SetText(um.Player) - profileButton.menu = generateUserMenu(window.ctx, app, window.window, um.PlayerSID, um.UserId, - kickFunc, attrs, markFunc, settings.Links, createUserChat, createNameHistory) - profileButton.menu.Refresh() + profileButton.SetIcon(gcw.avatarCache.GetAvatar(um.PlayerSID)) + profileButton.menu = generateUserMenu(gcw.ctx, app, gcw.window, um.PlayerSID, um.UserId, cb, attrs, settings.Links) + //profileButton.menu.Refresh() profileButton.Refresh() nameStyle := widget.RichTextStyleInline if um.Team == model.Red { @@ -92,32 +99,31 @@ func newGameChatWindow(ctx context.Context, app fyne.App, kickFunc model.KickFun } messageRichText.Refresh() - window.objectMu.Unlock() + gcw.objectMu.Unlock() } - window.list = widget.NewListWithData(window.boundList, createFunc, updateFunc) - window.window.SetContent(container.NewBorder( + gcw.list = widget.NewListWithData(gcw.boundList, createFunc, updateFunc) + gcw.window.SetContent(container.NewBorder( container.NewBorder( nil, nil, container.NewHBox( - widget.NewCheckWithData(translations.One(translations.LabelAutoScroll), window.autoScrollEnabled), - widget.NewButtonWithIcon(translations.One(translations.LabelBottom), theme.MoveDownIcon(), window.list.ScrollToBottom), + widget.NewCheckWithData(translations.One(translations.LabelAutoScroll), gcw.autoScrollEnabled), + widget.NewButtonWithIcon(translations.One(translations.LabelBottom), theme.MoveDownIcon(), gcw.list.ScrollToBottom), widget.NewButtonWithIcon(translations.One(translations.LabelClear), theme.ContentClearIcon(), func() { - if errReload := window.boundList.Set(nil); errReload != nil { + if errReload := gcw.boundList.Set(nil); errReload != nil { log.Printf("Failed to clear chat: %v\n", errReload) } }), ), - widget.NewLabelWithData(binding.IntToStringWithFormat(window.messageCount, fmt.Sprintf("%s%%d", translations.One(translations.LabelMessageCount)))), + widget.NewLabelWithData(binding.IntToStringWithFormat(gcw.messageCount, fmt.Sprintf("%s%%d", translations.One(translations.LabelMessageCount)))), widget.NewLabel(""), ), nil, nil, nil, - container.NewVScroll(window.list))) - chatWindow.Resize(fyne.NewSize(1000, 500)) - window.window.Content().Refresh() - return &window + container.NewVScroll(gcw.list))) + gcw.window.Resize(fyne.NewSize(1000, 500)) + return &gcw } func (gcw *gameChatWindow) append(msg any) error { diff --git a/ui/chat_user.go b/ui/chat_user.go index e3ec5ba..19b6683 100644 --- a/ui/chat_user.go +++ b/ui/chat_user.go @@ -35,7 +35,9 @@ func newUserChatWindow(ctx context.Context, app fyne.App, queryFunc model.QueryU 1, map[string]interface{}{ "SteamId": sid64, })) - + appWindow.SetCloseIntercept(func() { + appWindow.Hide() + }) window := userChatWindow{ Window: appWindow, app: app, @@ -105,7 +107,7 @@ func newUserChatWindow(ctx context.Context, app fyne.App, queryFunc model.QueryU Team: 0, Player: "Bot Detector", PlayerSID: 0, - UserId: 69, + UserId: -1, Message: "No messages", Created: time.Now(), Dead: false, @@ -116,6 +118,5 @@ func newUserChatWindow(ctx context.Context, app fyne.App, queryFunc model.QueryU log.Printf("Failed to set messages: %v\n", errSet) } window.Resize(fyne.NewSize(600, 600)) - window.Show() return &window } diff --git a/ui/menu.go b/ui/menu.go index f5dbfa2..6dececd 100644 --- a/ui/menu.go +++ b/ui/menu.go @@ -176,17 +176,16 @@ func generateKickMenu(ctx context.Context, userId int64, kickFunc model.KickFunc ) } -func generateUserMenu(ctx context.Context, app fyne.App, window fyne.Window, steamId steamid.SID64, userId int64, kickFunc model.KickFunc, - knownAttributes binding.StringList, markFunc model.MarkFunc, links []model.LinkConfig, - createUserChat func(sid64 steamid.SID64), createNameHistory func(sid64 steamid.SID64)) *fyne.Menu { +func generateUserMenu(ctx context.Context, app fyne.App, window fyne.Window, steamId steamid.SID64, userId int64, cb callBacks, + knownAttributes binding.StringList, links []model.LinkConfig) *fyne.Menu { menu := fyne.NewMenu("User Actions", &fyne.MenuItem{ Icon: theme.CheckButtonCheckedIcon(), - ChildMenu: generateKickMenu(ctx, userId, kickFunc), + ChildMenu: generateKickMenu(ctx, userId, cb.kickFunc), Label: translations.One(translations.MenuCallVote)}, &fyne.MenuItem{ Icon: theme.ZoomFitIcon(), - ChildMenu: generateAttributeMenu(window, steamId, knownAttributes, markFunc), + ChildMenu: generateAttributeMenu(window, steamId, knownAttributes, cb.markFn), Label: translations.One(translations.MenuMarkAs)}, &fyne.MenuItem{ Icon: theme.SearchIcon(), @@ -199,13 +198,13 @@ func generateUserMenu(ctx context.Context, app fyne.App, window fyne.Window, ste &fyne.MenuItem{ Icon: theme.ListIcon(), Action: func() { - createUserChat(steamId) + cb.createUserChat(steamId) }, Label: translations.One(translations.MenuChatHistory)}, &fyne.MenuItem{ Icon: theme.VisibilityIcon(), Action: func() { - createNameHistory(steamId) + cb.createNameHistory(steamId) }, Label: translations.One(translations.MenuNameHistory)}, ) diff --git a/ui/names.go b/ui/names.go index f42be4a..050476f 100644 --- a/ui/names.go +++ b/ui/names.go @@ -6,6 +6,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/leighmacdonald/bd/model" "github.com/leighmacdonald/bd/translations" @@ -21,10 +22,9 @@ type userNameWindow struct { list *widget.List boundList binding.ExternalUntypedList - content fyne.CanvasObject objectMu sync.RWMutex boundListMu sync.RWMutex - messageCount binding.Int + nameCount binding.Int autoScrollEnabled binding.Bool } @@ -54,16 +54,21 @@ func newUserNameWindow(ctx context.Context, app fyne.App, namesFunc model.QueryN appWindow := app.NewWindow(translations.Tr(&i18n.Message{ID: string(translations.WindowNameHistory)}, 1, map[string]interface{}{ "SteamId": sid64, })) - + appWindow.SetCloseIntercept(func() { + appWindow.Hide() + }) unl := &userNameWindow{ Window: appWindow, boundList: binding.BindUntypedList(&[]interface{}{}), autoScrollEnabled: binding.NewBool(), - messageCount: binding.NewInt(), + nameCount: binding.NewInt(), + } + if errSet := unl.autoScrollEnabled.Set(true); errSet != nil { + log.Printf("Failed to set default autoscroll: %v\n", errSet) } - boundList := binding.BindUntypedList(&[]interface{}{}) + userMessageListWidget := widget.NewListWithData( - boundList, + unl.boundList, func() fyne.CanvasObject { return container.NewBorder( nil, @@ -89,8 +94,7 @@ func newUserNameWindow(ctx context.Context, app fyne.App, namesFunc model.QueryN unl.objectMu.Unlock() }) unl.list = userMessageListWidget - unl.boundList = boundList - unl.content = container.NewVScroll(userMessageListWidget) + names, err := namesFunc(ctx, sid64) if err != nil { names = append(names, model.UserNameHistory{ @@ -102,5 +106,25 @@ func newUserNameWindow(ctx context.Context, app fyne.App, namesFunc model.QueryN if errSet := unl.boundList.Set(names.AsAny()); errSet != nil { log.Printf("Failed to set names list: %v\n", errSet) } + if errSetCount := unl.nameCount.Set(unl.boundList.Length()); errSetCount != nil { + log.Printf("Failed to set name count: %v", errSetCount) + } + unl.SetContent(container.NewBorder( + container.NewBorder( + nil, + nil, + container.NewHBox( + widget.NewCheckWithData(translations.One(translations.LabelAutoScroll), unl.autoScrollEnabled), + widget.NewButtonWithIcon(translations.One(translations.LabelBottom), theme.MoveDownIcon(), unl.list.ScrollToBottom), + ), + widget.NewLabelWithData(binding.IntToStringWithFormat(unl.nameCount, fmt.Sprintf("%s%%d", translations.One(translations.LabelMessageCount)))), + widget.NewLabel(""), + ), + nil, + nil, + nil, + container.NewVScroll(unl.list))) + unl.Resize(fyne.NewSize(600, 600)) + unl.Show() return unl } diff --git a/ui/players.go b/ui/players.go index 3ea1537..c1ae24b 100644 --- a/ui/players.go +++ b/ui/players.go @@ -50,7 +50,8 @@ type playerWindow struct { menuCreator MenuCreator onReload func(count int) - onLaunch model.LaunchFunc + callBacks callBacks + avatarCache *avatarCache } func (screen *playerWindow) updatePlayerState(players model.PlayerCollection) { @@ -160,7 +161,7 @@ func (screen *playerWindow) createMainMenu() { shortCutAbout := &desktop.CustomShortcut{KeyName: fyne.KeyA, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift} screen.window.Canvas().AddShortcut(shortCutLaunch, func(shortcut fyne.Shortcut) { - screen.onLaunch() + screen.callBacks.gameLauncherFunc() }) screen.window.Canvas().AddShortcut(shortCutChat, func(shortcut fyne.Shortcut) { screen.onShowChat() @@ -189,7 +190,7 @@ func (screen *playerWindow) createMainMenu() { &fyne.MenuItem{ Shortcut: shortCutLaunch, Label: translations.One(translations.LabelLaunch), - Action: screen.onLaunch, + Action: screen.callBacks.gameLauncherFunc, Icon: resourceTf2Png, }, &fyne.MenuItem{ @@ -251,22 +252,18 @@ const symbolOk = "✓" const symbolBad = "✗" // ┌─────┬───────────────────────────────────────────────────┐ -// │ X │ profile name │ Vac.. │ +// │ P │ profile name │ Vac.. │ // │─────────────────────────────────────────────────────────┤ -// │ K: 10 A: 66 │ -// └─────────────────────────────────────────────────────────┘ -func newPlayerWindow(app fyne.App, settings *model.Settings, - boundSettings boundSettings, showChatWindowFunc func(), launchFunc func(), menuCreator MenuCreator) *playerWindow { - window := app.NewWindow("Bot Detector") - +func newPlayerWindow(app fyne.App, settings *model.Settings, boundSettings boundSettings, showChatWindowFunc func(), callbacks callBacks, menuCreator MenuCreator, cache *avatarCache) *playerWindow { screen := &playerWindow{ app: app, - window: window, + window: app.NewWindow("Bot Detector"), boundList: binding.BindUntypedList(&[]interface{}{}), bindingPlayerCount: binding.NewInt(), onShowChat: showChatWindowFunc, - onLaunch: launchFunc, + callBacks: callbacks, menuCreator: menuCreator, + avatarCache: cache, labelHostname: widget.NewRichText( &widget.TextSegment{Text: translations.One(translations.LabelHostname), Style: widget.RichTextStyleInline}, &widget.TextSegment{Text: "n/a", Style: widget.RichTextStyleStrong}, @@ -280,7 +277,7 @@ func newPlayerWindow(app fyne.App, settings *model.Settings, screen.labelPlayersHeading = widget.NewLabelWithData(binding.IntToStringWithFormat(screen.bindingPlayerCount, "%d Players")) screen.settingsDialog = newSettingsDialog(screen.window, boundSettings, settings) screen.listsDialog = newRuleListConfigDialog(screen.window, settings.Save, settings) - screen.aboutDialog = newAboutDialog(window) + screen.aboutDialog = newAboutDialog(screen.window) screen.onReload = func(count int) { if errSet := screen.bindingPlayerCount.Set(count); errSet != nil { log.Printf("Failed to update player count: %v\n", errSet) @@ -297,7 +294,7 @@ func newPlayerWindow(app fyne.App, settings *model.Settings, }, func() { screen.aboutDialog.dialog.Show() }, - screen.onLaunch, + screen.callBacks.gameLauncherFunc, func() { screen.listsDialog.Show() }) @@ -351,9 +348,7 @@ func newPlayerWindow(app fyne.App, settings *model.Settings, btn := upperContainer.Objects[1].(*menuButton) btn.menu = screen.menuCreator(screen.window, ps.SteamId, ps.UserId) - if ps.Avatar != nil { - btn.Icon = ps.Avatar - } + btn.Icon = screen.avatarCache.GetAvatar(ps.SteamId) btn.Refresh() profileLabel := upperContainer.Objects[0].(*widget.RichText) diff --git a/ui/ui.go b/ui/ui.go index abfb85e..56d6170 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -24,12 +24,17 @@ const ( urlHelp = "https://github.com/leighmacdonald/bd/wiki" ) +func defaultApp() fyne.App { + application := app.NewWithID(AppId) + application.Settings().SetTheme(&bdTheme{}) + application.SetIcon(resourceIconPng) + return application +} + type UserInterface interface { Refresh() Start() SetBuildInfo(version string, commit string, date string, builtBy string) - SetOnLaunchTF2(func()) - SetOnKick(kickFunc model.KickFunc) UpdateServerState(state model.Server) UpdatePlayerState(collection model.PlayerCollection) AddUserMessage(message model.UserMessage) @@ -41,28 +46,30 @@ type windows struct { player *playerWindow chat *gameChatWindow chatHistory map[steamid.SID64]*userChatWindow - nameHistory map[steamid.SID64]fyne.Window + nameHistory map[steamid.SID64]*userNameWindow } -type MenuCreator func(window fyne.Window, steamId steamid.SID64, userId int64) *fyne.Menu - -type Ui struct { - ctx context.Context - application fyne.App - boundSettings boundSettings - settings *model.Settings - //players model.PlayerCollection - windows *windows - - knownAttributes binding.StringList - gameLauncherFunc func() +type callBacks struct { markFn model.MarkFunc kickFunc model.KickFunc queryNamesFunc model.QueryNamesFunc queryUserMessagesFunc model.QueryUserMessagesFunc - userAvatarMu *sync.RWMutex + gameLauncherFunc model.LaunchFunc + createUserChat model.SteamIDFunc + createNameHistory model.SteamIDFunc +} - userAvatar map[steamid.SID64]fyne.Resource +type MenuCreator func(window fyne.Window, steamId steamid.SID64, userId int64) *fyne.Menu + +type Ui struct { + ctx context.Context + application fyne.App + boundSettings boundSettings + settings *model.Settings + windows *windows + callBacks callBacks + knownAttributes binding.StringList + avatarCache *avatarCache } func (ui *Ui) UpdateServerState(state model.Server) { @@ -73,13 +80,6 @@ func (ui *Ui) UpdatePlayerState(collection model.PlayerCollection) { ui.windows.player.updatePlayerState(collection) } -func defaultApp() fyne.App { - application := app.NewWithID(AppId) - application.Settings().SetTheme(&bdTheme{}) - application.SetIcon(resourceIconPng) - return application -} - func New(ctx context.Context, settings *model.Settings, markFunc model.MarkFunc, namesFunc model.QueryNamesFunc, messagesFunc model.QueryUserMessagesFunc, gameLaunchFunc func(), kickFunc model.KickFunc) UserInterface { ui := Ui{ @@ -90,33 +90,40 @@ func New(ctx context.Context, settings *model.Settings, markFunc model.MarkFunc, knownAttributes: binding.NewStringList(), windows: &windows{ chatHistory: map[steamid.SID64]*userChatWindow{}, - nameHistory: map[steamid.SID64]fyne.Window{}, + nameHistory: map[steamid.SID64]*userNameWindow{}, + }, + avatarCache: &avatarCache{ + RWMutex: &sync.RWMutex{}, + userAvatar: make(map[steamid.SID64]fyne.Resource), + }, + callBacks: callBacks{ + queryNamesFunc: namesFunc, + queryUserMessagesFunc: messagesFunc, + kickFunc: kickFunc, + markFn: markFunc, + gameLauncherFunc: gameLaunchFunc, }, - userAvatarMu: &sync.RWMutex{}, - userAvatar: make(map[steamid.SID64]fyne.Resource), - queryNamesFunc: namesFunc, - queryUserMessagesFunc: messagesFunc, - kickFunc: kickFunc, - gameLauncherFunc: gameLaunchFunc, } - - ui.windows.chat = newGameChatWindow(ui.ctx, ui.application, ui.kickFunc, ui.knownAttributes, markFunc, settings, func(sid64 steamid.SID64) { + ui.callBacks.createUserChat = func(sid64 steamid.SID64) { ui.createChatHistoryWindow(sid64) - }, func(sid64 steamid.SID64) { + } + ui.callBacks.createNameHistory = func(sid64 steamid.SID64) { ui.createNameHistoryWindow(sid64) - }) + } + + ui.windows.chat = newGameChatWindow(ui.ctx, ui.application, ui.callBacks, ui.knownAttributes, settings, ui.avatarCache) ui.windows.player = newPlayerWindow( ui.application, settings, - ui.boundSettings, func() { + ui.boundSettings, + func() { ui.windows.chat.window.Show() }, - ui.gameLauncherFunc, + ui.callBacks, func(window fyne.Window, steamId steamid.SID64, userId int64) *fyne.Menu { - return generateUserMenu(ui.ctx, ui.application, window, steamId, userId, ui.kickFunc, ui.knownAttributes, ui.markFn, - ui.settings.Links, ui.createChatHistoryWindow, ui.createNameHistoryWindow) - }) + return generateUserMenu(ui.ctx, ui.application, window, steamId, userId, ui.callBacks, ui.knownAttributes, ui.settings.Links) + }, ui.avatarCache) return &ui } @@ -125,41 +132,13 @@ func (ui *Ui) SetAvatar(sid64 steamid.SID64, data []byte) { if !sid64.Valid() || data == nil { return } - ui.userAvatarMu.Lock() - ui.userAvatar[sid64] = fyne.NewStaticResource(sid64.String(), data) - ui.userAvatarMu.Unlock() -} - -func (ui *Ui) GetAvatar(sid64 steamid.SID64) fyne.Resource { - ui.userAvatarMu.RLock() - defer ui.userAvatarMu.RUnlock() - av, found := ui.userAvatar[sid64] - if found { - return av - } - return resourceDefaultavatarJpg + ui.avatarCache.SetAvatar(sid64, data) } func (ui *Ui) SetBuildInfo(version string, commit string, date string, builtBy string) { ui.windows.player.aboutDialog.SetBuildInfo(version, commit, date, builtBy) } -func (ui *Ui) SetFetchMessageHistory(messagesFunc model.QueryUserMessagesFunc) { - ui.queryUserMessagesFunc = messagesFunc -} - -func (ui *Ui) SetFetchNameHistory(namesFunc model.QueryNamesFunc) { - ui.queryNamesFunc = namesFunc -} - -func (ui *Ui) SetOnMark(fn model.MarkFunc) { - ui.markFn = fn -} - -func (ui *Ui) SetOnKick(fn model.KickFunc) { - ui.kickFunc = fn -} - func (ui *Ui) Refresh() { ui.windows.chat.window.Content().Refresh() if ui.windows.player != nil { @@ -202,24 +181,18 @@ func (ui *Ui) AddUserMessage(msg model.UserMessage) { func (ui *Ui) createChatHistoryWindow(sid64 steamid.SID64) { _, found := ui.windows.chatHistory[sid64] - if found { - ui.windows.chatHistory[sid64].Show() - } else { - ui.windows.chatHistory[sid64] = newUserChatWindow(ui.ctx, ui.application, ui.queryUserMessagesFunc, sid64) + if !found { + ui.windows.chatHistory[sid64] = newUserChatWindow(ui.ctx, ui.application, ui.callBacks.queryUserMessagesFunc, sid64) } + ui.windows.chatHistory[sid64].Show() } func (ui *Ui) createNameHistoryWindow(sid64 steamid.SID64) { _, found := ui.windows.nameHistory[sid64] - if found { - ui.windows.nameHistory[sid64].Show() - } else { - ui.windows.nameHistory[sid64] = newUserNameWindow(ui.ctx, ui.application, ui.queryNamesFunc, sid64) + if !found { + ui.windows.nameHistory[sid64] = newUserNameWindow(ui.ctx, ui.application, ui.callBacks.queryNamesFunc, sid64) } -} - -func (ui *Ui) SetOnLaunchTF2(fn func()) { - ui.gameLauncherFunc = fn + ui.windows.nameHistory[sid64].Show() } func (ui *Ui) Start() { @@ -231,11 +204,6 @@ func (ui *Ui) OnDisconnect(sid64 steamid.SID64) { log.Printf("Player disconnected: %d", sid64.Int64()) } -func (ui *Ui) Run() { - ui.windows.player.window.Show() - ui.application.Run() -} - func showUserError(msg string, parent fyne.Window) { d := dialog.NewError(errors.New(msg), parent) d.Show()