diff --git a/interface/matrix.go b/interface/matrix.go index b7b52c3..cf011cf 100644 --- a/interface/matrix.go +++ b/interface/matrix.go @@ -34,5 +34,5 @@ type MatrixContainer interface { LeaveRoom(roomID string) error GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) GetRoom(roomID string) *rooms.Room - Download(mxcURL string) ([]byte, error) + Download(mxcURL string) ([]byte, string, error) } diff --git a/interface/ui.go b/interface/ui.go index df92308..8d3836d 100644 --- a/interface/ui.go +++ b/interface/ui.go @@ -19,7 +19,7 @@ package ifc import ( "time" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "maunium.net/go/gomatrix" "maunium.net/go/gomuks/matrix/pushrules" "maunium.net/go/gomuks/matrix/rooms" diff --git a/lib/ansimage/ansimage.go b/lib/ansimage/ansimage.go new file mode 100644 index 0000000..a41c833 --- /dev/null +++ b/lib/ansimage/ansimage.go @@ -0,0 +1,287 @@ +// ___ _____ ____ +// / _ \/ _/ |/_/ /____ ______ _ +// / ___// /_> =2") + + // ErrOutOfBounds happens when ANSI-pixel coordinates are out of ANSImage bounds. + ErrOutOfBounds = errors.New("ANSImage: out of bounds") +) + +// ANSIpixel represents a pixel of an ANSImage. +type ANSIpixel struct { + Brightness uint8 + R, G, B uint8 + upper bool + source *ANSImage +} + +// ANSImage represents an image encoded in ANSI escape codes. +type ANSImage struct { + h, w int + maxprocs int + bgR uint8 + bgG uint8 + bgB uint8 + pixmap [][]*ANSIpixel +} + +func (ai *ANSImage) Pixmap() [][]*ANSIpixel { + return ai.pixmap +} + +// Height gets total rows of ANSImage. +func (ai *ANSImage) Height() int { + return ai.h +} + +// Width gets total columns of ANSImage. +func (ai *ANSImage) Width() int { + return ai.w +} + +// SetMaxProcs sets the maximum number of parallel goroutines to render the ANSImage +// (user should manually sets `runtime.GOMAXPROCS(max)` before to this change takes effect). +func (ai *ANSImage) SetMaxProcs(max int) { + ai.maxprocs = max +} + +// GetMaxProcs gets the maximum number of parallels goroutines to render the ANSImage. +func (ai *ANSImage) GetMaxProcs() int { + return ai.maxprocs +} + +// SetAt sets ANSI-pixel color (RBG) and brightness in coordinates (y,x). +func (ai *ANSImage) SetAt(y, x int, r, g, b, brightness uint8) error { + if y >= 0 && y < ai.h && x >= 0 && x < ai.w { + ai.pixmap[y][x].R = r + ai.pixmap[y][x].G = g + ai.pixmap[y][x].B = b + ai.pixmap[y][x].Brightness = brightness + ai.pixmap[y][x].upper = y%2 == 0 + return nil + } + return ErrOutOfBounds +} + +// GetAt gets ANSI-pixel in coordinates (y,x). +func (ai *ANSImage) GetAt(y, x int) (*ANSIpixel, error) { + if y >= 0 && y < ai.h && x >= 0 && x < ai.w { + return &ANSIpixel{ + R: ai.pixmap[y][x].R, + G: ai.pixmap[y][x].G, + B: ai.pixmap[y][x].B, + Brightness: ai.pixmap[y][x].Brightness, + upper: ai.pixmap[y][x].upper, + source: ai.pixmap[y][x].source, + }, + nil + } + return nil, ErrOutOfBounds +} + +// Render returns the ANSI-compatible string form of ANSImage. +// (Nice info for ANSI True Colour - https://gist.github.com/XVilka/8346728) +func (ai *ANSImage) Render() []tstring.TString { + type renderData struct { + row int + render tstring.TString + } + + rows := make([]tstring.TString, ai.h/2) + for y := 0; y < ai.h; y += ai.maxprocs { + ch := make(chan renderData, ai.maxprocs) + for n, r := 0, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 { + go func(r, y int) { + str := make(tstring.TString, ai.w) + for x := 0; x < ai.w; x++ { + topPixel := ai.pixmap[y][x] + topColor := tcell.NewRGBColor(int32(topPixel.R), int32(topPixel.G), int32(topPixel.B)) + + bottomPixel := ai.pixmap[y+1][x] + bottomColor := tcell.NewRGBColor(int32(bottomPixel.R), int32(bottomPixel.G), int32(bottomPixel.B)) + + str[x] = tstring.Cell{ + Char: '▄', + Style: tcell.StyleDefault.Background(topColor).Foreground(bottomColor), + } + } + ch <- renderData{row: r, render: str} + }(r, 2*r) + } + for n, r := 0, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 { + data := <-ch + rows[data.row] = data.render + } + } + return rows +} + +// New creates a new empty ANSImage ready to draw on it. +func New(h, w int, bg color.Color) (*ANSImage, error) { + if h%2 != 0 { + return nil, ErrHeightNonMoT + } + + if h < 2 || w < 2 { + return nil, ErrInvalidBoundsMoT + } + + r, g, b, _ := bg.RGBA() + ansimage := &ANSImage{ + h: h, w: w, + maxprocs: 1, + bgR: uint8(r), + bgG: uint8(g), + bgB: uint8(b), + pixmap: nil, + } + + ansimage.pixmap = func() [][]*ANSIpixel { + v := make([][]*ANSIpixel, h) + for y := 0; y < h; y++ { + v[y] = make([]*ANSIpixel, w) + for x := 0; x < w; x++ { + v[y][x] = &ANSIpixel{ + R: 0, + G: 0, + B: 0, + Brightness: 0, + source: ansimage, + upper: y%2 == 0, + } + } + } + return v + }() + + return ansimage, nil +} + +// NewFromReader creates a new ANSImage from an io.Reader. +// Background color is used to fill when image has transparency or dithering mode is enabled +// Dithering mode is used to specify the way that ANSImage render ANSI-pixels (char/block elements). +func NewFromReader(reader io.Reader, bg color.Color) (*ANSImage, error) { + img, _, err := image.Decode(reader) + if err != nil { + return nil, err + } + + return createANSImage(img, bg) +} + +// NewScaledFromReader creates a new scaled ANSImage from an io.Reader. +// Background color is used to fill when image has transparency or dithering mode is enabled +// Dithering mode is used to specify the way that ANSImage render ANSI-pixels (char/block elements). +func NewScaledFromReader(reader io.Reader, y, x int, bg color.Color) (*ANSImage, error) { + img, _, err := image.Decode(reader) + if err != nil { + return nil, err + } + + img = imaging.Resize(img, x, y, imaging.Lanczos) + + return createANSImage(img, bg) +} + +// NewFromFile creates a new ANSImage from a file. +// Background color is used to fill when image has transparency or dithering mode is enabled +// Dithering mode is used to specify the way that ANSImage render ANSI-pixels (char/block elements). +func NewFromFile(name string, bg color.Color) (*ANSImage, error) { + reader, err := os.Open(name) + if err != nil { + return nil, err + } + defer reader.Close() + return NewFromReader(reader, bg) +} + +// NewScaledFromFile creates a new scaled ANSImage from a file. +// Background color is used to fill when image has transparency or dithering mode is enabled +// Dithering mode is used to specify the way that ANSImage render ANSI-pixels (char/block elements). +func NewScaledFromFile(name string, y, x int, bg color.Color) (*ANSImage, error) { + reader, err := os.Open(name) + if err != nil { + return nil, err + } + defer reader.Close() + return NewScaledFromReader(reader, y, x, bg) +} + +// createANSImage loads data from an image and returns an ANSImage. +// Background color is used to fill when image has transparency or dithering mode is enabled +// Dithering mode is used to specify the way that ANSImage render ANSI-pixels (char/block elements). +func createANSImage(img image.Image, bg color.Color) (*ANSImage, error) { + var rgbaOut *image.RGBA + bounds := img.Bounds() + + // do compositing only if background color has no transparency (thank you @disq for the idea!) + // (info - http://stackoverflow.com/questions/36595687/transparent-pixel-color-go-lang-image) + if _, _, _, a := bg.RGBA(); a >= 0xffff { + rgbaOut = image.NewRGBA(bounds) + draw.Draw(rgbaOut, bounds, image.NewUniform(bg), image.ZP, draw.Src) + draw.Draw(rgbaOut, bounds, img, image.ZP, draw.Over) + } else { + if v, ok := img.(*image.RGBA); ok { + rgbaOut = v + } else { + rgbaOut = image.NewRGBA(bounds) + draw.Draw(rgbaOut, bounds, img, image.ZP, draw.Src) + } + } + + yMin, xMin := bounds.Min.Y, bounds.Min.X + yMax, xMax := bounds.Max.Y, bounds.Max.X + + // always sets an even number of ANSIPixel rows... + yMax = yMax - yMax%2 // one for upper pixel and another for lower pixel --> without dithering + + ansimage, err := New(yMax, xMax, bg) + if err != nil { + return nil, err + } + + for y := yMin; y < yMax; y++ { + for x := xMin; x < xMax; x++ { + v := rgbaOut.RGBAAt(x, y) + if err := ansimage.SetAt(y, x, v.R, v.G, v.B, 0); err != nil { + return nil, err + } + } + } + + return ansimage, nil +} diff --git a/matrix/matrix.go b/matrix/matrix.go index 29419b0..b4d5775 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "net/url" "os" "path" @@ -412,42 +413,44 @@ func (c *Container) GetRoom(roomID string) *rooms.Room { var mxcRegex = regexp.MustCompile("mxc://(.+)/(.+)") -func (c *Container) Download(mxcURL string) ([]byte, error) { +func (c *Container) Download(mxcURL string) (data []byte, fullID string, err error) { parts := mxcRegex.FindStringSubmatch(mxcURL) if parts == nil || len(parts) != 3 { - debug.Print(parts) - return nil, fmt.Errorf("invalid matrix content URL") + err = fmt.Errorf("invalid matrix content URL") + return } hs := parts[1] id := parts[2] + fullID = fmt.Sprintf("%s/%s", hs, id) cacheFile := c.getCachePath(hs, id) - if _, err := os.Stat(cacheFile); err != nil { - data, err := ioutil.ReadFile(cacheFile) + if _, err = os.Stat(cacheFile); err != nil { + data, err = ioutil.ReadFile(cacheFile) if err == nil { - return data, nil + return } } dlURL, _ := url.Parse(c.client.HomeserverURL.String()) dlURL.Path = path.Join(dlURL.Path, "/_matrix/media/v1/download", hs, id) - resp, err := c.client.Client.Get(dlURL.String()) + var resp *http.Response + resp, err = c.client.Client.Get(dlURL.String()) if err != nil { - return nil, err + return } defer resp.Body.Close() var buf bytes.Buffer _, err = io.Copy(&buf, resp.Body) if err != nil { - return nil, err + return } - data := buf.Bytes() + data = buf.Bytes() err = ioutil.WriteFile(cacheFile, data, 0600) - return data, err + return } func (c *Container) getCachePath(homeserver, fileID string) string { dir := filepath.Join(c.config.MediaDir, homeserver) diff --git a/ui/message-view.go b/ui/message-view.go index dd3edb5..87dc993 100644 --- a/ui/message-view.go +++ b/ui/message-view.go @@ -22,7 +22,8 @@ import ( "math" "os" - "github.com/gdamore/tcell" + "maunium.net/go/gomuks/ui/messages/tstring" + "maunium.net/go/tcell" "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/ui/messages" @@ -48,7 +49,7 @@ type MessageView struct { messageIDs map[string]messages.UIMessage messages []messages.UIMessage - textBuffer []messages.UIString + textBuffer []tstring.TString metaBuffer []ifc.MessageMeta } @@ -61,7 +62,7 @@ func NewMessageView() *MessageView { messages: make([]messages.UIMessage, 0), messageIDs: make(map[string]messages.UIMessage), - textBuffer: make([]messages.UIString, 0), + textBuffer: make([]tstring.TString, 0), metaBuffer: make([]ifc.MessageMeta, 0), widestSender: 5, @@ -183,7 +184,7 @@ func (view *MessageView) appendBuffer(message messages.UIMessage) { if len(view.metaBuffer) > 0 { prevMeta := view.metaBuffer[len(view.metaBuffer)-1] if prevMeta != nil && prevMeta.FormatDate() != message.FormatDate() { - view.textBuffer = append(view.textBuffer, messages.NewColorUIString( + view.textBuffer = append(view.textBuffer, tstring.NewColorTString( fmt.Sprintf("Date changed to %s", message.FormatDate()), tcell.ColorGreen)) view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{ @@ -218,7 +219,6 @@ func (view *MessageView) replaceBuffer(message messages.UIMessage) { view.textBuffer = append(append(view.textBuffer[0:start], message.Buffer()...), view.textBuffer[end:]...) if len(message.Buffer()) != end-start+1 { - debug.Print(end, "-", start, "!=", len(message.Buffer())) metaBuffer := view.metaBuffer[0:start] for range message.Buffer() { metaBuffer = append(metaBuffer, message) @@ -233,7 +233,7 @@ func (view *MessageView) recalculateBuffers() { width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap recalculateMessageBuffers := width != view.prevWidth if height != view.prevHeight || recalculateMessageBuffers || len(view.messages) != view.prevMsgCount { - view.textBuffer = []messages.UIString{} + view.textBuffer = []tstring.TString{} view.metaBuffer = []ifc.MessageMeta{} view.prevMsgCount = 0 for i, message := range view.messages { diff --git a/ui/messages/imagemessage.go b/ui/messages/imagemessage.go index 53c0588..7129e5a 100644 --- a/ui/messages/imagemessage.go +++ b/ui/messages/imagemessage.go @@ -23,11 +23,12 @@ import ( "image/color" - "github.com/gdamore/tcell" "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/interface" + "maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/widget" - "maunium.net/go/pixterm/ansimage" + "maunium.net/go/gomuks/lib/ansimage" + "maunium.net/go/tcell" ) func init() { @@ -36,11 +37,12 @@ func init() { type UIImageMessage struct { UITextMessage + Path string data []byte } // NewImageMessage creates a new UIImageMessage object with the provided values and the default state. -func NewImageMessage(id, sender, msgtype string, data []byte, timestamp time.Time) UIMessage { +func NewImageMessage(id, sender, msgtype string, path string, data []byte, timestamp time.Time) UIMessage { return &UIImageMessage{ UITextMessage{ MsgSender: sender, @@ -53,6 +55,7 @@ func NewImageMessage(id, sender, msgtype string, data []byte, timestamp time.Tim MsgIsHighlight: false, MsgIsService: false, }, + path, data, } } @@ -90,24 +93,13 @@ func (msg *UIImageMessage) CalculateBuffer(width int) { return } - image, err := ansimage.NewScaledFromReader(bytes.NewReader(msg.data), -1, width, color.Black, ansimage.ScaleModeResize, ansimage.NoDithering) + image, err := ansimage.NewScaledFromReader(bytes.NewReader(msg.data), 0, width, color.Black) if err != nil { - msg.buffer = []UIString{NewColorUIString("Failed to display image", tcell.ColorRed)} + msg.buffer = []tstring.TString{tstring.NewColorTString("Failed to display image", tcell.ColorRed)} debug.Print("Failed to display image:", err) return } - msg.buffer = make([]UIString, image.Height()) - pixels := image.Pixmap() - for row, pixelRow := range pixels { - msg.buffer[row] = make(UIString, len(pixelRow)) - for column, pixel := range pixelRow { - pixelColor := tcell.NewRGBColor(int32(pixel.R), int32(pixel.G), int32(pixel.B)) - msg.buffer[row][column] = Cell{ - Char: ' ', - Style: tcell.StyleDefault.Background(pixelColor), - } - } - } + msg.buffer = image.Render() msg.prevBufferWidth = width } diff --git a/ui/messages/message.go b/ui/messages/message.go index f116d84..ac118ad 100644 --- a/ui/messages/message.go +++ b/ui/messages/message.go @@ -18,6 +18,7 @@ package messages import ( "maunium.net/go/gomuks/interface" + "maunium.net/go/gomuks/ui/messages/tstring" ) // Message is a wrapper for the content and metadata of a Matrix message intended to be displayed. @@ -26,7 +27,7 @@ type UIMessage interface { CalculateBuffer(width int) RecalculateBuffer() - Buffer() []UIString + Buffer() []tstring.TString Height() int RealSender() string diff --git a/ui/messages/meta.go b/ui/messages/meta.go index 3f9c9ab..7e2f29f 100644 --- a/ui/messages/meta.go +++ b/ui/messages/meta.go @@ -19,7 +19,7 @@ package messages import ( "time" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "maunium.net/go/gomuks/interface" ) diff --git a/ui/messages/parser.go b/ui/messages/parser.go index 4300c86..22ba4e7 100644 --- a/ui/messages/parser.go +++ b/ui/messages/parser.go @@ -20,12 +20,13 @@ import ( "fmt" "time" - "github.com/gdamore/tcell" "maunium.net/go/gomatrix" "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/matrix/rooms" + "maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/widget" + "maunium.net/go/tcell" ) func ParseEvent(mx ifc.MatrixContainer, room *rooms.Room, evt *gomatrix.Event) UIMessage { @@ -59,16 +60,16 @@ func ParseMessage(mx ifc.MatrixContainer, evt *gomatrix.Event) UIMessage { return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts) case "m.image": url, _ := evt.Content["url"].(string) - data, err := mx.Download(url) + data, path, err := mx.Download(url) if err != nil { debug.Printf("Failed to download %s: %v", url, err) } - return NewImageMessage(evt.ID, evt.Sender, msgtype, data, ts) + return NewImageMessage(evt.ID, evt.Sender, msgtype, path, data, ts) } return nil } -func getMembershipEventContent(evt *gomatrix.Event) (sender string, text UIString) { +func getMembershipEventContent(evt *gomatrix.Event) (sender string, text tstring.TString) { membership, _ := evt.Content["membership"].(string) displayname, _ := evt.Content["displayname"].(string) if len(displayname) == 0 { @@ -85,28 +86,28 @@ func getMembershipEventContent(evt *gomatrix.Event) (sender string, text UIStrin switch membership { case "invite": sender = "---" - text = NewColorUIString(fmt.Sprintf("%s invited %s.", evt.Sender, displayname), tcell.ColorYellow) + text = tstring.NewColorTString(fmt.Sprintf("%s invited %s.", evt.Sender, displayname), tcell.ColorYellow) text.Colorize(0, len(evt.Sender), widget.GetHashColor(evt.Sender)) text.Colorize(len(evt.Sender)+len(" invited "), len(displayname), widget.GetHashColor(displayname)) case "join": sender = "-->" - text = NewColorUIString(fmt.Sprintf("%s joined the room.", displayname), tcell.ColorGreen) + text = tstring.NewColorTString(fmt.Sprintf("%s joined the room.", displayname), tcell.ColorGreen) text.Colorize(0, len(displayname), widget.GetHashColor(displayname)) case "leave": sender = "<--" if evt.Sender != *evt.StateKey { reason, _ := evt.Content["reason"].(string) - text = NewColorUIString(fmt.Sprintf("%s kicked %s: %s", evt.Sender, displayname, reason), tcell.ColorRed) + text = tstring.NewColorTString(fmt.Sprintf("%s kicked %s: %s", evt.Sender, displayname, reason), tcell.ColorRed) text.Colorize(0, len(evt.Sender), widget.GetHashColor(evt.Sender)) text.Colorize(len(evt.Sender)+len(" kicked "), len(displayname), widget.GetHashColor(displayname)) } else { - text = NewColorUIString(fmt.Sprintf("%s left the room.", displayname), tcell.ColorRed) + text = tstring.NewColorTString(fmt.Sprintf("%s left the room.", displayname), tcell.ColorRed) text.Colorize(0, len(displayname), widget.GetHashColor(displayname)) } } } else if displayname != prevDisplayname { sender = "---" - text = NewColorUIString(fmt.Sprintf("%s changed their display name to %s.", prevDisplayname, displayname), tcell.ColorYellow) + text = tstring.NewColorTString(fmt.Sprintf("%s changed their display name to %s.", prevDisplayname, displayname), tcell.ColorYellow) text.Colorize(0, len(prevDisplayname), widget.GetHashColor(prevDisplayname)) text.Colorize(len(prevDisplayname)+len(" changed their display name to "), len(displayname), widget.GetHashColor(displayname)) } diff --git a/ui/messages/textmessage.go b/ui/messages/textmessage.go index 8ad3168..12b098d 100644 --- a/ui/messages/textmessage.go +++ b/ui/messages/textmessage.go @@ -22,7 +22,8 @@ import ( "regexp" "time" - "github.com/gdamore/tcell" + "maunium.net/go/gomuks/ui/messages/tstring" + "maunium.net/go/tcell" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/ui/widget" ) @@ -34,11 +35,11 @@ func init() { type UIExpandedTextMessage struct { UITextMessage - MsgUIStringText UIString + MsgTStringText tstring.TString } // NewExpandedTextMessage creates a new UIExpandedTextMessage object with the provided values and the default state. -func NewExpandedTextMessage(id, sender, msgtype string, text UIString, timestamp time.Time) UIMessage { +func NewExpandedTextMessage(id, sender, msgtype string, text tstring.TString, timestamp time.Time) UIMessage { return &UIExpandedTextMessage{ UITextMessage{ MsgSender: sender, @@ -56,8 +57,8 @@ func NewExpandedTextMessage(id, sender, msgtype string, text UIString, timestamp } } -func (msg *UIExpandedTextMessage) GetUIStringText() UIString { - return msg.MsgUIStringText +func (msg *UIExpandedTextMessage) GetTStringText() tstring.TString { + return msg.MsgTStringText } // CopyFrom replaces the content of this message object with the content of the given object. @@ -78,9 +79,9 @@ func (msg *UIExpandedTextMessage) CopyFrom(from ifc.MessageMeta) { fromExpandedMsg, ok := from.(*UIExpandedTextMessage) if ok { - msg.MsgUIStringText = fromExpandedMsg.MsgUIStringText + msg.MsgTStringText = fromExpandedMsg.MsgTStringText } else { - msg.MsgUIStringText = NewColorUIString(fromMsg.Text(), from.TextColor()) + msg.MsgTStringText = tstring.NewColorTString(fromMsg.Text(), from.TextColor()) } msg.RecalculateBuffer() @@ -97,7 +98,7 @@ type UITextMessage struct { MsgState ifc.MessageState MsgIsHighlight bool MsgIsService bool - buffer []UIString + buffer []tstring.TString prevBufferWidth int } @@ -235,7 +236,7 @@ func (msg *UITextMessage) RecalculateBuffer() { // // 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() []UIString { +func (msg *UITextMessage) Buffer() []tstring.TString { return msg.buffer } @@ -307,8 +308,8 @@ func (msg *UITextMessage) SetIsService(isService bool) { msg.MsgIsService = isService } -func (msg *UITextMessage) GetUIStringText() UIString { - return NewColorUIString(msg.Text(), msg.TextColor()) +func (msg *UITextMessage) GetTStringText() tstring.TString { + return tstring.NewColorTString(msg.Text(), msg.TextColor()) } // Regular expressions used to split lines when calculating the buffer. @@ -327,10 +328,10 @@ func (msg *UITextMessage) CalculateBuffer(width int) { return } - msg.buffer = []UIString{} - text := msg.GetUIStringText() + msg.buffer = []tstring.TString{} + text := msg.GetTStringText() if msg.MsgType == "m.emote" { - text = NewColorUIString(fmt.Sprintf("* %s %s", msg.MsgSender, text.String()), msg.TextColor()) + text = tstring.NewColorTString(fmt.Sprintf("* %s %s", msg.MsgSender, text.String()), msg.TextColor()) text.Colorize(2, len(msg.MsgSender), msg.SenderColor()) } @@ -338,7 +339,7 @@ func (msg *UITextMessage) CalculateBuffer(width int) { newlines := 0 for _, str := range forcedLinebreaks { if len(str) == 0 && newlines < 1 { - msg.buffer = append(msg.buffer, UIString{}) + msg.buffer = append(msg.buffer, tstring.TString{}) newlines++ } else { newlines = 0 diff --git a/ui/messages/cell.go b/ui/messages/tstring/cell.go similarity index 96% rename from ui/messages/cell.go rename to ui/messages/tstring/cell.go index a919da7..8a400ee 100644 --- a/ui/messages/cell.go +++ b/ui/messages/tstring/cell.go @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package messages +package tstring import ( - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "github.com/mattn/go-runewidth" ) diff --git a/ui/messages/string.go b/ui/messages/tstring/string.go similarity index 75% rename from ui/messages/string.go rename to ui/messages/tstring/string.go index 7c3143b..ad0b2c8 100644 --- a/ui/messages/string.go +++ b/ui/messages/tstring/string.go @@ -14,18 +14,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package messages +package tstring import ( "strings" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "github.com/mattn/go-runewidth" ) -type UIString []Cell +type TString []Cell -func NewUIString(str string) UIString { +func NewTString(str string) TString { newStr := make([]Cell, len(str)) for i, char := range str { newStr[i] = NewCell(char) @@ -33,7 +33,7 @@ func NewUIString(str string) UIString { return newStr } -func NewColorUIString(str string, color tcell.Color) UIString { +func NewColorTString(str string, color tcell.Color) TString { newStr := make([]Cell, len(str)) for i, char := range str { newStr[i] = NewColorCell(char, color) @@ -41,7 +41,7 @@ func NewColorUIString(str string, color tcell.Color) UIString { return newStr } -func NewStyleUIString(str string, style tcell.Style) UIString { +func NewStyleTString(str string, style tcell.Style) TString { newStr := make([]Cell, len(str)) for i, char := range str { newStr[i] = NewStyleCell(char, style) @@ -49,27 +49,27 @@ func NewStyleUIString(str string, style tcell.Style) UIString { return newStr } -func (str UIString) Colorize(from, length int, color tcell.Color) { +func (str TString) Colorize(from, length int, color tcell.Color) { for i := from; i < from+length; i++ { str[i].Style = str[i].Style.Foreground(color) } } -func (str UIString) Draw(screen tcell.Screen, x, y int) { +func (str TString) Draw(screen tcell.Screen, x, y int) { offsetX := 0 for _, cell := range str { offsetX += cell.Draw(screen, x+offsetX, y) } } -func (str UIString) RuneWidth() (width int) { +func (str TString) RuneWidth() (width int) { for _, cell := range str { width += runewidth.RuneWidth(cell.Char) } return width } -func (str UIString) String() string { +func (str TString) String() string { var buf strings.Builder for _, cell := range str { buf.WriteRune(cell.Char) @@ -78,7 +78,7 @@ func (str UIString) String() string { } // Truncate return string truncated with w cells -func (str UIString) Truncate(w int) UIString { +func (str TString) Truncate(w int) TString { if str.RuneWidth() <= w { return str[:] } @@ -94,7 +94,7 @@ func (str UIString) Truncate(w int) UIString { return str[0:i] } -func (str UIString) IndexFrom(r rune, from int) int { +func (str TString) IndexFrom(r rune, from int) int { for i := from; i < len(str); i++ { if str[i].Char == r { return i @@ -103,11 +103,11 @@ func (str UIString) IndexFrom(r rune, from int) int { return -1 } -func (str UIString) Index(r rune) int { +func (str TString) Index(r rune) int { return str.IndexFrom(r, 0) } -func (str UIString) Count(r rune) (counter int) { +func (str TString) Count(r rune) (counter int) { index := 0 for { index = str.IndexFrom(r, index) @@ -120,8 +120,8 @@ func (str UIString) Count(r rune) (counter int) { return } -func (str UIString) Split(sep rune) []UIString { - a := make([]UIString, str.Count(sep)+1) +func (str TString) Split(sep rune) []TString { + a := make([]TString, str.Count(sep)+1) i := 0 orig := str for { diff --git a/ui/room-list.go b/ui/room-list.go index 2ff3ed7..b85ed24 100644 --- a/ui/room-list.go +++ b/ui/room-list.go @@ -20,7 +20,7 @@ import ( "fmt" "strconv" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/ui/widget" "maunium.net/go/tview" diff --git a/ui/room-view.go b/ui/room-view.go index 749a432..b7c9a34 100644 --- a/ui/room-view.go +++ b/ui/room-view.go @@ -23,7 +23,7 @@ import ( "strings" "time" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/ui/messages" diff --git a/ui/ui.go b/ui/ui.go index 16321b8..b5f847d 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -17,7 +17,7 @@ package ui import ( - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "maunium.net/go/gomuks/interface" "maunium.net/go/tview" ) diff --git a/ui/view-main.go b/ui/view-main.go index 3a6c38d..056f8a0 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -23,7 +23,7 @@ import ( "time" "unicode" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "github.com/mattn/go-runewidth" "maunium.net/go/gomatrix" "maunium.net/go/gomuks/config" @@ -230,6 +230,11 @@ func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMo x, y := event.Position() switch event.Buttons() { + case tcell.Button1: + mx, my, mw, mh := msgView.GetRect() + if x >= mx && y >= my && x < mx+mw && y < my+mh { + debug.Print("Message view clicked") + } case tcell.WheelUp: if msgView.IsAtTop() { go view.LoadHistory(roomView.Room.ID, false) diff --git a/ui/widget/advanced-inputfield.go b/ui/widget/advanced-inputfield.go index f74ce29..7e01478 100644 --- a/ui/widget/advanced-inputfield.go +++ b/ui/widget/advanced-inputfield.go @@ -24,7 +24,7 @@ import ( "strings" "unicode/utf8" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "github.com/mattn/go-runewidth" "github.com/zyedidia/clipboard" "maunium.net/go/tview" diff --git a/ui/widget/border.go b/ui/widget/border.go index 7c42f3d..b3eb65d 100644 --- a/ui/widget/border.go +++ b/ui/widget/border.go @@ -17,7 +17,7 @@ package widget import ( - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "maunium.net/go/tview" ) diff --git a/ui/widget/color.go b/ui/widget/color.go index 12ee791..c4f1abf 100644 --- a/ui/widget/color.go +++ b/ui/widget/color.go @@ -21,7 +21,7 @@ import ( "hash/fnv" "sort" - "github.com/gdamore/tcell" + "maunium.net/go/tcell" ) var colorNames []string diff --git a/ui/widget/util.go b/ui/widget/util.go index bd80903..920afad 100644 --- a/ui/widget/util.go +++ b/ui/widget/util.go @@ -17,7 +17,7 @@ package widget import ( - "github.com/gdamore/tcell" + "maunium.net/go/tcell" "github.com/mattn/go-runewidth" "maunium.net/go/tview" )