From eda2b575f06e72040ebf82d24a7ec1ac84b7948c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 9 Apr 2018 23:45:54 +0300 Subject: Refactor UI to use interfaces everywhere --- ui/messages/doc.go | 2 + ui/messages/message.go | 29 +++++ ui/messages/meta.go | 70 +++++++++++ ui/messages/textmessage.go | 295 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 396 insertions(+) create mode 100644 ui/messages/doc.go create mode 100644 ui/messages/message.go create mode 100644 ui/messages/meta.go create mode 100644 ui/messages/textmessage.go (limited to 'ui/messages') diff --git a/ui/messages/doc.go b/ui/messages/doc.go new file mode 100644 index 0000000..7c3c077 --- /dev/null +++ b/ui/messages/doc.go @@ -0,0 +1,2 @@ +// Package types contains common type definitions used by the UI. +package messages diff --git a/ui/messages/message.go b/ui/messages/message.go new file mode 100644 index 0000000..ef0966c --- /dev/null +++ b/ui/messages/message.go @@ -0,0 +1,29 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package messages + +import "maunium.net/go/gomuks/interface" + +// Message is a wrapper for the content and metadata of a Matrix message intended to be displayed. +type UIMessage interface { + ifc.Message + + CalculateBuffer(width int) + RecalculateBuffer() + Buffer() []string + Height() int +} diff --git a/ui/messages/meta.go b/ui/messages/meta.go new file mode 100644 index 0000000..8cb6c1b --- /dev/null +++ b/ui/messages/meta.go @@ -0,0 +1,70 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package messages + +import ( + "github.com/gdamore/tcell" + "maunium.net/go/gomuks/interface" +) + +// BasicMeta is a simple variable store implementation of MessageMeta. +type BasicMeta struct { + BSender, BTimestamp, BDate string + BSenderColor, BTextColor, BTimestampColor tcell.Color +} + +// Sender gets the string that should be displayed as the sender of this message. +func (meta *BasicMeta) Sender() string { + return meta.BSender +} + +// SenderColor returns the color the name of the sender should be shown in. +func (meta *BasicMeta) SenderColor() tcell.Color { + return meta.BSenderColor +} + +// Timestamp returns the formatted time when the message was sent. +func (meta *BasicMeta) Timestamp() string { + return meta.BTimestamp +} + +// Date returns the formatted date when the message was sent. +func (meta *BasicMeta) Date() string { + return meta.BDate +} + +// TextColor returns the color the actual content of the message should be shown in. +func (meta *BasicMeta) TextColor() tcell.Color { + return meta.BTextColor +} + +// TimestampColor returns the color the timestamp should be shown in. +// +// This usually does not apply to the date, as it is rendered separately from the message. +func (meta *BasicMeta) TimestampColor() tcell.Color { + return meta.BTimestampColor +} + +// CopyFrom replaces the content of this meta object with the content of the given object. +func (meta *BasicMeta) CopyFrom(from ifc.MessageMeta) { + meta.BSender = from.Sender() + meta.BTimestamp = from.Timestamp() + meta.BDate = from.Date() + meta.BSenderColor = from.SenderColor() + meta.BTextColor = from.TextColor() + meta.BTimestampColor = from.TimestampColor() +} diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go new file mode 100644 index 0000000..1f37418 --- /dev/null +++ b/ui/messages/textmessage.go @@ -0,0 +1,295 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package messages + +import ( + "encoding/gob" + "fmt" + "regexp" + "strings" + + "github.com/gdamore/tcell" + "github.com/mattn/go-runewidth" + "maunium.net/go/gomuks/interface" +) + +func init() { + gob.Register(&UITextMessage{}) +} + +type UITextMessage struct { + MsgID string + MsgType string + MsgSender string + MsgSenderColor tcell.Color + MsgTimestamp string + MsgDate string + MsgText string + MsgState ifc.MessageState + MsgIsHighlight bool + MsgIsService bool + buffer []string + prevBufferWidth int +} + +// NewMessage creates a new Message object with the provided values and the default state. +func NewMessage(id, sender, msgtype, text, timestamp, date string, senderColor tcell.Color) UIMessage { + return &UITextMessage{ + MsgSender: sender, + MsgTimestamp: timestamp, + MsgDate: date, + MsgSenderColor: senderColor, + MsgType: msgtype, + MsgText: text, + MsgID: id, + prevBufferWidth: 0, + MsgState: ifc.MessageStateDefault, + MsgIsHighlight: false, + MsgIsService: false, + } +} + +// CopyFrom replaces the content of this message object with the content of the given object. +func (msg *UITextMessage) CopyFrom(from ifc.MessageMeta) { + msg.MsgSender = from.Sender() + msg.MsgTimestamp = from.Timestamp() + msg.MsgDate = from.Date() + msg.MsgSenderColor = from.SenderColor() + + fromMsg, ok := from.(UIMessage) + if ok { + msg.MsgID = fromMsg.ID() + msg.MsgType = fromMsg.Type() + msg.MsgText = fromMsg.Text() + msg.MsgState = fromMsg.State() + msg.MsgIsService = fromMsg.IsService() + msg.MsgIsHighlight = fromMsg.IsHighlight() + + msg.RecalculateBuffer() + } +} + +// Sender gets the string that should be displayed as the sender of this message. +// +// If the message is being sent, the sender is "Sending...". +// If sending has failed, the sender is "Error". +// If the message is an emote, the sender is blank. +// In any other case, the sender is the display name of the user who sent the message. +func (msg *UITextMessage) Sender() string { + switch msg.MsgState { + case ifc.MessageStateSending: + return "Sending..." + case ifc.MessageStateFailed: + return "Error" + } + switch msg.MsgType { + case "m.emote": + // Emotes don't show a separate sender, it's included in the buffer. + return "" + default: + return msg.MsgSender + } +} + +func (msg *UITextMessage) getStateSpecificColor() tcell.Color { + switch msg.MsgState { + case ifc.MessageStateSending: + return tcell.ColorGray + case ifc.MessageStateFailed: + return tcell.ColorRed + case ifc.MessageStateDefault: + fallthrough + default: + return tcell.ColorDefault + } +} + +// SenderColor returns the color the name of the sender should be shown in. +// +// If the message is being sent, the color is gray. +// If sending has failed, the color is red. +// +// In any other case, the color is whatever is specified in the Message struct. +// Usually that means it is the hash-based color of the sender (see ui/widget/color.go) +func (msg *UITextMessage) SenderColor() tcell.Color { + stateColor := msg.getStateSpecificColor() + switch { + case stateColor != tcell.ColorDefault: + return stateColor + case msg.MsgIsService: + return tcell.ColorGray + default: + return msg.MsgSenderColor + } +} + +// TextColor returns the color the actual content of the message should be shown in. +func (msg *UITextMessage) TextColor() tcell.Color { + stateColor := msg.getStateSpecificColor() + switch { + case stateColor != tcell.ColorDefault: + return stateColor + case msg.MsgIsService: + return tcell.ColorGray + case msg.MsgIsHighlight: + return tcell.ColorYellow + case msg.MsgType == "m.room.member": + return tcell.ColorGreen + default: + return tcell.ColorDefault + } +} + +// TimestampColor returns the color the timestamp should be shown in. +// +// As with SenderColor(), messages being sent and messages that failed to be sent are +// gray and red respectively. +// +// However, other messages are the default color instead of a color stored in the struct. +func (msg *UITextMessage) TimestampColor() tcell.Color { + return msg.getStateSpecificColor() +} + +// RecalculateBuffer calculates the buffer again with the previously provided width. +func (msg *UITextMessage) RecalculateBuffer() { + msg.CalculateBuffer(msg.prevBufferWidth) +} + +// Buffer returns the computed text buffer. +// +// The buffer contains the text of the message split into lines with a maximum +// width of whatever was provided to CalculateBuffer(). +// +// N.B. This will NOT automatically calculate the buffer if it hasn't been +// calculated already, as that requires the target width. +func (msg *UITextMessage) Buffer() []string { + return msg.buffer +} + +// Height returns the number of rows in the computed buffer (see Buffer()). +func (msg *UITextMessage) Height() int { + return len(msg.buffer) +} + +// Timestamp returns the formatted time when the message was sent. +func (msg *UITextMessage) Timestamp() string { + return msg.MsgTimestamp +} + +// Date returns the formatted date when the message was sent. +func (msg *UITextMessage) Date() string { + return msg.MsgDate +} + +func (msg *UITextMessage) ID() string { + return msg.MsgID +} + +func (msg *UITextMessage) SetID(id string) { + msg.MsgID = id +} + +func (msg *UITextMessage) Type() string { + return msg.MsgType +} + +func (msg *UITextMessage) SetType(msgtype string) { + msg.MsgType = msgtype +} + +func (msg *UITextMessage) Text() string { + return msg.MsgText +} + +func (msg *UITextMessage) SetText(text string) { + msg.MsgText = text +} + +func (msg *UITextMessage) State() ifc.MessageState { + return msg.MsgState +} + +func (msg *UITextMessage) SetState(state ifc.MessageState) { + msg.MsgState = state +} + +func (msg *UITextMessage) IsHighlight() bool { + return msg.MsgIsHighlight +} + +func (msg *UITextMessage) SetIsHighlight(isHighlight bool) { + msg.MsgIsHighlight = isHighlight +} + +func (msg *UITextMessage) IsService() bool { + return msg.MsgIsService +} + +func (msg *UITextMessage) SetIsService(isService bool) { + msg.MsgIsService = isService +} + +// Regular expressions used to split lines when calculating the buffer. +// +// From tview/textview.go +var ( + boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)") + spacePattern = regexp.MustCompile(`\s+`) +) + +// CalculateBuffer generates the internal buffer for this message that consists +// of the text of this message split into lines at most as wide as the width +// parameter. +func (msg *UITextMessage) CalculateBuffer(width int) { + if width < 2 { + return + } + + msg.buffer = []string{} + text := msg.MsgText + if msg.MsgType == "m.emote" { + text = fmt.Sprintf("* %s %s", msg.MsgSender, msg.MsgText) + } + + forcedLinebreaks := strings.Split(text, "\n") + newlines := 0 + for _, str := range forcedLinebreaks { + if len(str) == 0 && newlines < 1 { + msg.buffer = append(msg.buffer, "") + newlines++ + } else { + newlines = 0 + } + // From tview/textview.go#reindexBuffer() + for len(str) > 0 { + extract := runewidth.Truncate(str, width, "") + if len(extract) < len(str) { + if spaces := spacePattern.FindStringIndex(str[len(extract):]); spaces != nil && spaces[0] == 0 { + extract = str[:len(extract)+spaces[1]] + } + + matches := boundaryPattern.FindAllStringIndex(extract, -1) + if len(matches) > 0 { + extract = extract[:matches[len(matches)-1][1]] + } + } + msg.buffer = append(msg.buffer, extract) + str = str[len(extract):] + } + } + msg.prevBufferWidth = width +} -- cgit v1.2.3