1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-12-22 12:16:46 +00:00

Slight Gtk fixes and improvements

This commit uses the GtkFileChooserNative over Gtk's own. It also
slightly refactors the message container API and fixes minor UI
appearances, especially adding the separator.
This commit is contained in:
diamondburned 2020-11-05 11:08:30 -08:00
parent cf4f8ce245
commit f5ba082b86
13 changed files with 114 additions and 79 deletions

2
go.mod
View file

@ -2,7 +2,7 @@ module github.com/diamondburned/cchat-gtk
go 1.14
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200816224505-3cd69b83a48a
replace github.com/gotk3/gotk3 => github.com/gotk3/gotk3 v0.5.1-0.20201028052159-952547abf55a
require (
github.com/Xuanwo/go-locale v1.0.0

2
go.sum
View file

@ -234,6 +234,8 @@ github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotk3/gotk3 v0.5.1-0.20201028052159-952547abf55a h1:9O8VeGmNRqh8UPYLfjYc+W3Gu7vSVTo2uEswq4FO9xI=
github.com/gotk3/gotk3 v0.5.1-0.20201028052159-952547abf55a/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=

View file

@ -226,11 +226,10 @@ func RenderPixbuf(img image.Image) *gdk.Pixbuf {
}
func SpawnUploader(dirpath string, callback func(absolutePaths []string)) {
dialog, _ := gtk.FileChooserDialogNewWith2Buttons(
dialog, _ := gtk.FileChooserNativeDialogNew(
"Upload File", App.Window,
gtk.FILE_CHOOSER_ACTION_OPEN,
"Cancel", gtk.RESPONSE_CANCEL,
"Upload", gtk.RESPONSE_ACCEPT,
"Upload", "Cancel",
)
App.Throttler.Connect(dialog)
@ -248,9 +247,7 @@ func SpawnUploader(dirpath string, callback func(absolutePaths []string)) {
dialog.SetCurrentFolder(dirpath)
dialog.SetSelectMultiple(true)
defer dialog.Close()
if res := dialog.Run(); res != gtk.RESPONSE_ACCEPT {
if res := dialog.Run(); res != int(gtk.RESPONSE_ACCEPT) {
return
}
@ -259,12 +256,12 @@ func SpawnUploader(dirpath string, callback func(absolutePaths []string)) {
}
// BindPreviewer binds the file chooser dialog with a previewer.
func BindPreviewer(fc *gtk.FileChooserDialog) {
func BindPreviewer(fc *gtk.FileChooserNativeDialog) {
img, _ := gtk.ImageNew()
fc.SetPreviewWidget(img)
fc.Connect("update-preview",
func(fc *gtk.FileChooserDialog, img *gtk.Image) {
func(_ interface{}, img *gtk.Image) {
file := fc.GetPreviewFilename()
b, err := gdk.PixbufNewFromFileAtScale(file, 256, 256, true)

View file

@ -16,7 +16,6 @@ type State struct {
}
type Connector interface {
gtk.IWidget
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
}

View file

@ -2,6 +2,7 @@ package compact
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
@ -17,6 +18,18 @@ func NewContainer(ctrl container.Controller) *Container {
return &Container{c}
}
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
gts.ExecAsync(func() { c.GridContainer.CreateMessageUnsafe(msg) })
}
func (c *Container) UpdateMessage(msg cchat.MessageUpdate) {
gts.ExecAsync(func() { c.GridContainer.UpdateMessageUnsafe(msg) })
}
func (c *Container) DeleteMessage(msg cchat.MessageDelete) {
gts.ExecAsync(func() { c.GridContainer.DeleteMessageUnsafe(msg) })
}
type constructor struct{}
func (constructor) NewMessage(msg cchat.MessageCreate) container.GridMessage {

View file

@ -2,7 +2,6 @@ package container
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
@ -34,9 +33,12 @@ type Container interface {
gtk.IWidget
// Thread-safe methods.
cchat.MessagesContainer
// cchat.MessagesContainer
// Thread-unsafe methods.
// CreateMessageUnsafe creates a new message and returns the index that is
// the location the message is added to.
CreateMessageUnsafe(cchat.MessageCreate)
UpdateMessageUnsafe(cchat.MessageUpdate)
DeleteMessageUnsafe(cchat.MessageDelete)
@ -111,15 +113,3 @@ func (c *GridContainer) CreateMessageUnsafe(msg cchat.MessageCreate) {
c.DeleteEarliest(c.MessagesLen() - BacklogLimit)
}
}
func (c *GridContainer) CreateMessage(msg cchat.MessageCreate) {
gts.ExecAsync(func() { c.CreateMessageUnsafe(msg) })
}
func (c *GridContainer) UpdateMessage(msg cchat.MessageUpdate) {
gts.ExecAsync(func() { c.UpdateMessageUnsafe(msg) })
}
func (c *GridContainer) DeleteMessage(msg cchat.MessageDelete) {
gts.ExecAsync(func() { c.DeleteMessageUnsafe(msg) })
}

View file

@ -121,7 +121,8 @@ func (c *Container) reuseAvatar(authorID, avatarURL string, full *FullMessage) {
}
func (c *Container) lastMessageIsAuthor(id string, offset int) bool {
var last = c.GridStore.NthMessage(c.GridStore.MessagesLen() - (1 + offset))
// Get the offfsetth message from last.
var last = c.GridStore.NthMessage((c.GridStore.MessagesLen() - 1) + offset)
return last != nil && last.AuthorID() == id
}
@ -131,33 +132,42 @@ func (c *Container) CreateMessage(msg cchat.MessageCreate) {
// wipe old messages.
c.GridContainer.CreateMessageUnsafe(msg)
// Should we collapse this message? Yes, if the current message's author
// is the same as the last author.
if c.lastMessageIsAuthor(msg.Author().ID(), 1) {
c.compact(c.GridContainer.LastMessage())
}
// See if we need to collapse the second message.
if sec := c.NthMessage(1); sec != nil {
// If the author isn't the same, then ignore.
if sec.AuthorID() != msg.Author().ID() {
return
}
// The author is the same; collapse.
c.compact(sec)
}
// Did the handler wipe old messages? It will only do so if the user is
// scrolled to the bottom.
if !c.Bottomed() {
// If we're not at the bottom, then we exit.
return
if c.Bottomed() {
// We need to uncollapse the first (top) message. No length check is
// needed here, as we just inserted a message.
c.uncompact(c.FirstMessage())
}
// We need to uncollapse the first (top) message. No length check is
// needed here, as we just inserted a message.
c.uncompact(c.FirstMessage())
switch msg.ID() {
// Should we collapse this message? Yes, if the current message is
// inserted at the end and its author is the same as the last author.
case c.GridContainer.LastMessage().ID():
if c.lastMessageIsAuthor(msg.Author().ID(), -1) {
c.compact(c.GridContainer.LastMessage())
}
// If we've prepended the message, then see if we need to collapse the
// second message.
case c.GridContainer.FirstMessage().ID():
if sec := c.NthMessage(1); sec != nil {
// If the author isn't the same, then ignore.
if sec.AuthorID() != msg.Author().ID() {
return
}
// The author is the same; collapse.
c.compact(sec)
}
}
})
}
func (c *Container) UpdateMessage(msg cchat.MessageUpdate) {
gts.ExecAsync(func() {
c.UpdateMessageUnsafe(msg)
})
}

View file

@ -208,12 +208,20 @@ func (c *GridStore) NthMessage(n int) GridMessage {
// FirstMessage returns the first message.
func (c *GridStore) FirstMessage() GridMessage {
return c.NthMessage(0)
if c.messageList.Len() == 0 {
return nil
}
// Long unwrap.
return c.messageList.Front().Value.(*gridMessage).GridMessage
}
// LastMessage returns the latest message.
func (c *GridStore) LastMessage() GridMessage {
return c.NthMessage(c.MessagesLen() - 1)
if c.messageList.Len() == 0 {
return nil
}
// Long unwrap.
return c.messageList.Back().Value.(*gridMessage).GridMessage
}
// Message finds the message state in the container. It is not thread-safe. This
@ -273,17 +281,21 @@ func (c *GridStore) AddPresendMessage(msg input.PresendMessage) PresendGridMessa
return presend
}
// Many attempts were made to have CreateMessageUnsafe return an index. That is
// unreliable. The index might be off if the message buffer is cleaned up. Don't
// rely on it.
func (c *GridStore) CreateMessageUnsafe(msg cchat.MessageCreate) {
// Call the event handler last.
defer c.Controller.AuthorEvent(msg.Author())
// Attempt to update before insertion (aka upsert).
if msgc := c.Message(msg.ID(), msg.Nonce()); msgc != nil {
if msgc := c.message(msg.ID(), msg.Nonce()); msgc != nil {
msgc.UpdateAuthor(msg.Author())
msgc.UpdateContent(msg.Content(), false)
msgc.UpdateTimestamp(msg.Time())
c.Controller.BindMenu(msgc)
c.Controller.BindMenu(msgc.GridMessage)
return
}

View file

@ -241,7 +241,7 @@ func (f *Field) SetMessenger(session cchat.Session, messenger cchat.Messenger) {
f.typing = f.Messenger.AsTypingIndicator()
// See if we can upload files.
f.SetAllowUpload(f.Sender.CanAttach())
f.SetAllowUpload(f.Sender != nil && f.Sender.CanAttach())
// Populate the duration state if typer is not nil.
if f.typing != nil {

View file

@ -52,6 +52,12 @@ type Controller interface {
OnMessageDone()
}
type MessagesContainer interface {
gtk.IWidget
container.Container
cchat.MessagesContainer
}
type View struct {
*gtk.Box
@ -66,7 +72,7 @@ type View struct {
MsgBox *gtk.Box
Typing *typing.Container
Container container.Container
Container MessagesContainer
contType int // msgIndex
MemberList *memberlist.Container // right box

View file

@ -2,7 +2,6 @@ package primitives
import (
"runtime/debug"
"time"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
@ -279,24 +278,7 @@ func InlineCSS(ctx StyleContexter, css string) {
// LeafletOnFold binds a callback to a leaflet that would be called when the
// leaflet's folded state changes.
func LeafletOnFold(leaflet *handy.Leaflet, foldedFn func(folded bool)) {
var lastFold = leaflet.GetFolded()
foldedFn(lastFold)
// Give each callback a 500ms wait for animations to complete.
const dt = 500 * time.Millisecond
var last = time.Now()
leaflet.ConnectAfter("size-allocate", func() {
// Ignore if this event is too recent.
if now := time.Now(); now.Add(-dt).Before(last) {
return
} else {
last = now
}
if folded := leaflet.GetFolded(); folded != lastFold {
lastFold = folded
foldedFn(folded)
}
leaflet.ConnectAfter("notify::folded", func() {
foldedFn(leaflet.GetFolded())
})
}

View file

@ -15,6 +15,7 @@ import (
"github.com/diamondburned/handy"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"
)
@ -83,10 +84,19 @@ func NewApplication() *App {
app.HeaderGroup.AddHeaderBar(&app.Services.Header.HeaderBar)
app.HeaderGroup.AddHeaderBar(&app.MessageView.Header.HeaderBar)
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
separator.Show()
app.Leaflet = *handy.LeafletNew()
app.Leaflet.SetTransitionType(handy.LeafletTransitionTypeUnder)
app.Leaflet.SetChildTransitionDuration(75)
app.Leaflet.SetTransitionType(handy.LeafletTransitionTypeSlide)
app.Leaflet.SetCanSwipeBack(true)
app.Leaflet.Add(app.Services)
app.Leaflet.Add(separator)
app.Leaflet.Add(app.MessageView)
app.Leaflet.ChildSetProperty(separator, "navigatable", false)
app.Leaflet.Show()
// Bind the preferences action for our GAction button in the header popover.

View file

@ -1,12 +1,26 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation rec {
let libhandy = pkgs.libhandy.overrideAttrs(old: {
name = "libhandy-1.0.1";
src = builtins.fetchGit {
url = "https://gitlab.gnome.org/GNOME/libhandy.git";
rev = "5cee0927b8b39dea1b2a62ec6d19169f73ba06c6";
};
patches = [];
buildInputs = old.buildInputs ++ (with pkgs; [
gnome3.librsvg
gdk-pixbuf
]);
});
in pkgs.stdenv.mkDerivation rec {
name = "cchat-gtk";
version = "0.0.2";
buildInputs = with pkgs; [
libhandy gnome3.gspell gnome3.glib gnome3.gtk
];
buildInputs = [ libhandy ] ++ (with pkgs; [
gnome3.gspell gnome3.glib gnome3.gtk
]);
nativeBuildInputs = with pkgs; [
pkgconfig go