Minor fixes
parent
4a615e02bb
commit
d8f68cb852
2
go.mod
2
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue