Rename UIString to TString, move ansimage to lib/ and switch to tcell fork
This commit is contained in:
parent
c0705b02a0
commit
ff7ee333a1
@ -34,5 +34,5 @@ type MatrixContainer interface {
|
|||||||
LeaveRoom(roomID string) error
|
LeaveRoom(roomID string) error
|
||||||
GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error)
|
GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error)
|
||||||
GetRoom(roomID string) *rooms.Room
|
GetRoom(roomID string) *rooms.Room
|
||||||
Download(mxcURL string) ([]byte, error)
|
Download(mxcURL string) ([]byte, string, error)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package ifc
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
|
287
lib/ansimage/ansimage.go
Normal file
287
lib/ansimage/ansimage.go
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
// ___ _____ ____
|
||||||
|
// / _ \/ _/ |/_/ /____ ______ _
|
||||||
|
// / ___// /_> </ __/ -_) __/ ' \
|
||||||
|
// /_/ /___/_/|_|\__/\__/_/ /_/_/_/
|
||||||
|
//
|
||||||
|
// Copyright 2017 Eliuk Blau
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package ansimage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
_ "image/gif" // initialize decoder
|
||||||
|
_ "image/jpeg" // initialize decoder
|
||||||
|
_ "image/png" // initialize decoder
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
_ "golang.org/x/image/bmp" // initialize decoder
|
||||||
|
_ "golang.org/x/image/tiff" // initialize decoder
|
||||||
|
_ "golang.org/x/image/webp" // initialize decoder
|
||||||
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
|
"maunium.net/go/tcell"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrHeightNonMoT happens when ANSImage height is not a Multiple of Two value.
|
||||||
|
ErrHeightNonMoT = errors.New("ANSImage: height must be a Multiple of Two value")
|
||||||
|
|
||||||
|
// ErrInvalidBoundsMoT happens when ANSImage height or width are invalid values (Multiple of Two).
|
||||||
|
ErrInvalidBoundsMoT = errors.New("ANSImage: height or width must be >=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
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -412,42 +413,44 @@ func (c *Container) GetRoom(roomID string) *rooms.Room {
|
|||||||
|
|
||||||
var mxcRegex = regexp.MustCompile("mxc://(.+)/(.+)")
|
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)
|
parts := mxcRegex.FindStringSubmatch(mxcURL)
|
||||||
if parts == nil || len(parts) != 3 {
|
if parts == nil || len(parts) != 3 {
|
||||||
debug.Print(parts)
|
err = fmt.Errorf("invalid matrix content URL")
|
||||||
return nil, fmt.Errorf("invalid matrix content URL")
|
return
|
||||||
}
|
}
|
||||||
hs := parts[1]
|
hs := parts[1]
|
||||||
id := parts[2]
|
id := parts[2]
|
||||||
|
fullID = fmt.Sprintf("%s/%s", hs, id)
|
||||||
|
|
||||||
cacheFile := c.getCachePath(hs, id)
|
cacheFile := c.getCachePath(hs, id)
|
||||||
if _, err := os.Stat(cacheFile); err != nil {
|
if _, err = os.Stat(cacheFile); err != nil {
|
||||||
data, err := ioutil.ReadFile(cacheFile)
|
data, err = ioutil.ReadFile(cacheFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return data, nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dlURL, _ := url.Parse(c.client.HomeserverURL.String())
|
dlURL, _ := url.Parse(c.client.HomeserverURL.String())
|
||||||
dlURL.Path = path.Join(dlURL.Path, "/_matrix/media/v1/download", hs, id)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
_, err = io.Copy(&buf, resp.Body)
|
_, err = io.Copy(&buf, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := buf.Bytes()
|
data = buf.Bytes()
|
||||||
|
|
||||||
err = ioutil.WriteFile(cacheFile, data, 0600)
|
err = ioutil.WriteFile(cacheFile, data, 0600)
|
||||||
return data, err
|
return
|
||||||
}
|
}
|
||||||
func (c *Container) getCachePath(homeserver, fileID string) string {
|
func (c *Container) getCachePath(homeserver, fileID string) string {
|
||||||
dir := filepath.Join(c.config.MediaDir, homeserver)
|
dir := filepath.Join(c.config.MediaDir, homeserver)
|
||||||
|
@ -22,7 +22,8 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"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/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/ui/messages"
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
@ -48,7 +49,7 @@ type MessageView struct {
|
|||||||
messageIDs map[string]messages.UIMessage
|
messageIDs map[string]messages.UIMessage
|
||||||
messages []messages.UIMessage
|
messages []messages.UIMessage
|
||||||
|
|
||||||
textBuffer []messages.UIString
|
textBuffer []tstring.TString
|
||||||
metaBuffer []ifc.MessageMeta
|
metaBuffer []ifc.MessageMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ func NewMessageView() *MessageView {
|
|||||||
|
|
||||||
messages: make([]messages.UIMessage, 0),
|
messages: make([]messages.UIMessage, 0),
|
||||||
messageIDs: make(map[string]messages.UIMessage),
|
messageIDs: make(map[string]messages.UIMessage),
|
||||||
textBuffer: make([]messages.UIString, 0),
|
textBuffer: make([]tstring.TString, 0),
|
||||||
metaBuffer: make([]ifc.MessageMeta, 0),
|
metaBuffer: make([]ifc.MessageMeta, 0),
|
||||||
|
|
||||||
widestSender: 5,
|
widestSender: 5,
|
||||||
@ -183,7 +184,7 @@ func (view *MessageView) appendBuffer(message messages.UIMessage) {
|
|||||||
if len(view.metaBuffer) > 0 {
|
if len(view.metaBuffer) > 0 {
|
||||||
prevMeta := view.metaBuffer[len(view.metaBuffer)-1]
|
prevMeta := view.metaBuffer[len(view.metaBuffer)-1]
|
||||||
if prevMeta != nil && prevMeta.FormatDate() != message.FormatDate() {
|
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()),
|
fmt.Sprintf("Date changed to %s", message.FormatDate()),
|
||||||
tcell.ColorGreen))
|
tcell.ColorGreen))
|
||||||
view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{
|
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:]...)
|
view.textBuffer = append(append(view.textBuffer[0:start], message.Buffer()...), view.textBuffer[end:]...)
|
||||||
if len(message.Buffer()) != end-start+1 {
|
if len(message.Buffer()) != end-start+1 {
|
||||||
debug.Print(end, "-", start, "!=", len(message.Buffer()))
|
|
||||||
metaBuffer := view.metaBuffer[0:start]
|
metaBuffer := view.metaBuffer[0:start]
|
||||||
for range message.Buffer() {
|
for range message.Buffer() {
|
||||||
metaBuffer = append(metaBuffer, message)
|
metaBuffer = append(metaBuffer, message)
|
||||||
@ -233,7 +233,7 @@ func (view *MessageView) recalculateBuffers() {
|
|||||||
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
||||||
recalculateMessageBuffers := width != view.prevWidth
|
recalculateMessageBuffers := width != view.prevWidth
|
||||||
if height != view.prevHeight || recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
|
if height != view.prevHeight || recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
|
||||||
view.textBuffer = []messages.UIString{}
|
view.textBuffer = []tstring.TString{}
|
||||||
view.metaBuffer = []ifc.MessageMeta{}
|
view.metaBuffer = []ifc.MessageMeta{}
|
||||||
view.prevMsgCount = 0
|
view.prevMsgCount = 0
|
||||||
for i, message := range view.messages {
|
for i, message := range view.messages {
|
||||||
|
@ -23,11 +23,12 @@ import (
|
|||||||
|
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/pixterm/ansimage"
|
"maunium.net/go/gomuks/lib/ansimage"
|
||||||
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -36,11 +37,12 @@ func init() {
|
|||||||
|
|
||||||
type UIImageMessage struct {
|
type UIImageMessage struct {
|
||||||
UITextMessage
|
UITextMessage
|
||||||
|
Path string
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImageMessage creates a new UIImageMessage object with the provided values and the default state.
|
// 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{
|
return &UIImageMessage{
|
||||||
UITextMessage{
|
UITextMessage{
|
||||||
MsgSender: sender,
|
MsgSender: sender,
|
||||||
@ -53,6 +55,7 @@ func NewImageMessage(id, sender, msgtype string, data []byte, timestamp time.Tim
|
|||||||
MsgIsHighlight: false,
|
MsgIsHighlight: false,
|
||||||
MsgIsService: false,
|
MsgIsService: false,
|
||||||
},
|
},
|
||||||
|
path,
|
||||||
data,
|
data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,24 +93,13 @@ func (msg *UIImageMessage) CalculateBuffer(width int) {
|
|||||||
return
|
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 {
|
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)
|
debug.Print("Failed to display image:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.buffer = make([]UIString, image.Height())
|
msg.buffer = image.Render()
|
||||||
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.prevBufferWidth = width
|
msg.prevBufferWidth = width
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package messages
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomuks/interface"
|
"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.
|
// 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)
|
CalculateBuffer(width int)
|
||||||
RecalculateBuffer()
|
RecalculateBuffer()
|
||||||
Buffer() []UIString
|
Buffer() []tstring.TString
|
||||||
Height() int
|
Height() int
|
||||||
|
|
||||||
RealSender() string
|
RealSender() string
|
||||||
|
@ -19,7 +19,7 @@ package messages
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,12 +20,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseEvent(mx ifc.MatrixContainer, room *rooms.Room, evt *gomatrix.Event) UIMessage {
|
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)
|
return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
|
||||||
case "m.image":
|
case "m.image":
|
||||||
url, _ := evt.Content["url"].(string)
|
url, _ := evt.Content["url"].(string)
|
||||||
data, err := mx.Download(url)
|
data, path, err := mx.Download(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Printf("Failed to download %s: %v", url, err)
|
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
|
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)
|
membership, _ := evt.Content["membership"].(string)
|
||||||
displayname, _ := evt.Content["displayname"].(string)
|
displayname, _ := evt.Content["displayname"].(string)
|
||||||
if len(displayname) == 0 {
|
if len(displayname) == 0 {
|
||||||
@ -85,28 +86,28 @@ func getMembershipEventContent(evt *gomatrix.Event) (sender string, text UIStrin
|
|||||||
switch membership {
|
switch membership {
|
||||||
case "invite":
|
case "invite":
|
||||||
sender = "---"
|
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(0, len(evt.Sender), widget.GetHashColor(evt.Sender))
|
||||||
text.Colorize(len(evt.Sender)+len(" invited "), len(displayname), widget.GetHashColor(displayname))
|
text.Colorize(len(evt.Sender)+len(" invited "), len(displayname), widget.GetHashColor(displayname))
|
||||||
case "join":
|
case "join":
|
||||||
sender = "-->"
|
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))
|
text.Colorize(0, len(displayname), widget.GetHashColor(displayname))
|
||||||
case "leave":
|
case "leave":
|
||||||
sender = "<--"
|
sender = "<--"
|
||||||
if evt.Sender != *evt.StateKey {
|
if evt.Sender != *evt.StateKey {
|
||||||
reason, _ := evt.Content["reason"].(string)
|
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(0, len(evt.Sender), widget.GetHashColor(evt.Sender))
|
||||||
text.Colorize(len(evt.Sender)+len(" kicked "), len(displayname), widget.GetHashColor(displayname))
|
text.Colorize(len(evt.Sender)+len(" kicked "), len(displayname), widget.GetHashColor(displayname))
|
||||||
} else {
|
} 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))
|
text.Colorize(0, len(displayname), widget.GetHashColor(displayname))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if displayname != prevDisplayname {
|
} else if displayname != prevDisplayname {
|
||||||
sender = "---"
|
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(0, len(prevDisplayname), widget.GetHashColor(prevDisplayname))
|
||||||
text.Colorize(len(prevDisplayname)+len(" changed their display name to "), len(displayname), widget.GetHashColor(displayname))
|
text.Colorize(len(prevDisplayname)+len(" changed their display name to "), len(displayname), widget.GetHashColor(displayname))
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"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/interface"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
)
|
)
|
||||||
@ -34,11 +35,11 @@ func init() {
|
|||||||
|
|
||||||
type UIExpandedTextMessage struct {
|
type UIExpandedTextMessage struct {
|
||||||
UITextMessage
|
UITextMessage
|
||||||
MsgUIStringText UIString
|
MsgTStringText tstring.TString
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExpandedTextMessage creates a new UIExpandedTextMessage object with the provided values and the default state.
|
// 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{
|
return &UIExpandedTextMessage{
|
||||||
UITextMessage{
|
UITextMessage{
|
||||||
MsgSender: sender,
|
MsgSender: sender,
|
||||||
@ -56,8 +57,8 @@ func NewExpandedTextMessage(id, sender, msgtype string, text UIString, timestamp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *UIExpandedTextMessage) GetUIStringText() UIString {
|
func (msg *UIExpandedTextMessage) GetTStringText() tstring.TString {
|
||||||
return msg.MsgUIStringText
|
return msg.MsgTStringText
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFrom replaces the content of this message object with the content of the given object.
|
// 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)
|
fromExpandedMsg, ok := from.(*UIExpandedTextMessage)
|
||||||
if ok {
|
if ok {
|
||||||
msg.MsgUIStringText = fromExpandedMsg.MsgUIStringText
|
msg.MsgTStringText = fromExpandedMsg.MsgTStringText
|
||||||
} else {
|
} else {
|
||||||
msg.MsgUIStringText = NewColorUIString(fromMsg.Text(), from.TextColor())
|
msg.MsgTStringText = tstring.NewColorTString(fromMsg.Text(), from.TextColor())
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.RecalculateBuffer()
|
msg.RecalculateBuffer()
|
||||||
@ -97,7 +98,7 @@ type UITextMessage struct {
|
|||||||
MsgState ifc.MessageState
|
MsgState ifc.MessageState
|
||||||
MsgIsHighlight bool
|
MsgIsHighlight bool
|
||||||
MsgIsService bool
|
MsgIsService bool
|
||||||
buffer []UIString
|
buffer []tstring.TString
|
||||||
prevBufferWidth int
|
prevBufferWidth int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ func (msg *UITextMessage) RecalculateBuffer() {
|
|||||||
//
|
//
|
||||||
// N.B. This will NOT automatically calculate the buffer if it hasn't been
|
// N.B. This will NOT automatically calculate the buffer if it hasn't been
|
||||||
// calculated already, as that requires the target width.
|
// calculated already, as that requires the target width.
|
||||||
func (msg *UITextMessage) Buffer() []UIString {
|
func (msg *UITextMessage) Buffer() []tstring.TString {
|
||||||
return msg.buffer
|
return msg.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,8 +308,8 @@ func (msg *UITextMessage) SetIsService(isService bool) {
|
|||||||
msg.MsgIsService = isService
|
msg.MsgIsService = isService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *UITextMessage) GetUIStringText() UIString {
|
func (msg *UITextMessage) GetTStringText() tstring.TString {
|
||||||
return NewColorUIString(msg.Text(), msg.TextColor())
|
return tstring.NewColorTString(msg.Text(), msg.TextColor())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular expressions used to split lines when calculating the buffer.
|
// Regular expressions used to split lines when calculating the buffer.
|
||||||
@ -327,10 +328,10 @@ func (msg *UITextMessage) CalculateBuffer(width int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.buffer = []UIString{}
|
msg.buffer = []tstring.TString{}
|
||||||
text := msg.GetUIStringText()
|
text := msg.GetTStringText()
|
||||||
if msg.MsgType == "m.emote" {
|
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())
|
text.Colorize(2, len(msg.MsgSender), msg.SenderColor())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +339,7 @@ func (msg *UITextMessage) CalculateBuffer(width int) {
|
|||||||
newlines := 0
|
newlines := 0
|
||||||
for _, str := range forcedLinebreaks {
|
for _, str := range forcedLinebreaks {
|
||||||
if len(str) == 0 && newlines < 1 {
|
if len(str) == 0 && newlines < 1 {
|
||||||
msg.buffer = append(msg.buffer, UIString{})
|
msg.buffer = append(msg.buffer, tstring.TString{})
|
||||||
newlines++
|
newlines++
|
||||||
} else {
|
} else {
|
||||||
newlines = 0
|
newlines = 0
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package messages
|
package tstring
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
@ -14,18 +14,18 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package messages
|
package tstring
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
"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))
|
newStr := make([]Cell, len(str))
|
||||||
for i, char := range str {
|
for i, char := range str {
|
||||||
newStr[i] = NewCell(char)
|
newStr[i] = NewCell(char)
|
||||||
@ -33,7 +33,7 @@ func NewUIString(str string) UIString {
|
|||||||
return newStr
|
return newStr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewColorUIString(str string, color tcell.Color) UIString {
|
func NewColorTString(str string, color tcell.Color) TString {
|
||||||
newStr := make([]Cell, len(str))
|
newStr := make([]Cell, len(str))
|
||||||
for i, char := range str {
|
for i, char := range str {
|
||||||
newStr[i] = NewColorCell(char, color)
|
newStr[i] = NewColorCell(char, color)
|
||||||
@ -41,7 +41,7 @@ func NewColorUIString(str string, color tcell.Color) UIString {
|
|||||||
return newStr
|
return newStr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStyleUIString(str string, style tcell.Style) UIString {
|
func NewStyleTString(str string, style tcell.Style) TString {
|
||||||
newStr := make([]Cell, len(str))
|
newStr := make([]Cell, len(str))
|
||||||
for i, char := range str {
|
for i, char := range str {
|
||||||
newStr[i] = NewStyleCell(char, style)
|
newStr[i] = NewStyleCell(char, style)
|
||||||
@ -49,27 +49,27 @@ func NewStyleUIString(str string, style tcell.Style) UIString {
|
|||||||
return newStr
|
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++ {
|
for i := from; i < from+length; i++ {
|
||||||
str[i].Style = str[i].Style.Foreground(color)
|
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
|
offsetX := 0
|
||||||
for _, cell := range str {
|
for _, cell := range str {
|
||||||
offsetX += cell.Draw(screen, x+offsetX, y)
|
offsetX += cell.Draw(screen, x+offsetX, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (str UIString) RuneWidth() (width int) {
|
func (str TString) RuneWidth() (width int) {
|
||||||
for _, cell := range str {
|
for _, cell := range str {
|
||||||
width += runewidth.RuneWidth(cell.Char)
|
width += runewidth.RuneWidth(cell.Char)
|
||||||
}
|
}
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
func (str UIString) String() string {
|
func (str TString) String() string {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
for _, cell := range str {
|
for _, cell := range str {
|
||||||
buf.WriteRune(cell.Char)
|
buf.WriteRune(cell.Char)
|
||||||
@ -78,7 +78,7 @@ func (str UIString) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Truncate return string truncated with w cells
|
// 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 {
|
if str.RuneWidth() <= w {
|
||||||
return str[:]
|
return str[:]
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ func (str UIString) Truncate(w int) UIString {
|
|||||||
return str[0:i]
|
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++ {
|
for i := from; i < len(str); i++ {
|
||||||
if str[i].Char == r {
|
if str[i].Char == r {
|
||||||
return i
|
return i
|
||||||
@ -103,11 +103,11 @@ func (str UIString) IndexFrom(r rune, from int) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (str UIString) Index(r rune) int {
|
func (str TString) Index(r rune) int {
|
||||||
return str.IndexFrom(r, 0)
|
return str.IndexFrom(r, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (str UIString) Count(r rune) (counter int) {
|
func (str TString) Count(r rune) (counter int) {
|
||||||
index := 0
|
index := 0
|
||||||
for {
|
for {
|
||||||
index = str.IndexFrom(r, index)
|
index = str.IndexFrom(r, index)
|
||||||
@ -120,8 +120,8 @@ func (str UIString) Count(r rune) (counter int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (str UIString) Split(sep rune) []UIString {
|
func (str TString) Split(sep rune) []TString {
|
||||||
a := make([]UIString, str.Count(sep)+1)
|
a := make([]TString, str.Count(sep)+1)
|
||||||
i := 0
|
i := 0
|
||||||
orig := str
|
orig := str
|
||||||
for {
|
for {
|
@ -20,7 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/messages"
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
|
2
ui/ui.go
2
ui/ui.go
@ -17,7 +17,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
@ -230,6 +230,11 @@ func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMo
|
|||||||
x, y := event.Position()
|
x, y := event.Position()
|
||||||
|
|
||||||
switch event.Buttons() {
|
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:
|
case tcell.WheelUp:
|
||||||
if msgView.IsAtTop() {
|
if msgView.IsAtTop() {
|
||||||
go view.LoadHistory(roomView.Room.ID, false)
|
go view.LoadHistory(roomView.Room.ID, false)
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/zyedidia/clipboard"
|
"github.com/zyedidia/clipboard"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package widget
|
package widget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var colorNames []string
|
var colorNames []string
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package widget
|
package widget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell"
|
"maunium.net/go/tcell"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/tview"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user