From 1eebca289f442a496e6e8cd3bfd4a170c493138a Mon Sep 17 00:00:00 2001 From: foglar Date: Thu, 4 Sep 2025 16:03:49 +0200 Subject: [PATCH] Max Fixes and scrolling update --- .gitignore | 2 + README.md | 2 +- gomod2nix.toml | 33 +++------------ internal/tui/chat.go | 44 +++++++++++++++----- internal/tui/layout.go | 4 +- internal/tui/messages.go | 23 ----------- internal/tui/profile-sidebar.go | 32 +++++++++++++++ internal/tui/sidebar.go | 69 +++++++++++++++++++++----------- internal/tui/tui.go | 1 - pkg/render_image/render_image.go | 12 +++--- 10 files changed, 128 insertions(+), 94 deletions(-) create mode 100644 internal/tui/profile-sidebar.go diff --git a/.gitignore b/.gitignore index ae7dd17..0429e4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ vendor/ /gomod2nix-template result + +configs/servers/default/users/* diff --git a/README.md b/README.md index a192f64..f45fef6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ go run cmd/main.go ``` -- Run on nix +- Run on nix (don't work for now) ```shell nix run git+https://git.foglar.tech/foglar/WhspBrd.git diff --git a/gomod2nix.toml b/gomod2nix.toml index 06a2537..0e9dee9 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -1,30 +1,12 @@ schema = 3 [mod] - [mod."github.com/getlantern/context"] - version = "v0.0.0-20190109183933-c447772a6520" - hash = "sha256-T4v8t2Hg7lT5d69hD7189WN+dPeMxWXY3vfyiW+oQSM=" - [mod."github.com/getlantern/errors"] - version = "v0.0.0-20190325191628-abdb3e3e36f7" - hash = "sha256-AvZvWYUJtOMo7Lk7sZ0BzIougsGltlZ3fAZ6yNjkZs8=" - [mod."github.com/getlantern/golog"] - version = "v0.0.0-20190830074920-4ef2e798c2d7" - hash = "sha256-X3o4fSfl+Hb2ZIViUpIg9jpfWjMObrJ53m5E4WFwiLg=" - [mod."github.com/getlantern/hex"] - version = "v0.0.0-20190417191902-c6586a6fe0b7" - hash = "sha256-WGOCIMQrXovgp1TGheQv9GOZa/4T5xI2h2gh5Eeqayc=" - [mod."github.com/getlantern/hidden"] - version = "v0.0.0-20190325191715-f02dbb02be55" - hash = "sha256-zTYo91NllpZhrWKerxtOdqNkLm7hxpd91POHSAajKT4=" - [mod."github.com/getlantern/ops"] - version = "v0.0.0-20190325191751-d70cb0d6f85f" - hash = "sha256-2+oDnDZ1YjJc68ERVV902VAbjmGbFi6rvWtWisFjrlQ=" - [mod."github.com/getlantern/systray"] - version = "v1.2.2" - hash = "sha256-GEflgBfashORmopz8kxD7R3GRMKyF7bGE2DXr0w5nX0=" - [mod."github.com/go-stack/stack"] - version = "v1.8.0" - hash = "sha256-26RlTEcAkbewMUtmirKrDGQ1WJlNousp69v7HMopYnI=" + [mod."github.com/Endg4meZer0/go-mpris"] + version = "v1.0.5" + hash = "sha256-BT5lIuVGPfYCikmVdK8JdTHQorMgALB7a5nScv78GVc=" + [mod."github.com/godbus/dbus/v5"] + version = "v5.1.0" + hash = "sha256-xOCMJpQK3KTmHTPn/CdqI4j0eENCtMmJDgAIoYqYOEY=" [mod."github.com/jroimartin/gocui"] version = "v0.5.0" hash = "sha256-yNVYFx11d9ITkJKPNoFoGM1gIXnuJBjA4VZJXPh/zZM=" @@ -34,9 +16,6 @@ schema = 3 [mod."github.com/nsf/termbox-go"] version = "v1.1.1" hash = "sha256-Fxk9s3vKmXO3uWmpneN1iZQ+nCbUEZLWShDyeJcwhvM=" - [mod."github.com/oxtoacart/bpool"] - version = "v0.0.0-20190530202638-03653db5a59c" - hash = "sha256-Jaw3QTrj05MwADtv7lSjwMACp8s2Z/ratmxPw0t9LbM=" [mod."golang.org/x/sys"] version = "v0.30.0" hash = "sha256-BuhWtwDkciVioc03rxty6G2vcZVnPX85lI7tgQOFVP8=" diff --git a/internal/tui/chat.go b/internal/tui/chat.go index 41ea570..f39687d 100644 --- a/internal/tui/chat.go +++ b/internal/tui/chat.go @@ -1,22 +1,28 @@ package tui import ( + "encoding/base64" "fmt" "log" + "strings" + "time" "whspbrd/pkg/cell_size" + //"whspbrd/pkg/clean_image" "whspbrd/pkg/render_image" - "whspbrd/pkg/clean_image" "github.com/jroimartin/gocui" ) +var chatData ChatData +var selectedUserIdx int = 0 + func layoutChat(g *gocui.Gui, maxX, maxY int) error { if v, err := g.SetView("chat", 21, 0, maxX-1, maxY-5); err != nil { if err != gocui.ErrUnknownView { return err } - v.Title = "Chat" + v.Title = " Chat " v.Wrap = true v.Autoscroll = true updateChatView(v) @@ -29,7 +35,7 @@ func layoutInput(g *gocui.Gui, maxX, maxY int) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Type your message" + v.Title = " Type your message: " v.Editable = true v.Wrap = true if _, err := g.SetCurrentView("input"); err != nil { @@ -42,12 +48,23 @@ func layoutInput(g *gocui.Gui, maxX, maxY int) error { func updateChatView(v *gocui.View) { v.Clear() - clear := cleanimage.NewKittyImageCleaner() + //clear := cleanimage.NewKittyImageCleaner() // TODO: In future optimize this to only clear certain part of screen - fmt.Print(clear.DeleteAllVisiblePlacements(true)) + //fmt.Print(clear.DeleteAllVisiblePlacements(true)) + + for i, msg := range chatData.Messages { + decoded, err := base64.StdEncoding.DecodeString(msg.Content) + if err != nil { + log.Printf("Error decoding message: %v", err) + continue + } + if strings.HasSuffix(string(decoded), "\n") { + decoded = []byte(strings.TrimSuffix(string(decoded), "\n")) + } + + t, _ := time.Parse(time.RFC3339, msg.Timestamp) + formattedTime := t.Format("2006-01-02 15:04") - for i, msg := range messages { - fmt.Fprintf(v, "%s\n\n", msg) w, h, err := cell_size.GetTerminalCellSizePixels() if err != nil { log.Println("Error getting terminal cell size:", err) @@ -60,7 +77,16 @@ func updateChatView(v *gocui.View) { w = w*3 - (w / 10) h = 0 } - render_image.RenderImage("./configs/icon.png", i*3+2, 23, w, h, 0) + if !strings.EqualFold(msg.Sender, users[selectedUserIdx]) { + + fmt.Fprintf(v, "%s", "\t\t\t\t\t"+Colors.Text(Colors.Base02)+"You ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)+"\n\n") + render_image.RenderImage("./configs/icon.png", i*3+2, 23, w, h, false) + + } else { + fmt.Fprintf(v, "%s", "\t\t\t\t\t"+Colors.Text(Colors.Base05)+msg.Sender+" ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)+"\n\n") + icon_path := fmt.Sprintf("./configs/servers/default/users/%s/icon.png", strings.ToLower(msg.Sender)) + render_image.RenderImage(icon_path, i*3+2, 23, w, h, false) + } } } @@ -72,8 +98,6 @@ func sendMessage(g *gocui.Gui, v *gocui.View) error { v.SetOrigin(0, 0) WriteMessage(users[selectedUserIdx], "You", users[selectedUserIdx], input) - - messages = []string{} LoadMessages(users[selectedUserIdx]) updateChatView(g.Views()[1]) diff --git a/internal/tui/layout.go b/internal/tui/layout.go index cf36fc4..18cb80e 100644 --- a/internal/tui/layout.go +++ b/internal/tui/layout.go @@ -15,7 +15,7 @@ func layout(g *gocui.Gui) error { } if err := layoutSidebar(g, maxY); err != nil { - updateUsersView(g) + updateContactsView(g) return err } if err := layoutChat(g, maxX, maxY); err != nil { @@ -27,5 +27,3 @@ func layout(g *gocui.Gui) error { return nil } - - diff --git a/internal/tui/messages.go b/internal/tui/messages.go index 38f1bbc..5ad8c92 100644 --- a/internal/tui/messages.go +++ b/internal/tui/messages.go @@ -103,31 +103,8 @@ func LoadMessages(username string) { return } - var chatData ChatData if err := json.Unmarshal(data, &chatData); err != nil { log.Printf("Error parsing JSON: %v", err) return } - - for _, msg := range chatData.Messages { - decoded, err := base64.StdEncoding.DecodeString(msg.Content) - if err != nil { - log.Printf("Error decoding message: %v", err) - continue - } - if strings.HasSuffix(string(decoded), "\n") { - decoded = []byte(strings.TrimSuffix(string(decoded), "\n")) - } - - t, _ := time.Parse(time.RFC3339, msg.Timestamp) - formattedTime := t.Format("2006-01-02 15:04") - - // TODO: Move this part to the rendering chat file (./chat.go) and here i should only load all messages and related data to that - // TODO: And in the chat file i should get all data in nice structure and then just select what i need from that - if !strings.EqualFold(msg.Sender, username) { - messages = append(messages, "\t\t\t\t\t"+Colors.Text(Colors.Base02)+"You ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)) - } else { - messages = append(messages, "\t\t\t\t\t"+Colors.Text(Colors.Base05)+msg.Sender+" ("+formattedTime+"):\n"+Colors.Reset+"\t\t\t\t\t"+string(decoded)) - } - } } diff --git a/internal/tui/profile-sidebar.go b/internal/tui/profile-sidebar.go new file mode 100644 index 0000000..ad5c847 --- /dev/null +++ b/internal/tui/profile-sidebar.go @@ -0,0 +1,32 @@ +package tui + +import ( + "github.com/jroimartin/gocui" +) + +func layoutProfile(g *gocui.Gui, maxX, maxY int) error { + var VIEW_WIDTH int + if maxX-maxX/6 < 21 { + VIEW_WIDTH = 30 + } else { + VIEW_WIDTH = maxX - maxX/6 + } + if v, err := g.SetView("profile", VIEW_WIDTH, 0, maxX-1, maxY-5); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = " Profile " + v.Wrap = true + //updateProfileView(v) + } + return nil +} + +func toggleProfileView(g *gocui.Gui, v *gocui.View) error { + if _, err := g.View("profile"); err != nil { + layoutProfile(g, prevWidth, prevHeight) + } else { + g.DeleteView("profile") + } + return nil +} diff --git a/internal/tui/sidebar.go b/internal/tui/sidebar.go index 3ff5df0..48d05fa 100644 --- a/internal/tui/sidebar.go +++ b/internal/tui/sidebar.go @@ -1,12 +1,18 @@ package tui import ( + "errors" "fmt" - "github.com/jroimartin/gocui" -) + //"math" + //"strings" + //"whspbrd/pkg/cell_size" + "whspbrd/pkg/render_image" + //"whspbrd/pkg/resize_image" -var selectedUserIdx int = 0 + "github.com/jroimartin/gocui" + //"os" +) // LAYOUT func layoutSidebar(g *gocui.Gui, maxY int) error { @@ -14,14 +20,14 @@ func layoutSidebar(g *gocui.Gui, maxY int) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Users" + v.Title = " Users " v.Clear() - updateUsersView(g) + updateContactsView(g) } return nil } -func updateUsersView(g *gocui.Gui) error { +func updateContactsView(g *gocui.Gui) error { v, err := g.View("users") if err != nil { return err @@ -29,27 +35,34 @@ func updateUsersView(g *gocui.Gui) error { v.Clear() - messages = nil - // TODO: If no contacts then error, create some add contacts window or hello to WhspBrd LoadMessages(users[selectedUserIdx]) // TODO: Render profile image of users and change colors of each user maybe? - for i, u := range users { + if len(users) == 0 { + fmt.Fprintln(v, "No Contacts") + return errors.New("no contacts in the list, find some friends") + } + + _, maxY := g.Size() + h := min(len(users), (maxY / 2) - 1) + startI := max(0, min(selectedUserIdx - (h / 2), len(users) - h)) + + fmt.Fprint(v, "\n\n") + for i := startI; i < startI + h; i++ { + u := users[i] + + fmt.Fprint(v, "\t\t\t\t") + + icon_path := fmt.Sprintf("./configs/servers/default/users/%s/icon.png", u) + render_image.RenderImage(icon_path, 3 + 2 * (i - startI), 2, 30, 30, false) - // Change Selected User In The TUI Window if i == selectedUserIdx { - fmt.Fprintf(v, "%s%s%s\n", Colors.Background(Colors.Base06), u, Colors.Reset) - _, y := v.Size() - if i == 0 { - v.SetOrigin(0, 0) - } else { - v.SetOrigin(0, i-y+1) - } - + fmt.Fprintln(v, "\x1b[7m"+u+"\x1b[0m\n") } else { - fmt.Fprintln(v, u) + fmt.Fprintln(v, u+"\n") } + } return nil } @@ -59,8 +72,13 @@ func nextContact(g *gocui.Gui, v *gocui.View) error { if len(users) == 0 { return nil } - selectedUserIdx = (selectedUserIdx + 1) % len(users) - err := updateUsersView(g) + selectedUserIdx++ + if selectedUserIdx == len(users) { + selectedUserIdx = 0 + } + + err := updateContactsView(g) + updateChatView(g.Views()[1]) return err } @@ -69,8 +87,13 @@ func prevContact(g *gocui.Gui, v *gocui.View) error { if len(users) == 0 { return nil } - selectedUserIdx = (selectedUserIdx - 1 + len(users)) % len(users) - err := updateUsersView(g) + selectedUserIdx-- + if selectedUserIdx == -1 { + selectedUserIdx = len(users) - 1 + } + + err := updateContactsView(g) + updateChatView(g.Views()[1]) return err } diff --git a/internal/tui/tui.go b/internal/tui/tui.go index c279b3e..40df229 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -6,7 +6,6 @@ import ( "github.com/jroimartin/gocui" ) -var messages []string var users []string var prevWidth, prevHeight int diff --git a/pkg/render_image/render_image.go b/pkg/render_image/render_image.go index ffdeb1c..8c1550c 100644 --- a/pkg/render_image/render_image.go +++ b/pkg/render_image/render_image.go @@ -22,30 +22,30 @@ func LoadImage(filePath string) (image.Image, error) { return img, err } -func convertToRGBA(img image.Image) *image.RGBA { +func ConvertToRGBA(img image.Image) *image.RGBA { rgba := image.NewRGBA(img.Bounds()) draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src) return rgba } -func encodeImageToBase64RGBA(rgba *image.RGBA) string { +func EncodeImageToBase64RGBA(rgba *image.RGBA) string { return base64.StdEncoding.EncodeToString(rgba.Pix) } -func RenderImage(filepath string, row int, col int, width_ int, height_ int, units int) { +func RenderImage(filepath string, row int, col int, width_ int, height_ int, units bool) { img, err := LoadImage(filepath) if err != nil { fmt.Printf("Error loading image: %v\n", err) return } - rgba := convertToRGBA(img) - if units == 1 { + rgba := ConvertToRGBA(img) + if units { rgba, _ = resize_image.ResizeInTerminal(*rgba, width_, height_) } else { rgba, _ = resize_image.Resize(*rgba, width_, height_) } - encoded := encodeImageToBase64RGBA(rgba) + encoded := EncodeImageToBase64RGBA(rgba) width := rgba.Rect.Dx() height := rgba.Rect.Dy()