Minor fixes

This commit is contained in:
diamondburned 2021-05-04 20:30:50 -07:00
parent 4a615e02bb
commit d8f68cb852
17 changed files with 138 additions and 52 deletions

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/Xuanwo/go-locale v1.0.0
github.com/alecthomas/chroma v0.7.3
github.com/diamondburned/cchat v0.6.4
github.com/diamondburned/cchat-discord v0.0.0-20210501072434-cc2b2ee4c799
github.com/diamondburned/cchat-discord v0.0.0-20210501221918-71c3069fa46f
github.com/diamondburned/gspell v0.0.0-20201229064336-e43698fd5828
github.com/diamondburned/handy v0.0.0-20210329054445-387ad28eb2c2
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972

2
go.sum
View File

@ -147,6 +147,8 @@ github.com/diamondburned/cchat-discord v0.0.0-20210326063953-deb4ccb32bff h1:p5X
github.com/diamondburned/cchat-discord v0.0.0-20210326063953-deb4ccb32bff/go.mod h1:zbm+BpkQOMD6s87x4FrP3lTt9ddJLWTTPXyMROT+LZs=
github.com/diamondburned/cchat-discord v0.0.0-20210501072434-cc2b2ee4c799 h1:xxqeuAx0T9SsS8DYKe4jxzL2saEpLyQeAttD0sX/g1E=
github.com/diamondburned/cchat-discord v0.0.0-20210501072434-cc2b2ee4c799/go.mod h1:zbm+BpkQOMD6s87x4FrP3lTt9ddJLWTTPXyMROT+LZs=
github.com/diamondburned/cchat-discord v0.0.0-20210501221918-71c3069fa46f h1:IDC3qToEm5owHf5FlJY9q9Kjbsv45+nly4I2YMv76lE=
github.com/diamondburned/cchat-discord v0.0.0-20210501221918-71c3069fa46f/go.mod h1:zbm+BpkQOMD6s87x4FrP3lTt9ddJLWTTPXyMROT+LZs=
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2PdbsdsRJ7d669kp35GbCUO44KZ0Xfqdu4o/oqVg=
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc=
github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw=

View File

@ -1,6 +1,7 @@
package gts
import (
"context"
"io"
"os"
"time"
@ -154,6 +155,7 @@ func Main(wfn func() MainApplication) {
// Async runs fn asynchronously, then runs the function it returns in the Gtk
// main thread.
// TODO: deprecate Async.
func Async(fn func() (func(), error)) {
go func() {
f, err := fn()
@ -168,27 +170,66 @@ func Async(fn func() (func(), error)) {
}()
}
// AsyncCancel is similar to AsyncCtx, but the context is created internally.
func AsyncCancel(fn func(ctx context.Context) (func(), error)) context.CancelFunc {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// fn() is assumed to use the same given ctx.
f, err := fn(ctx)
if err != nil {
log.Error(err)
}
// Attempt to run the callback if it's there.
if f != nil {
ExecAsyncCtx(ctx, f)
}
}()
return cancel
}
// AsyncCtx does what Async does, except the returned callback will not be
// executed if the given context has expired or the returned callback is called.
func AsyncCtx(ctx context.Context, fn func() (func(), error)) {
go func() {
// fn() is assumed to use the same given ctx.
f, err := fn()
if err != nil {
log.Error(err)
}
// Attempt to run the callback if it's there.
if f != nil {
ExecAsyncCtx(ctx, f)
}
}()
}
// ExecLater executes the function asynchronously with a low priority.
func ExecLater(fn func()) {
glib.IdleAddPriority(glib.PRIORITY_DEFAULT_IDLE, fn)
}
// ExecAsync executes function asynchronously in the Gtk main thread.
// TODO: deprecate Async.
func ExecAsync(fn func()) {
glib.IdleAddPriority(glib.PRIORITY_HIGH, fn)
}
// ExecSync executes the function asynchronously, but returns a channel that
// indicates when the job is done.
func ExecSync(fn func()) <-chan struct{} {
var ch = make(chan struct{})
// ExecAsyncCtx executes the function asynchronously in the Gtk main thread only
// if the context has not expired. This API has absolutely no race conditions if
// the context is only canceled in the main thread.
func ExecAsyncCtx(ctx context.Context, fn func()) {
ExecAsync(func() {
select {
case <-ctx.Done():
glib.IdleAddPriority(glib.PRIORITY_HIGH, func() {
fn()
close(ch)
default:
fn()
}
})
return ch
}
// DoAfter calls f after the given duration in the Gtk main loop.

View File

@ -21,8 +21,8 @@ type truncator struct {
var shortTruncators = []truncator{
{d: Day, s: "15:04"},
{d: Week, s: "Mon 15:04"},
{d: Year, s: "15:04 02/01"},
{d: -1, s: "15:04 02/01/2006"},
{d: Year, s: "02/01 15:04"},
{d: -1, s: "02/01/2006 15:04"},
}
func TimeAgo(t time.Time) string {

View File

@ -110,3 +110,16 @@ var toRestore = map[string]interface{}{}
func RegisterConfig(filename string, jsonValue interface{}) {
toRestore[filename] = jsonValue
}
// Updaters contains a list of callbacks to be called when something is updated.
type Updaters []func()
func (us *Updaters) Add(f func()) {
*us = append(*us, f)
}
func (us *Updaters) Updated() {
for _, f := range *us {
f()
}
}

View File

@ -6,10 +6,29 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/gotk3/gotk3/gtk"
)
type Container struct {
*container.ListContainer
sg SizeGroups
}
type SizeGroups struct {
Timestamp *gtk.SizeGroup
Username *gtk.SizeGroup
}
func NewSizeGroups() SizeGroups {
sg1, _ := gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL)
sg2, _ := gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL)
return SizeGroups{sg1, sg2}
}
func (sgs *SizeGroups) Add(msg Message) {
sgs.Timestamp.AddWidget(msg.Timestamp)
sgs.Username.AddWidget(msg.Username)
}
var _ container.Container = (*Container)(nil)
@ -17,11 +36,12 @@ var _ container.Container = (*Container)(nil)
func NewContainer(ctrl container.Controller) *Container {
c := container.NewListContainer(ctrl)
primitives.AddClass(c, "compact-container")
return &Container{c}
return &Container{c, NewSizeGroups()}
}
func (c *Container) NewPresendMessage(state *message.PresendState) container.PresendMessageRow {
msg := WrapPresendMessage(state)
c.sg.Add(msg.Message)
c.addMessage(msg)
return msg
}
@ -29,6 +49,7 @@ func (c *Container) NewPresendMessage(state *message.PresendState) container.Pre
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
gts.ExecAsync(func() {
msg := WrapMessage(message.NewState(msg))
c.sg.Add(msg)
c.addMessage(msg)
c.CleanMessages()
})

View File

@ -16,8 +16,7 @@ import (
var messageTimeCSS = primitives.PrepareClassCSS("", `
.message-time {
margin-left: 1em;
margin-right: 1em;
margin: 0 8px;
}
`)
@ -52,13 +51,18 @@ var _ container.MessageRow = (*Message)(nil)
func WrapMessage(ct *message.State) Message {
ts := message.NewTimestamp()
ts.SetVAlign(gtk.ALIGN_START)
ts.SetHAlign(gtk.ALIGN_END)
ts.SetXAlign(1.00)
ts.SetText(humanize.TimeAgo(ct.Time))
ts.SetTooltipText(ct.Time.Format(time.Stamp))
ts.Show()
messageTimeCSS(ts)
user := message.NewUsername()
user.SetMaxWidthChars(25)
user.SetMaxWidthChars(22)
user.SetHAlign(gtk.ALIGN_END)
user.SetXAlign(1.0)
user.SetJustify(gtk.JUSTIFY_RIGHT)
user.SetEllipsize(pango.ELLIPSIZE_NONE)
user.SetLineWrap(true)
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)

View File

@ -10,10 +10,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
)
const (
AvatarSize = 40
AvatarMargin = 10
)
const AvatarSize = message.AvatarSize
// NewMessage creates a new message.
func NewMessage(
@ -47,7 +44,7 @@ func NewContainer(ctrl container.Controller) *Container {
return &Container{ListContainer: c}
}
const splitDuration = 10 * time.Minute
const splitDuration = 3 * time.Minute
// isCollapsible returns true if the given lastMsg has matching conditions with
// the given msg.

View File

@ -3,7 +3,6 @@ package cozy
import (
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"github.com/gotk3/gotk3/gtk"
)
// Collapsed is a message that follows after FullMessage. It does not show
@ -11,36 +10,26 @@ import (
type CollapsedMessage struct {
// Author is still updated normally.
*message.State
Timestamp *gtk.Label
}
// WrapCollapsedMessage wraps the given message state to be a collapsed message.
func WrapCollapsedMessage(gc *message.State) *CollapsedMessage {
// Set Timestamp's padding accordingly to Avatar's.
ts := message.NewTimestamp()
ts.SetSizeRequest(AvatarSize, -1)
ts.SetVAlign(gtk.ALIGN_START)
ts.SetXAlign(0.5) // middle align
ts.SetMarginEnd(container.ColumnSpacing)
ts.SetMarginStart(container.ColumnSpacing * 2)
// Set Content's padding accordingly to FullMessage's main box.
gc.Content.SetMarginEnd(container.ColumnSpacing * 2)
gc.Content.SetMarginStart(container.ColumnSpacing*2 + AvatarSize)
gc.Content.SetMarginEnd(container.ColumnSpacing)
gc.PackStart(ts, false, false, 0)
gc.PackStart(gc.Content, true, true, 0)
gc.SetClass("cozy-collapsed")
return &CollapsedMessage{
State: gc,
Timestamp: ts,
State: gc,
}
}
func (c *CollapsedMessage) Revert() *message.State {
c.ClearBox()
c.Content.SetMarginStart(0)
c.Content.SetMarginEnd(0)
c.Timestamp.Destroy()
return c.Unwrap()
}

View File

@ -61,7 +61,7 @@ func WrapFullMessage(gc *message.State) *FullMessage {
header.Show()
avatar := NewAvatar(gc.Row)
avatar.SetMarginStart(container.ColumnSpacing * 2)
avatar.SetMarginStart(container.ColumnSpacing)
avatar.Connect("clicked", func(w gtk.IWidget) {
if output := header.Output(); len(output.Mentions) > 0 {
labeluri.PopoverMentioner(w, output.Input, output.Mentions[0])
@ -78,12 +78,10 @@ func WrapFullMessage(gc *message.State) *FullMessage {
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
main.PackStart(header, false, false, 0)
main.PackStart(gc.Content, false, false, 0)
main.SetMarginEnd(container.ColumnSpacing * 2)
main.SetMarginEnd(container.ColumnSpacing)
main.SetMarginStart(container.ColumnSpacing)
main.Show()
// Also attach a class for the main box shown on the right.
primitives.AddClass(main, "cozy-main")
mainCSS(main)
gc.PackStart(avatar, false, false, 0)
gc.PackStart(main, true, true, 0)

View File

@ -479,6 +479,7 @@ func (c *ListStore) Highlight(msg MessageRow) {
}
func destroyMsg(row *messageRow) {
row.Revert()
row.state.Author.Name.Stop()
row.state.Row.Destroy()
}

View File

@ -14,14 +14,16 @@ import (
const AvatarSize = 24
var showUser = true
var currentRevealer = func(bool) {} // noop by default
var (
showUser = true
updaters config.Updaters
)
func init() {
// Bind this revealer in settings.
config.AppearanceAdd("Show Username in Input", config.Switch(
&showUser,
func(b bool) { currentRevealer(b) },
func(b bool) { updaters.Updated() },
))
}
@ -55,7 +57,7 @@ func NewContainer() *Container {
// Bind the current global revealer to this revealer for settings. This
// operation should be thread-safe, as everything is being done in the main
// thread.
currentRevealer = rev.SetRevealChild
updaters.Add(func() { rev.SetRevealChild(showUser) })
author := message.NewCustomAuthor("", text.Plain("self"))
@ -68,6 +70,7 @@ func NewContainer() *Container {
u.avatar = roundimage.NewImage(0)
u.avatar.SetSize(AvatarSize)
u.avatar.SetHAlign(gtk.ALIGN_CENTER)
u.avatar.SetPlaceholderIcon("user-available-symbolic", AvatarSize)
u.avatar.Show()

View File

@ -15,6 +15,8 @@ import (
"github.com/gotk3/gotk3/pango"
)
const AvatarSize = 40
// Container describes a message container that wraps a state. These methods are
// made for containers to override; methods not meant to be override are not
// exposed and will be done directly on the State.
@ -70,6 +72,7 @@ func NewState(msg cchat.MessageCreate) *State {
// immediately afterwards; it is invalid once the state is used.
func NewEmptyState() *State {
ctbody := labeluri.NewLabel(text.Rich{})
ctbody.Tooltip = false
ctbody.SetHAlign(gtk.ALIGN_FILL)
ctbody.SetEllipsize(pango.ELLIPSIZE_NONE)
ctbody.SetLineWrap(true)

View File

@ -24,6 +24,8 @@ func RenderSkipImages(rich text.Rich) markup.RenderOutput {
// need to manually
type Label struct {
gtk.Label
Tooltip bool
label text.Rich
output markup.RenderOutput
render LabelRenderer
@ -41,7 +43,7 @@ func NewStaticLabel(rich text.Rich) *Label {
label.SetMarkup(markup.Render(rich))
}
return &Label{Label: *label}
return &Label{Label: *label, Tooltip: true}
}
// NewLabel creates a self-updating label.
@ -83,7 +85,10 @@ func (l *Label) SetLabel(content text.Rich) {
l.output = out
l.SetMarkup(out.Markup)
l.SetTooltipMarkup(out.Markup)
if l.Tooltip {
l.SetTooltipMarkup(out.Markup)
}
}
// SetRenderer sets a custom renderer. If the given renderer is nil, then the

View File

@ -59,6 +59,7 @@ type ServerRow struct {
mentioned bool
showLabel bool
UnreadIndicator cchat.UnreadIndicator
// callback to cancel unread indicator
cancelUnread func()
}
@ -96,9 +97,10 @@ func NewHollowServer(p traverse.Breadcrumber, sv cchat.Server, ctrl ParentContro
serverRow.children.SetUnreadHandler(serverRow.SetUnreadUnsafe)
case messenger != nil:
if unreader := messenger.AsUnreadIndicator(); unreader != nil {
serverRow.UnreadIndicator = messenger.AsUnreadIndicator()
if serverRow.UnreadIndicator != nil {
gts.Async(func() (func(), error) {
c, err := unreader.UnreadIndicate(&serverRow)
c, err := serverRow.UnreadIndicator.UnreadIndicate(&serverRow)
if err != nil {
return nil, errors.Wrap(err, "Failed to use unread indicator")
}

View File

@ -181,8 +181,10 @@ func (s *Servers) setDone() {
s.SetVisibleChild(s.Main)
// stop the spinner.
s.spinner.Destroy()
s.spinner = nil
if s.spinner != nil {
s.spinner.Destroy()
s.spinner = nil
}
}
// setLoading shows a loading spinner. Use this after the session row is

View File

@ -20,7 +20,12 @@ undershoot { background-size: 0 }
*/
.top-level .server-list.expanded {
background-color: @borders;
background-color: @theme_bg_color;
}
.top-level .server-list.expanded > .server-button,
.top-level .server-list.expanded > revealer > .server-children {
background-color: mix(alpha(@theme_selected_bg_color, 0.5), @borders, 0.25);
}
.top-level .server-button {