Start moving to mauview
This commit is contained in:
parent
ae36b9cddd
commit
8aa134b8b2
3
go.mod
3
go.mod
@ -11,9 +11,8 @@ require (
|
|||||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d
|
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d
|
||||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
|
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc
|
golang.org/x/net v0.0.0-20190110200230-915654e7eabc
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3
|
maunium.net/go/mautrix v0.1.0-alpha.3
|
||||||
|
maunium.net/go/mauview v0.0.0-20190325223341-4c387be4b686
|
||||||
maunium.net/go/tcell v0.0.0-20190111223412-5e74142cb009
|
maunium.net/go/tcell v0.0.0-20190111223412-5e74142cb009
|
||||||
maunium.net/go/tview v0.0.0-20190111223510-de38190b095b
|
|
||||||
)
|
)
|
||||||
|
18
go.sum
18
go.sum
@ -10,6 +10,8 @@ github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC1
|
|||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/rivo/uniseg v0.0.0-20190313204849-f699dde9c340 h1:nOZbL5f2xmBAHWYrrHbHV1xatzZirN++oOQ3g83Ypgs=
|
||||||
|
github.com/rivo/uniseg v0.0.0-20190313204849-f699dde9c340/go.mod h1:SOLvOL4ybwgLJ6TYoX/rtaJ8EGOulH4XU7E9/TLrTCE=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
@ -18,29 +20,17 @@ github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d h1:Lhqt2eo+rgM8
|
|||||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
||||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM=
|
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM=
|
||||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
|
golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
|
||||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.2 h1:NsLc5tyrp5tyrKTvFSmqcLi+FISQ+FsuWC/ycL08PzI=
|
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.2/go.mod h1:C8akEpHpmmO8gQhLvmInr3HujhUXyKvCoCAzFsxHjGE=
|
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3 h1:kBz7M63hRetQnAnYK+gVmuSxsmZesX6xERphVgEn324=
|
maunium.net/go/mautrix v0.1.0-alpha.3 h1:kBz7M63hRetQnAnYK+gVmuSxsmZesX6xERphVgEn324=
|
||||||
maunium.net/go/mautrix v0.1.0-alpha.3/go.mod h1:GTVu6WDHR+98DKOrYetWsXorvUeKQV3jsSWO6ScbuFI=
|
maunium.net/go/mautrix v0.1.0-alpha.3/go.mod h1:GTVu6WDHR+98DKOrYetWsXorvUeKQV3jsSWO6ScbuFI=
|
||||||
maunium.net/go/tcell v0.0.0-20190111210611-542340841245 h1:JIekRWZ4na6cZJa5VMwLFpuiGzmeX+5Xx+WRVOHQHuk=
|
maunium.net/go/mauview v0.0.0-20190325223341-4c387be4b686 h1:kFgijToFPbMQGIMElizZGPQsffu+ZqO0olORXnfj1g4=
|
||||||
maunium.net/go/tcell v0.0.0-20190111210611-542340841245/go.mod h1:U+akxk8CP6vAWV74r2NOqEMMHw6kPGWTyvjzCtemxtM=
|
maunium.net/go/mauview v0.0.0-20190325223341-4c387be4b686/go.mod h1:Uw1CaNoCs9id/rKBF3Eg9KhhFVg+3akJTebZomFKW+4=
|
||||||
maunium.net/go/tcell v0.0.0-20190111212645-703b3f6ecec9 h1:aSZPhcBmGu8MIddPxWNksfjbXHmlRL0yUddwB9CP4s0=
|
|
||||||
maunium.net/go/tcell v0.0.0-20190111212645-703b3f6ecec9/go.mod h1:U+akxk8CP6vAWV74r2NOqEMMHw6kPGWTyvjzCtemxtM=
|
|
||||||
maunium.net/go/tcell v0.0.0-20190111223412-5e74142cb009 h1:4lojuJmNSun1nUB67m3DGg+RkYg1MUO6aUxgKQU5iZk=
|
maunium.net/go/tcell v0.0.0-20190111223412-5e74142cb009 h1:4lojuJmNSun1nUB67m3DGg+RkYg1MUO6aUxgKQU5iZk=
|
||||||
maunium.net/go/tcell v0.0.0-20190111223412-5e74142cb009/go.mod h1:U+akxk8CP6vAWV74r2NOqEMMHw6kPGWTyvjzCtemxtM=
|
maunium.net/go/tcell v0.0.0-20190111223412-5e74142cb009/go.mod h1:U+akxk8CP6vAWV74r2NOqEMMHw6kPGWTyvjzCtemxtM=
|
||||||
maunium.net/go/tview v0.0.0-20190111211351-2f23a5129af0 h1:xHG0S9ExKp+6dkhasnK/fgO9mLHSSSqVoAymjyUtDdI=
|
|
||||||
maunium.net/go/tview v0.0.0-20190111211351-2f23a5129af0/go.mod h1:ypYT6Dn71E7sVv6NxCjNo2cBJWJa257VSHCGOssGbV0=
|
|
||||||
maunium.net/go/tview v0.0.0-20190111212720-d6aa1eac1b9a h1:f4JVX4GHJH/wMcL9VACMVXT0eaWhxx9a7OT/NLcxsw8=
|
|
||||||
maunium.net/go/tview v0.0.0-20190111212720-d6aa1eac1b9a/go.mod h1:CTOF8OnDeK31Wl25GXdbYzTDvvZoiazmKNZdwkVUmzE=
|
|
||||||
maunium.net/go/tview v0.0.0-20190111223510-de38190b095b h1:misvyPolT0TVGAtjc9Lr+oEYqGsV4YDByJVqZHxuu70=
|
|
||||||
maunium.net/go/tview v0.0.0-20190111223510-de38190b095b/go.mod h1:Oi2eW32B8/cE7ZYXL6jyHMrXJL8ARDOQk/3aDvLEyVs=
|
|
||||||
|
@ -23,19 +23,18 @@ import (
|
|||||||
|
|
||||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FuzzySearchModal struct {
|
type FuzzySearchModal struct {
|
||||||
tview.Primitive
|
mauview.Component
|
||||||
|
|
||||||
search *tview.InputField
|
search *mauview.InputField
|
||||||
results *tview.TextView
|
results *mauview.TextView
|
||||||
|
|
||||||
matches fuzzy.Ranks
|
matches fuzzy.Ranks
|
||||||
selected int
|
selected int
|
||||||
@ -43,39 +42,39 @@ type FuzzySearchModal struct {
|
|||||||
roomList []*rooms.Room
|
roomList []*rooms.Room
|
||||||
roomTitles []string
|
roomTitles []string
|
||||||
|
|
||||||
parent *GomuksUI
|
parent *MainView
|
||||||
mainView *MainView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFuzzySearchModal(mainView *MainView, width int, height int) *FuzzySearchModal {
|
func NewFuzzySearchModal(mainView *MainView, width int, height int) *FuzzySearchModal {
|
||||||
fs := &FuzzySearchModal{
|
fs := &FuzzySearchModal{
|
||||||
parent: mainView.parent,
|
parent: mainView,
|
||||||
mainView: mainView,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.InitList(mainView.rooms)
|
fs.InitList(mainView.rooms)
|
||||||
|
|
||||||
fs.search = tview.NewInputField().
|
fs.search = mauview.NewInputField().SetChangedFunc(fs.changeHandler)
|
||||||
SetLabel("Room: ")
|
wrappedSearch := mauview.NewBox(fs.search).SetKeyCaptureFunc(fs.keyHandler)
|
||||||
fs.search.
|
searchLabel := mauview.NewTextField().SetText("Room")
|
||||||
SetChangedFunc(fs.changeHandler).
|
combinedSearch := mauview.NewFlex().
|
||||||
SetInputCapture(fs.keyHandler)
|
SetDirection(mauview.FlexColumn).
|
||||||
|
AddFixedComponent(searchLabel, 5).
|
||||||
|
AddProportionalComponent(wrappedSearch, 1)
|
||||||
|
|
||||||
fs.results = tview.NewTextView().
|
fs.results = mauview.NewTextView().SetRegions(true)
|
||||||
SetRegions(true)
|
|
||||||
fs.results.SetBorderPadding(1, 0, 0, 0)
|
|
||||||
|
|
||||||
// Flex widget containing input box and results
|
// Flex widget containing input box and results
|
||||||
container := tview.NewFlex().
|
container := mauview.NewBox(mauview.NewFlex().
|
||||||
SetDirection(tview.FlexRow).
|
SetDirection(mauview.FlexRow).
|
||||||
AddItem(fs.search, 1, 0, true).
|
AddFixedComponent(combinedSearch, 1).
|
||||||
AddItem(fs.results, 0, 1, false)
|
AddProportionalComponent(fs.results, 1)).
|
||||||
container.
|
|
||||||
SetBorder(true).
|
SetBorder(true).
|
||||||
SetBorderPadding(1, 1, 1, 1).
|
SetTitle("Quick Room Switcher").
|
||||||
SetTitle("Quick Room Switcher")
|
SetBlurCaptureFunc(func() bool {
|
||||||
|
fs.parent.HideModal()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
fs.Primitive = widget.TransparentCenter(width, height, container)
|
fs.Component = mauview.Center(container, width, height)
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
}
|
}
|
||||||
@ -96,7 +95,7 @@ func (fs *FuzzySearchModal) changeHandler(str string) {
|
|||||||
for _, match := range fs.matches {
|
for _, match := range fs.matches {
|
||||||
fmt.Fprintf(fs.results, `["%d"]%s[""]%s`, match.OriginalIndex, match.Target, "\n")
|
fmt.Fprintf(fs.results, `["%d"]%s[""]%s`, match.OriginalIndex, match.Target, "\n")
|
||||||
}
|
}
|
||||||
fs.parent.Render()
|
fs.parent.parent.Render()
|
||||||
fs.results.Highlight(strconv.Itoa(fs.matches[0].OriginalIndex))
|
fs.results.Highlight(strconv.Itoa(fs.matches[0].OriginalIndex))
|
||||||
fs.results.ScrollToBeginning()
|
fs.results.ScrollToBeginning()
|
||||||
} else {
|
} else {
|
||||||
@ -105,13 +104,12 @@ func (fs *FuzzySearchModal) changeHandler(str string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FuzzySearchModal) keyHandler(event *tcell.EventKey) *tcell.EventKey {
|
func (fs *FuzzySearchModal) keyHandler(event mauview.KeyEvent) mauview.KeyEvent {
|
||||||
highlights := fs.results.GetHighlights()
|
highlights := fs.results.GetHighlights()
|
||||||
switch event.Key() {
|
switch event.Key() {
|
||||||
case tcell.KeyEsc:
|
case tcell.KeyEsc:
|
||||||
// Close room finder
|
// Close room finder
|
||||||
fs.parent.views.RemovePage("fuzzy-search-modal")
|
fs.parent.HideModal()
|
||||||
fs.parent.app.SetFocus(fs.parent.views)
|
|
||||||
return nil
|
return nil
|
||||||
case tcell.KeyTab:
|
case tcell.KeyTab:
|
||||||
// Cycle highlighted area to next match
|
// Cycle highlighted area to next match
|
||||||
@ -125,10 +123,9 @@ func (fs *FuzzySearchModal) keyHandler(event *tcell.EventKey) *tcell.EventKey {
|
|||||||
// Switch room to currently selected room
|
// Switch room to currently selected room
|
||||||
if len(highlights) > 0 {
|
if len(highlights) > 0 {
|
||||||
debug.Print("Fuzzy Selected Room:", fs.roomList[fs.matches[fs.selected].OriginalIndex].GetTitle())
|
debug.Print("Fuzzy Selected Room:", fs.roomList[fs.matches[fs.selected].OriginalIndex].GetTitle())
|
||||||
fs.mainView.SwitchRoom(fs.roomList[fs.matches[fs.selected].OriginalIndex].Tags()[0].Tag, fs.roomList[fs.matches[fs.selected].OriginalIndex])
|
fs.parent.SwitchRoom(fs.roomList[fs.matches[fs.selected].OriginalIndex].Tags()[0].Tag, fs.roomList[fs.matches[fs.selected].OriginalIndex])
|
||||||
}
|
}
|
||||||
fs.parent.views.RemovePage("fuzzy-search-modal")
|
fs.parent.HideModal()
|
||||||
fs.parent.app.SetFocus(fs.parent.views)
|
|
||||||
fs.results.Clear()
|
fs.results.Clear()
|
||||||
fs.search.SetText("")
|
fs.search.SetText("")
|
||||||
return nil
|
return nil
|
||||||
|
@ -25,8 +25,8 @@ import (
|
|||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
@ -38,8 +38,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MessageView struct {
|
type MessageView struct {
|
||||||
*tview.Box
|
|
||||||
|
|
||||||
parent *RoomView
|
parent *RoomView
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
|
||||||
@ -51,6 +49,8 @@ type MessageView struct {
|
|||||||
LoadingMessages bool
|
LoadingMessages bool
|
||||||
|
|
||||||
widestSender int
|
widestSender int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
prevWidth int
|
prevWidth int
|
||||||
prevHeight int
|
prevHeight int
|
||||||
prevMsgCount int
|
prevMsgCount int
|
||||||
@ -65,7 +65,6 @@ type MessageView struct {
|
|||||||
|
|
||||||
func NewMessageView(parent *RoomView) *MessageView {
|
func NewMessageView(parent *RoomView) *MessageView {
|
||||||
return &MessageView{
|
return &MessageView{
|
||||||
Box: tview.NewBox(),
|
|
||||||
parent: parent,
|
parent: parent,
|
||||||
config: parent.config,
|
config: parent.config,
|
||||||
|
|
||||||
@ -174,7 +173,7 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
|
|||||||
|
|
||||||
view.updateWidestSender(message.Sender())
|
view.updateWidestSender(message.Sender())
|
||||||
|
|
||||||
_, _, width, _ := view.GetRect()
|
width := view.width
|
||||||
bare := view.config.Preferences.BareMessageView
|
bare := view.config.Preferences.BareMessageView
|
||||||
if !bare {
|
if !bare {
|
||||||
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
||||||
@ -267,15 +266,15 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) recalculateBuffers() {
|
func (view *MessageView) recalculateBuffers() {
|
||||||
_, _, width, height := view.GetRect()
|
|
||||||
prefs := view.config.Preferences
|
prefs := view.config.Preferences
|
||||||
if !prefs.BareMessageView {
|
recalculateMessageBuffers := view.width != view.prevWidth ||
|
||||||
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
|
||||||
}
|
|
||||||
recalculateMessageBuffers := width != view.prevWidth ||
|
|
||||||
view.prevPrefs.BareMessageView != prefs.BareMessageView ||
|
view.prevPrefs.BareMessageView != prefs.BareMessageView ||
|
||||||
view.prevPrefs.DisableImages != prefs.DisableImages
|
view.prevPrefs.DisableImages != prefs.DisableImages
|
||||||
if recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
|
if recalculateMessageBuffers || len(view.messages) != view.prevMsgCount {
|
||||||
|
width := view.width
|
||||||
|
if !prefs.BareMessageView {
|
||||||
|
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
|
||||||
|
}
|
||||||
view.textBuffer = []tstring.TString{}
|
view.textBuffer = []tstring.TString{}
|
||||||
view.metaBuffer = []ifc.MessageMeta{}
|
view.metaBuffer = []ifc.MessageMeta{}
|
||||||
view.prevMsgCount = 0
|
view.prevMsgCount = 0
|
||||||
@ -290,8 +289,8 @@ func (view *MessageView) recalculateBuffers() {
|
|||||||
view.appendBuffer(message)
|
view.appendBuffer(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.prevHeight = height
|
view.prevHeight = view.height
|
||||||
view.prevWidth = width
|
view.prevWidth = view.width
|
||||||
view.prevPrefs = prefs
|
view.prevPrefs = prefs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,13 +342,22 @@ func (view *MessageView) handleUsernameClick(message ifc.MessageMeta, prevMessag
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) HandleClick(x, y int, button tcell.ButtonMask) bool {
|
func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool {
|
||||||
if button != tcell.Button1 {
|
switch event.Buttons() {
|
||||||
return false
|
case tcell.WheelUp:
|
||||||
|
if view.IsAtTop() {
|
||||||
|
go view.parent.parent.LoadHistory(view.parent.Room.ID)
|
||||||
|
} else {
|
||||||
|
view.AddScrollOffset(WheelScrollOffsetDiff)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
case tcell.WheelDown:
|
||||||
_, _, _, height := view.GetRect()
|
view.AddScrollOffset(-WheelScrollOffsetDiff)
|
||||||
line := view.TotalHeight() - view.ScrollOffset - height + y
|
view.parent.parent.MarkRead(view.parent)
|
||||||
|
return true
|
||||||
|
case tcell.Button1:
|
||||||
|
x, y := event.Position()
|
||||||
|
line := view.TotalHeight() - view.ScrollOffset - view.height + y
|
||||||
if line < 0 || line >= view.TotalHeight() {
|
if line < 0 || line >= view.TotalHeight() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -363,30 +371,27 @@ func (view *MessageView) HandleClick(x, y int, button tcell.ButtonMask) bool {
|
|||||||
usernameX := view.TimestampWidth + TimestampSenderGap
|
usernameX := view.TimestampWidth + TimestampSenderGap
|
||||||
messageX := usernameX + view.widestSender + SenderMessageGap
|
messageX := usernameX + view.widestSender + SenderMessageGap
|
||||||
|
|
||||||
shouldRerender := false
|
|
||||||
if x >= messageX {
|
if x >= messageX {
|
||||||
shouldRerender = view.handleMessageClick(message)
|
return view.handleMessageClick(message)
|
||||||
} else if x >= usernameX {
|
} else if x >= usernameX {
|
||||||
shouldRerender = view.handleUsernameClick(message, prevMessage)
|
return view.handleUsernameClick(message, prevMessage)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return shouldRerender
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaddingAtTop = 5
|
const PaddingAtTop = 5
|
||||||
|
|
||||||
func (view *MessageView) AddScrollOffset(diff int) {
|
func (view *MessageView) AddScrollOffset(diff int) {
|
||||||
_, _, _, height := view.GetRect()
|
|
||||||
|
|
||||||
totalHeight := view.TotalHeight()
|
totalHeight := view.TotalHeight()
|
||||||
if diff >= 0 && view.ScrollOffset+diff >= totalHeight-height+PaddingAtTop {
|
if diff >= 0 && view.ScrollOffset+diff >= totalHeight-view.height+PaddingAtTop {
|
||||||
view.ScrollOffset = totalHeight - height + PaddingAtTop
|
view.ScrollOffset = totalHeight - view.height + PaddingAtTop
|
||||||
} else {
|
} else {
|
||||||
view.ScrollOffset += diff
|
view.ScrollOffset += diff
|
||||||
}
|
}
|
||||||
|
|
||||||
if view.ScrollOffset > totalHeight-height+PaddingAtTop {
|
if view.ScrollOffset > totalHeight-view.height+PaddingAtTop {
|
||||||
view.ScrollOffset = totalHeight - height + PaddingAtTop
|
view.ScrollOffset = totalHeight - view.height + PaddingAtTop
|
||||||
}
|
}
|
||||||
if view.ScrollOffset < 0 {
|
if view.ScrollOffset < 0 {
|
||||||
view.ScrollOffset = 0
|
view.ScrollOffset = 0
|
||||||
@ -394,8 +399,7 @@ func (view *MessageView) AddScrollOffset(diff int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) Height() int {
|
func (view *MessageView) Height() int {
|
||||||
_, _, _, height := view.GetRect()
|
return view.height
|
||||||
return height
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) TotalHeight() int {
|
func (view *MessageView) TotalHeight() int {
|
||||||
@ -403,9 +407,8 @@ func (view *MessageView) TotalHeight() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) IsAtTop() bool {
|
func (view *MessageView) IsAtTop() bool {
|
||||||
_, _, _, height := view.GetRect()
|
|
||||||
totalHeight := len(view.textBuffer)
|
totalHeight := len(view.textBuffer)
|
||||||
return view.ScrollOffset >= totalHeight-height+PaddingAtTop
|
return view.ScrollOffset >= totalHeight-view.height+PaddingAtTop
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -449,15 +452,14 @@ func (view *MessageView) calculateScrollBar(height int) (scrollBarHeight, scroll
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) getIndexOffset(screen tcell.Screen, height, messageX int) (indexOffset int) {
|
func (view *MessageView) getIndexOffset(screen mauview.Screen, height, messageX int) (indexOffset int) {
|
||||||
indexOffset = view.TotalHeight() - view.ScrollOffset - height
|
indexOffset = view.TotalHeight() - view.ScrollOffset - height
|
||||||
if indexOffset <= -PaddingAtTop {
|
if indexOffset <= -PaddingAtTop {
|
||||||
message := "Scroll up to load more messages."
|
message := "Scroll up to load more messages."
|
||||||
if view.LoadingMessages {
|
if view.LoadingMessages {
|
||||||
message = "Loading more messages..."
|
message = "Loading more messages..."
|
||||||
}
|
}
|
||||||
_, y, _, _ := view.GetRect()
|
widget.WriteLineSimpleColor(screen, message, messageX, 0, tcell.ColorGreen)
|
||||||
widget.WriteLineSimpleColor(screen, message, messageX, y, tcell.ColorGreen)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -488,25 +490,25 @@ func (view *MessageView) CapturePlaintext(height int) string {
|
|||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) Draw(screen tcell.Screen) {
|
func (view *MessageView) Draw(screen mauview.Screen) {
|
||||||
x, y, _, height := view.GetRect()
|
view.width, view.height = screen.Size()
|
||||||
view.recalculateBuffers()
|
view.recalculateBuffers()
|
||||||
|
|
||||||
if view.TotalHeight() == 0 {
|
if view.TotalHeight() == 0 {
|
||||||
widget.WriteLineSimple(screen, "It's quite empty in here.", x, y+height)
|
widget.WriteLineSimple(screen, "It's quite empty in here.", 0, view.height)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameX := x + view.TimestampWidth + TimestampSenderGap
|
usernameX := view.TimestampWidth + TimestampSenderGap
|
||||||
messageX := usernameX + view.widestSender + SenderMessageGap
|
messageX := usernameX + view.widestSender + SenderMessageGap
|
||||||
separatorX := usernameX + view.widestSender + SenderSeparatorGap
|
separatorX := usernameX + view.widestSender + SenderSeparatorGap
|
||||||
|
|
||||||
bareMode := view.config.Preferences.BareMessageView
|
bareMode := view.config.Preferences.BareMessageView
|
||||||
if bareMode {
|
if bareMode {
|
||||||
messageX = x
|
messageX = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
indexOffset := view.getIndexOffset(screen, height, messageX)
|
indexOffset := view.getIndexOffset(screen, view.height, messageX)
|
||||||
|
|
||||||
if len(view.textBuffer) != len(view.metaBuffer) {
|
if len(view.textBuffer) != len(view.metaBuffer) {
|
||||||
debug.Printf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer))
|
debug.Printf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer))
|
||||||
@ -514,13 +516,13 @@ func (view *MessageView) Draw(screen tcell.Screen) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollBarHeight, scrollBarPos := view.calculateScrollBar(height)
|
scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height)
|
||||||
|
|
||||||
var prevMeta ifc.MessageMeta
|
var prevMeta ifc.MessageMeta
|
||||||
firstLine := true
|
firstLine := true
|
||||||
skippedLines := 0
|
skippedLines := 0
|
||||||
|
|
||||||
for line := 0; line < height; line++ {
|
for line := 0; line < view.height; line++ {
|
||||||
index := indexOffset + line
|
index := indexOffset + line
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
skippedLines++
|
skippedLines++
|
||||||
@ -530,31 +532,32 @@ func (view *MessageView) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showScrollbar := line-skippedLines >= scrollBarPos-scrollBarHeight && line-skippedLines < scrollBarPos
|
showScrollbar := line-skippedLines >= scrollBarPos-scrollBarHeight && line-skippedLines < scrollBarPos
|
||||||
isTop := firstLine && view.ScrollOffset+height >= view.TotalHeight()
|
isTop := firstLine && view.ScrollOffset+view.height >= view.TotalHeight()
|
||||||
isBottom := line == height-1 && view.ScrollOffset == 0
|
isBottom := line == view.height-1 && view.ScrollOffset == 0
|
||||||
|
|
||||||
borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom)
|
borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom)
|
||||||
|
|
||||||
firstLine = false
|
firstLine = false
|
||||||
|
|
||||||
if !bareMode {
|
if !bareMode {
|
||||||
screen.SetContent(separatorX, y+line, borderChar, nil, borderStyle)
|
screen.SetContent(separatorX, line, borderChar, nil, borderStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
text, meta := view.textBuffer[index], view.metaBuffer[index]
|
text, meta := view.textBuffer[index], view.metaBuffer[index]
|
||||||
if meta != prevMeta {
|
if meta != prevMeta {
|
||||||
if len(meta.FormatTime()) > 0 {
|
if len(meta.FormatTime()) > 0 {
|
||||||
widget.WriteLineSimpleColor(screen, meta.FormatTime(), x, y+line, meta.TimestampColor())
|
widget.WriteLineSimpleColor(screen, meta.FormatTime(), 0, line, meta.TimestampColor())
|
||||||
}
|
}
|
||||||
if !bareMode && (prevMeta == nil || meta.Sender() != prevMeta.Sender()) {
|
if !bareMode && (prevMeta == nil || meta.Sender() != prevMeta.Sender()) {
|
||||||
widget.WriteLineColor(
|
widget.WriteLineColor(
|
||||||
screen, tview.AlignRight, meta.Sender(),
|
screen, mauview.AlignRight, meta.Sender(),
|
||||||
usernameX, y+line, view.widestSender,
|
usernameX, line, view.widestSender,
|
||||||
meta.SenderColor())
|
meta.SenderColor())
|
||||||
}
|
}
|
||||||
prevMeta = meta
|
prevMeta = meta
|
||||||
}
|
}
|
||||||
|
|
||||||
text.Draw(screen, messageX, y+line)
|
text.Draw(screen, messageX, line)
|
||||||
}
|
}
|
||||||
|
debug.Print(screen)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ func (msg *ImageMessage) updateData() {
|
|||||||
debug.Print("Loading image:", msg.Homeserver, msg.FileID)
|
debug.Print("Loading image:", msg.Homeserver, msg.FileID)
|
||||||
data, _, _, err := msg.matrix.Download(fmt.Sprintf("mxc://%s/%s", msg.Homeserver, msg.FileID))
|
data, _, _, err := msg.matrix.Download(fmt.Sprintf("mxc://%s/%s", msg.Homeserver, msg.FileID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to download image %s/%s: %v", msg.Homeserver, msg.FileID, err)
|
debug.Printf("Failed to download image %s/%s: %v", msg.Homeserver, msg.FileID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debug.Print("Image", msg.Homeserver, msg.FileID, "loaded.")
|
debug.Print("Image", msg.Homeserver, msg.FileID, "loaded.")
|
||||||
|
@ -102,7 +102,12 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Eve
|
|||||||
}
|
}
|
||||||
if len(evt.Content.GetReplyTo()) > 0 {
|
if len(evt.Content.GetReplyTo()) > 0 {
|
||||||
evt.Content.RemoveReplyFallback()
|
evt.Content.RemoveReplyFallback()
|
||||||
replyToEvt, _ := matrix.Client().GetEvent(room.ID, evt.Content.GetReplyTo())
|
roomID := evt.Content.RelatesTo.InReplyTo.RoomID
|
||||||
|
if len(roomID) == 0 {
|
||||||
|
roomID = room.ID
|
||||||
|
}
|
||||||
|
replyToEvt, _ := matrix.Client().GetEvent(roomID, evt.Content.GetReplyTo())
|
||||||
|
if replyToEvt != nil {
|
||||||
replyToEvt.Content.RemoveReplyFallback()
|
replyToEvt.Content.RemoveReplyFallback()
|
||||||
if len(replyToEvt.Content.FormattedBody) == 0 {
|
if len(replyToEvt.Content.FormattedBody) == 0 {
|
||||||
replyToEvt.Content.FormattedBody = html.EscapeString(replyToEvt.Content.Body)
|
replyToEvt.Content.FormattedBody = html.EscapeString(replyToEvt.Content.Body)
|
||||||
@ -110,6 +115,11 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Eve
|
|||||||
evt.Content.FormattedBody = fmt.Sprintf(
|
evt.Content.FormattedBody = fmt.Sprintf(
|
||||||
"In reply to <a href='https://matrix.to/#/%[1]s'>%[1]s</a><blockquote>%[2]s</blockquote><br/>%[3]s",
|
"In reply to <a href='https://matrix.to/#/%[1]s'>%[1]s</a><blockquote>%[2]s</blockquote><br/>%[3]s",
|
||||||
replyToEvt.Sender, replyToEvt.Content.FormattedBody, evt.Content.FormattedBody)
|
replyToEvt.Sender, replyToEvt.Content.FormattedBody, evt.Content.FormattedBody)
|
||||||
|
} else {
|
||||||
|
evt.Content.FormattedBody = fmt.Sprintf(
|
||||||
|
"In reply to unknown event https://matrix.to/#/%[1]s/%[2]s<br/>%[3]s",
|
||||||
|
roomID, evt.Content.GetReplyTo(), evt.Content.FormattedBody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ts := unixToTime(evt.Timestamp)
|
ts := unixToTime(evt.Timestamp)
|
||||||
switch evt.Content.MsgType {
|
switch evt.Content.MsgType {
|
||||||
|
@ -18,6 +18,7 @@ package tstring
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
@ -43,7 +44,7 @@ func (cell Cell) RuneWidth() int {
|
|||||||
return runewidth.RuneWidth(cell.Char)
|
return runewidth.RuneWidth(cell.Char)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell Cell) Draw(screen tcell.Screen, x, y int) (chWidth int) {
|
func (cell Cell) Draw(screen mauview.Screen, x, y int) (chWidth int) {
|
||||||
chWidth = cell.RuneWidth()
|
chWidth = cell.RuneWidth()
|
||||||
for runeWidthOffset := 0; runeWidthOffset < chWidth; runeWidthOffset++ {
|
for runeWidthOffset := 0; runeWidthOffset < chWidth; runeWidthOffset++ {
|
||||||
screen.SetContent(x+runeWidthOffset, y, cell.Char, nil, cell.Style)
|
screen.SetContent(x+runeWidthOffset, y, cell.Char, nil, cell.Style)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
@ -181,7 +182,7 @@ func (str TString) AdjustStyleFull(fn func(tcell.Style) tcell.Style) {
|
|||||||
str.AdjustStyle(0, len(str), fn)
|
str.AdjustStyle(0, len(str), fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (str TString) Draw(screen tcell.Screen, x, y int) {
|
func (str TString) Draw(screen mauview.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)
|
||||||
|
@ -21,15 +21,15 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RoomList struct {
|
type RoomList struct {
|
||||||
*tview.Box
|
parent *MainView
|
||||||
|
|
||||||
// The list of tags in display order.
|
// The list of tags in display order.
|
||||||
tags []string
|
tags []string
|
||||||
@ -40,6 +40,8 @@ type RoomList struct {
|
|||||||
selectedTag string
|
selectedTag string
|
||||||
|
|
||||||
scrollOffset int
|
scrollOffset int
|
||||||
|
height int
|
||||||
|
width int
|
||||||
|
|
||||||
// The item main text color.
|
// The item main text color.
|
||||||
mainTextColor tcell.Color
|
mainTextColor tcell.Color
|
||||||
@ -49,9 +51,10 @@ type RoomList struct {
|
|||||||
selectedBackgroundColor tcell.Color
|
selectedBackgroundColor tcell.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoomList() *RoomList {
|
func NewRoomList(parent *MainView) *RoomList {
|
||||||
list := &RoomList{
|
list := &RoomList{
|
||||||
Box: tview.NewBox(),
|
parent: parent,
|
||||||
|
|
||||||
items: make(map[string]*TagRoomList),
|
items: make(map[string]*TagRoomList),
|
||||||
tags: []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"},
|
tags: []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"},
|
||||||
|
|
||||||
@ -182,11 +185,10 @@ func (list *RoomList) SetSelected(tag string, room *rooms.Room) {
|
|||||||
list.selected = room
|
list.selected = room
|
||||||
list.selectedTag = tag
|
list.selectedTag = tag
|
||||||
pos := list.index(tag, room)
|
pos := list.index(tag, room)
|
||||||
_, _, _, height := list.GetRect()
|
|
||||||
if pos <= list.scrollOffset {
|
if pos <= list.scrollOffset {
|
||||||
list.scrollOffset = pos - 1
|
list.scrollOffset = pos - 1
|
||||||
} else if pos >= list.scrollOffset+height {
|
} else if pos >= list.scrollOffset+list.height {
|
||||||
list.scrollOffset = pos - height + 1
|
list.scrollOffset = pos - list.height + 1
|
||||||
}
|
}
|
||||||
if list.scrollOffset < 0 {
|
if list.scrollOffset < 0 {
|
||||||
list.scrollOffset = 0
|
list.scrollOffset = 0
|
||||||
@ -208,10 +210,9 @@ func (list *RoomList) SelectedRoom() *rooms.Room {
|
|||||||
|
|
||||||
func (list *RoomList) AddScrollOffset(offset int) {
|
func (list *RoomList) AddScrollOffset(offset int) {
|
||||||
list.scrollOffset += offset
|
list.scrollOffset += offset
|
||||||
_, _, _, viewHeight := list.GetRect()
|
|
||||||
contentHeight := list.ContentHeight()
|
contentHeight := list.ContentHeight()
|
||||||
if list.scrollOffset > contentHeight-viewHeight {
|
if list.scrollOffset > contentHeight-list.height {
|
||||||
list.scrollOffset = contentHeight - viewHeight
|
list.scrollOffset = contentHeight - list.height
|
||||||
}
|
}
|
||||||
if list.scrollOffset < 0 {
|
if list.scrollOffset < 0 {
|
||||||
list.scrollOffset = 0
|
list.scrollOffset = 0
|
||||||
@ -372,10 +373,39 @@ func (list *RoomList) ContentHeight() (height int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Room) {
|
func (list *RoomList) OnKeyEvent(event mauview.KeyEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *RoomList) OnPasteEvent(event mauview.PasteEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *RoomList) OnMouseEvent(event mauview.MouseEvent) bool {
|
||||||
|
switch event.Buttons() {
|
||||||
|
case tcell.WheelUp:
|
||||||
|
list.AddScrollOffset(-WheelScrollOffsetDiff)
|
||||||
|
case tcell.WheelDown:
|
||||||
|
list.AddScrollOffset(WheelScrollOffsetDiff)
|
||||||
|
case tcell.Button1:
|
||||||
|
x, y := event.Position()
|
||||||
|
return list.clickRoom(y, x, event.Modifiers() == tcell.ModCtrl)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *RoomList) Focus() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *RoomList) Blur() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *RoomList) clickRoom(line, column int, mod bool) bool {
|
||||||
line += list.scrollOffset
|
line += list.scrollOffset
|
||||||
if line < 0 {
|
if line < 0 {
|
||||||
return "", nil
|
return false
|
||||||
}
|
}
|
||||||
for _, tag := range list.tags {
|
for _, tag := range list.tags {
|
||||||
trl := list.items[tag]
|
trl := list.items[tag]
|
||||||
@ -391,7 +421,8 @@ func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Ro
|
|||||||
if line < 0 {
|
if line < 0 {
|
||||||
break
|
break
|
||||||
} else if line < trl.Length() {
|
} else if line < trl.Length() {
|
||||||
return tag, trl.Visible()[trl.Length()-1-line].Room
|
list.parent.SwitchRoom(tag, trl.Visible()[trl.Length()-1-line].Room)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag items
|
// Tag items
|
||||||
@ -405,10 +436,9 @@ func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Ro
|
|||||||
if mod {
|
if mod {
|
||||||
diff = 100
|
diff = 100
|
||||||
}
|
}
|
||||||
_, _, width, _ := list.GetRect()
|
|
||||||
if column <= 6 && hasLess {
|
if column <= 6 && hasLess {
|
||||||
trl.maxShown -= diff
|
trl.maxShown -= diff
|
||||||
} else if column >= width-6 && hasMore {
|
} else if column >= list.width-6 && hasMore {
|
||||||
trl.maxShown += diff
|
trl.maxShown += diff
|
||||||
}
|
}
|
||||||
if trl.maxShown < 10 {
|
if trl.maxShown < 10 {
|
||||||
@ -420,9 +450,8 @@ func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Ro
|
|||||||
// Tag footer
|
// Tag footer
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
return "", nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var nsRegex = regexp.MustCompile("^[a-z]\\.[a-z](?:\\.[a-z])*$")
|
var nsRegex = regexp.MustCompile("^[a-z]\\.[a-z](?:\\.[a-z])*$")
|
||||||
|
|
||||||
func (list *RoomList) GetTagDisplayName(tag string) string {
|
func (list *RoomList) GetTagDisplayName(tag string) string {
|
||||||
@ -445,11 +474,10 @@ func (list *RoomList) GetTagDisplayName(tag string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws this primitive onto the screen.
|
// Draw draws this primitive onto the screen.
|
||||||
func (list *RoomList) Draw(screen tcell.Screen) {
|
func (list *RoomList) Draw(screen mauview.Screen) {
|
||||||
list.Box.Draw(screen)
|
list.width, list.height = screen.Size()
|
||||||
|
y := 0
|
||||||
x, y, width, height := list.GetRect()
|
yLimit := y + list.height
|
||||||
yLimit := y + height
|
|
||||||
y -= list.scrollOffset
|
y -= list.scrollOffset
|
||||||
|
|
||||||
// Draw the list items.
|
// Draw the list items.
|
||||||
@ -464,8 +492,7 @@ func (list *RoomList) Draw(screen tcell.Screen) {
|
|||||||
if y+renderHeight >= yLimit {
|
if y+renderHeight >= yLimit {
|
||||||
renderHeight = yLimit - y
|
renderHeight = yLimit - y
|
||||||
}
|
}
|
||||||
trl.SetRect(x, y, width, renderHeight)
|
trl.Draw(mauview.NewProxyScreen(screen, 0, y, list.width, renderHeight))
|
||||||
trl.Draw(screen)
|
|
||||||
y += renderHeight
|
y += renderHeight
|
||||||
if y >= yLimit {
|
if y >= yLimit {
|
||||||
break
|
break
|
||||||
|
167
ui/room-view.go
167
ui/room-view.go
@ -26,9 +26,10 @@ import (
|
|||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
@ -39,16 +40,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RoomView struct {
|
type RoomView struct {
|
||||||
*tview.Box
|
topic *mauview.TextView
|
||||||
|
|
||||||
topic *tview.TextView
|
|
||||||
content *MessageView
|
content *MessageView
|
||||||
status *tview.TextView
|
status *mauview.TextField
|
||||||
userList *tview.TextView
|
userList *mauview.TextView
|
||||||
ulBorder *widget.Border
|
ulBorder *widget.Border
|
||||||
input *widget.AdvancedInputField
|
input *mauview.InputArea
|
||||||
Room *rooms.Room
|
Room *rooms.Room
|
||||||
|
|
||||||
|
topicScreen *mauview.ProxyScreen
|
||||||
|
contentScreen *mauview.ProxyScreen
|
||||||
|
statusScreen *mauview.ProxyScreen
|
||||||
|
inputScreen *mauview.ProxyScreen
|
||||||
|
ulBorderScreen *mauview.ProxyScreen
|
||||||
|
ulScreen *mauview.ProxyScreen
|
||||||
|
|
||||||
|
prevScreen mauview.Screen
|
||||||
|
|
||||||
parent *MainView
|
parent *MainView
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
|
||||||
@ -63,27 +71,34 @@ type RoomView struct {
|
|||||||
|
|
||||||
func NewRoomView(parent *MainView, room *rooms.Room) *RoomView {
|
func NewRoomView(parent *MainView, room *rooms.Room) *RoomView {
|
||||||
view := &RoomView{
|
view := &RoomView{
|
||||||
Box: tview.NewBox(),
|
topic: mauview.NewTextView(),
|
||||||
topic: tview.NewTextView(),
|
status: mauview.NewTextField(),
|
||||||
status: tview.NewTextView(),
|
userList: mauview.NewTextView(),
|
||||||
userList: tview.NewTextView(),
|
|
||||||
ulBorder: widget.NewBorder(),
|
ulBorder: widget.NewBorder(),
|
||||||
input: widget.NewAdvancedInputField(),
|
input: mauview.NewInputArea(),
|
||||||
Room: room,
|
Room: room,
|
||||||
|
|
||||||
|
topicScreen: &mauview.ProxyScreen{OffsetX: 0, OffsetY: 0, Height: TopicBarHeight},
|
||||||
|
contentScreen: &mauview.ProxyScreen{OffsetX: 0, OffsetY: StatusBarHeight},
|
||||||
|
statusScreen: &mauview.ProxyScreen{OffsetX: 0, Height: StatusBarHeight},
|
||||||
|
inputScreen: &mauview.ProxyScreen{OffsetX: 0},
|
||||||
|
ulBorderScreen: &mauview.ProxyScreen{OffsetY: StatusBarHeight, Width: UserListBorderWidth},
|
||||||
|
ulScreen: &mauview.ProxyScreen{OffsetY: StatusBarHeight, Width: UserListWidth},
|
||||||
|
|
||||||
parent: parent,
|
parent: parent,
|
||||||
config: parent.config,
|
config: parent.config,
|
||||||
}
|
}
|
||||||
view.content = NewMessageView(view)
|
view.content = NewMessageView(view)
|
||||||
|
|
||||||
view.input.
|
view.input.
|
||||||
SetFieldBackgroundColor(tcell.ColorDefault).
|
SetBackgroundColor(tcell.ColorDefault).
|
||||||
SetPlaceholder("Send a message...").
|
SetPlaceholder("Send a message...").
|
||||||
SetPlaceholderExtColor(tcell.ColorGray).
|
SetPlaceholderTextColor(tcell.ColorGray).
|
||||||
SetTabCompleteFunc(view.InputTabComplete)
|
SetTabCompleteFunc(view.InputTabComplete)
|
||||||
|
|
||||||
view.topic.
|
view.topic.
|
||||||
SetText(strings.Replace(room.GetTopic(), "\n", " ", -1)).
|
SetText(strings.Replace(room.GetTopic(), "\n", " ", -1)).
|
||||||
SetBackgroundColor(tcell.ColorDarkGreen)
|
SetTextColor(tcell.ColorDarkGreen)
|
||||||
|
|
||||||
view.status.SetBackgroundColor(tcell.ColorDimGray)
|
view.status.SetBackgroundColor(tcell.ColorDimGray)
|
||||||
|
|
||||||
@ -106,26 +121,13 @@ func (view *RoomView) LoadHistory(matrix ifc.MatrixContainer, dir string) (int,
|
|||||||
return view.MessageView().LoadHistory(matrix, view.logPath(dir))
|
return view.MessageView().LoadHistory(matrix, view.logPath(dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SetInputCapture(fn func(room *RoomView, event *tcell.EventKey) *tcell.EventKey) *RoomView {
|
|
||||||
view.input.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
||||||
return fn(view, event)
|
|
||||||
})
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *RoomView) SetMouseCapture(fn func(room *RoomView, event *tcell.EventMouse) *tcell.EventMouse) *RoomView {
|
|
||||||
view.input.SetMouseCapture(func(event *tcell.EventMouse) *tcell.EventMouse {
|
|
||||||
return fn(view, event)
|
|
||||||
})
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *RoomView) SetInputSubmitFunc(fn func(room *RoomView, text string)) *RoomView {
|
func (view *RoomView) SetInputSubmitFunc(fn func(room *RoomView, text string)) *RoomView {
|
||||||
view.input.SetDoneFunc(func(key tcell.Key) {
|
// FIXME
|
||||||
|
/*view.input.SetDoneFunc(func(key tcell.Key) {
|
||||||
if key == tcell.KeyEnter {
|
if key == tcell.KeyEnter {
|
||||||
fn(view, view.input.GetText())
|
fn(view, view.input.GetText())
|
||||||
}
|
}
|
||||||
})
|
})*/
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +139,7 @@ func (view *RoomView) SetInputChangedFunc(fn func(room *RoomView, text string))
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SetInputText(newText string) *RoomView {
|
func (view *RoomView) SetInputText(newText string) *RoomView {
|
||||||
view.input.SetText(newText)
|
view.input.SetTextAndMoveCursor(newText)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,12 +147,12 @@ func (view *RoomView) GetInputText() string {
|
|||||||
return view.input.GetText()
|
return view.input.GetText()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) GetInputField() *widget.AdvancedInputField {
|
func (view *RoomView) Focus() {
|
||||||
return view.input
|
view.input.Focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) Focus(delegate func(p tview.Primitive)) {
|
func (view *RoomView) Blur() {
|
||||||
delegate(view.input)
|
view.input.Blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) GetStatus() string {
|
func (view *RoomView) GetStatus() string {
|
||||||
@ -169,7 +171,7 @@ func (view *RoomView) GetStatus() string {
|
|||||||
buf.WriteString("Typing: " + view.typing[0])
|
buf.WriteString("Typing: " + view.typing[0])
|
||||||
buf.WriteString(" - ")
|
buf.WriteString(" - ")
|
||||||
} else if len(view.typing) > 1 {
|
} else if len(view.typing) > 1 {
|
||||||
fmt.Fprintf(&buf,
|
_, _ = fmt.Fprintf(&buf,
|
||||||
"Typing: %s and %s - ",
|
"Typing: %s and %s - ",
|
||||||
strings.Join(view.typing[:len(view.typing)-1], ", "), view.typing[len(view.typing)-1])
|
strings.Join(view.typing[:len(view.typing)-1], ", "), view.typing[len(view.typing)-1])
|
||||||
}
|
}
|
||||||
@ -177,14 +179,8 @@ func (view *RoomView) GetStatus() string {
|
|||||||
return strings.TrimSuffix(buf.String(), " - ")
|
return strings.TrimSuffix(buf.String(), " - ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) Draw(screen tcell.Screen) {
|
// Constants defining the size of the room view grid.
|
||||||
x, y, width, height := view.GetRect()
|
const (
|
||||||
if width <= 0 || height <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants defining the size of the room view grid.
|
|
||||||
const (
|
|
||||||
UserListBorderWidth = 1
|
UserListBorderWidth = 1
|
||||||
UserListWidth = 20
|
UserListWidth = 20
|
||||||
StaticHorizontalSpace = UserListBorderWidth + UserListWidth
|
StaticHorizontalSpace = UserListBorderWidth + UserListWidth
|
||||||
@ -193,48 +189,81 @@ func (view *RoomView) Draw(screen tcell.Screen) {
|
|||||||
StatusBarHeight = 1
|
StatusBarHeight = 1
|
||||||
InputBarHeight = 1
|
InputBarHeight = 1
|
||||||
StaticVerticalSpace = TopicBarHeight + StatusBarHeight + InputBarHeight
|
StaticVerticalSpace = TopicBarHeight + StatusBarHeight + InputBarHeight
|
||||||
)
|
|
||||||
|
MaxInputHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
func (view *RoomView) Draw(screen mauview.Screen) {
|
||||||
|
width, height := screen.Size()
|
||||||
|
if width <= 0 || height <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate actual grid based on view rectangle and constants defined above.
|
// Calculate actual grid based on view rectangle and constants defined above.
|
||||||
var (
|
var (
|
||||||
contentHeight = height - StaticVerticalSpace
|
contentHeight = height - StaticVerticalSpace
|
||||||
contentWidth = width - StaticHorizontalSpace
|
contentWidth = width - StaticHorizontalSpace
|
||||||
|
|
||||||
userListBorderColumn = x + contentWidth
|
|
||||||
userListColumn = userListBorderColumn + UserListBorderWidth
|
|
||||||
|
|
||||||
topicRow = y
|
|
||||||
contentRow = topicRow + TopicBarHeight
|
|
||||||
statusRow = contentRow + contentHeight
|
|
||||||
inputRow = statusRow + StatusBarHeight
|
|
||||||
)
|
)
|
||||||
if view.config.Preferences.HideUserList {
|
if view.config.Preferences.HideUserList {
|
||||||
contentWidth = width
|
contentWidth = width
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the rectangles of all the children.
|
if view.prevScreen != screen {
|
||||||
view.topic.SetRect(x, topicRow, width, TopicBarHeight)
|
view.topicScreen.Parent = screen
|
||||||
view.content.SetRect(x, contentRow, contentWidth, contentHeight)
|
view.contentScreen.Parent = screen
|
||||||
view.status.SetRect(x, statusRow, width, StatusBarHeight)
|
view.statusScreen.Parent = screen
|
||||||
if !view.config.Preferences.HideUserList && userListColumn > x {
|
view.inputScreen.Parent = screen
|
||||||
view.userList.SetRect(userListColumn, contentRow, UserListWidth, contentHeight)
|
view.ulBorderScreen.Parent = screen
|
||||||
view.ulBorder.SetRect(userListBorderColumn, contentRow, UserListBorderWidth, contentHeight)
|
view.ulScreen.Parent = screen
|
||||||
|
view.prevScreen = screen
|
||||||
}
|
}
|
||||||
view.input.SetRect(x, inputRow, width, InputBarHeight)
|
|
||||||
|
view.input.PrepareDraw(width)
|
||||||
|
inputHeight := view.input.GetTextHeight()
|
||||||
|
if inputHeight > MaxInputHeight {
|
||||||
|
inputHeight = MaxInputHeight
|
||||||
|
} else if inputHeight < 1 {
|
||||||
|
inputHeight = 1
|
||||||
|
}
|
||||||
|
contentHeight -= inputHeight
|
||||||
|
|
||||||
|
view.topicScreen.Width = width
|
||||||
|
view.contentScreen.Width = contentWidth
|
||||||
|
view.contentScreen.Height = contentHeight
|
||||||
|
view.statusScreen.OffsetY = view.contentScreen.YEnd()
|
||||||
|
view.statusScreen.Width = width
|
||||||
|
view.inputScreen.Width = width
|
||||||
|
view.inputScreen.OffsetY = view.statusScreen.YEnd()
|
||||||
|
view.inputScreen.Height = inputHeight
|
||||||
|
view.ulBorderScreen.OffsetX = view.contentScreen.XEnd()
|
||||||
|
view.ulBorderScreen.Height = contentHeight
|
||||||
|
view.ulScreen.OffsetX = view.ulBorderScreen.XEnd()
|
||||||
|
view.ulScreen.Height = contentHeight
|
||||||
|
|
||||||
// Draw everything
|
// Draw everything
|
||||||
view.Box.Draw(screen)
|
view.topic.Draw(view.topicScreen)
|
||||||
view.topic.Draw(screen)
|
view.content.Draw(view.contentScreen)
|
||||||
view.content.Draw(screen)
|
|
||||||
view.status.SetText(view.GetStatus())
|
view.status.SetText(view.GetStatus())
|
||||||
view.status.Draw(screen)
|
view.status.Draw(view.statusScreen)
|
||||||
view.input.Draw(screen)
|
view.input.Draw(view.inputScreen)
|
||||||
if !view.config.Preferences.HideUserList {
|
if !view.config.Preferences.HideUserList {
|
||||||
view.ulBorder.Draw(screen)
|
view.ulBorder.Draw(view.ulBorderScreen)
|
||||||
view.userList.Draw(screen)
|
view.userList.Draw(view.ulScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) OnKeyEvent(event mauview.KeyEvent) bool {
|
||||||
|
return view.input.OnKeyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) OnPasteEvent(event mauview.PasteEvent) bool {
|
||||||
|
return view.input.OnPasteEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) OnMouseEvent(event mauview.MouseEvent) bool {
|
||||||
|
return view.content.OnMouseEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
func (view *RoomView) SetCompletions(completions []string) {
|
func (view *RoomView) SetCompletions(completions []string) {
|
||||||
view.completions.list = completions
|
view.completions.list = completions
|
||||||
view.completions.textCache = view.input.GetText()
|
view.completions.textCache = view.input.GetText()
|
||||||
|
@ -21,8 +21,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
@ -44,7 +44,7 @@ func NewDefaultOrderedRoom(room *rooms.Room) *OrderedRoom {
|
|||||||
return NewOrderedRoom("0.5", room)
|
return NewOrderedRoom("0.5", room)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (or *OrderedRoom) Draw(roomList *RoomList, screen tcell.Screen, x, y, lineWidth int, isSelected bool) {
|
func (or *OrderedRoom) Draw(roomList *RoomList, screen mauview.Screen, x, y, lineWidth int, isSelected bool) {
|
||||||
style := tcell.StyleDefault.
|
style := tcell.StyleDefault.
|
||||||
Foreground(roomList.mainTextColor).
|
Foreground(roomList.mainTextColor).
|
||||||
Bold(or.HasNewMessages())
|
Bold(or.HasNewMessages())
|
||||||
@ -56,7 +56,7 @@ func (or *OrderedRoom) Draw(roomList *RoomList, screen tcell.Screen, x, y, lineW
|
|||||||
|
|
||||||
unreadCount := or.UnreadCount()
|
unreadCount := or.UnreadCount()
|
||||||
|
|
||||||
widget.WriteLinePadded(screen, tview.AlignLeft, or.GetTitle(), x, y, lineWidth, style)
|
widget.WriteLinePadded(screen, mauview.AlignLeft, or.GetTitle(), x, y, lineWidth, style)
|
||||||
|
|
||||||
if unreadCount > 0 {
|
if unreadCount > 0 {
|
||||||
unreadMessageCount := "99+"
|
unreadMessageCount := "99+"
|
||||||
@ -67,13 +67,13 @@ func (or *OrderedRoom) Draw(roomList *RoomList, screen tcell.Screen, x, y, lineW
|
|||||||
unreadMessageCount += "!"
|
unreadMessageCount += "!"
|
||||||
}
|
}
|
||||||
unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
|
unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
|
||||||
widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-7, y, 7, style)
|
widget.WriteLine(screen, mauview.AlignRight, unreadMessageCount, x+lineWidth-7, y, 7, style)
|
||||||
lineWidth -= len(unreadMessageCount)
|
lineWidth -= len(unreadMessageCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagRoomList struct {
|
type TagRoomList struct {
|
||||||
*tview.Box
|
mauview.NoopEventHandler
|
||||||
rooms []*OrderedRoom
|
rooms []*OrderedRoom
|
||||||
maxShown int
|
maxShown int
|
||||||
name string
|
name string
|
||||||
@ -83,7 +83,6 @@ type TagRoomList struct {
|
|||||||
|
|
||||||
func NewTagRoomList(parent *RoomList, name string, rooms ...*OrderedRoom) *TagRoomList {
|
func NewTagRoomList(parent *RoomList, name string, rooms ...*OrderedRoom) *TagRoomList {
|
||||||
return &TagRoomList{
|
return &TagRoomList{
|
||||||
Box: tview.NewBox(),
|
|
||||||
maxShown: 10,
|
maxShown: 10,
|
||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
name: name,
|
name: name,
|
||||||
@ -246,41 +245,40 @@ func (trl *TagRoomList) RenderHeight() int {
|
|||||||
return height
|
return height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (trl *TagRoomList) DrawHeader(screen tcell.Screen) {
|
func (trl *TagRoomList) DrawHeader(screen mauview.Screen) {
|
||||||
x, y, width, _ := trl.GetRect()
|
width, _ := screen.Size()
|
||||||
roomCount := strconv.Itoa(trl.TotalLength())
|
roomCount := strconv.Itoa(trl.TotalLength())
|
||||||
|
|
||||||
// Draw tag name
|
// Draw tag name
|
||||||
displayNameWidth := width - 1 - len(roomCount)
|
displayNameWidth := width - 1 - len(roomCount)
|
||||||
widget.WriteLine(screen, tview.AlignLeft, trl.displayname, x, y, displayNameWidth, TagDisplayNameStyle)
|
widget.WriteLine(screen, mauview.AlignLeft, trl.displayname, 0, 0, displayNameWidth, TagDisplayNameStyle)
|
||||||
|
|
||||||
// Draw tag room count
|
// Draw tag room count
|
||||||
roomCountX := x + len(trl.displayname) + 1
|
roomCountX := len(trl.displayname) + 1
|
||||||
roomCountWidth := width - 2 - len(trl.displayname)
|
roomCountWidth := width - 2 - len(trl.displayname)
|
||||||
widget.WriteLine(screen, tview.AlignLeft, roomCount, roomCountX, y, roomCountWidth, TagRoomCountStyle)
|
widget.WriteLine(screen, mauview.AlignLeft, roomCount, roomCountX, 0, roomCountWidth, TagRoomCountStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (trl *TagRoomList) Draw(screen tcell.Screen) {
|
func (trl *TagRoomList) Draw(screen mauview.Screen) {
|
||||||
if len(trl.displayname) == 0 {
|
if len(trl.displayname) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trl.DrawHeader(screen)
|
trl.DrawHeader(screen)
|
||||||
|
|
||||||
x, y, width, height := trl.GetRect()
|
width, height := screen.Size()
|
||||||
yLimit := y + height
|
|
||||||
|
|
||||||
items := trl.Visible()
|
items := trl.Visible()
|
||||||
|
|
||||||
if trl.IsCollapsed() {
|
if trl.IsCollapsed() {
|
||||||
screen.SetCell(x+width-1, y, tcell.StyleDefault, '▶')
|
screen.SetCell(width-1, 0, tcell.StyleDefault, '▶')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
screen.SetCell(x+width-1, y, tcell.StyleDefault, '▼')
|
screen.SetCell(width-1, 0, tcell.StyleDefault, '▼')
|
||||||
|
|
||||||
offsetY := 1
|
y := 1
|
||||||
for i := trl.Length() - 1; i >= 0; i-- {
|
for i := trl.Length() - 1; i >= 0; i-- {
|
||||||
if y+offsetY >= yLimit {
|
if y >= height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,18 +286,18 @@ func (trl *TagRoomList) Draw(screen tcell.Screen) {
|
|||||||
|
|
||||||
lineWidth := width
|
lineWidth := width
|
||||||
isSelected := trl.name == trl.parent.selectedTag && item.Room == trl.parent.selected
|
isSelected := trl.name == trl.parent.selectedTag && item.Room == trl.parent.selected
|
||||||
item.Draw(trl.parent, screen, x, y+offsetY, lineWidth, isSelected)
|
item.Draw(trl.parent, screen, 0, y, lineWidth, isSelected)
|
||||||
offsetY++
|
y++
|
||||||
}
|
}
|
||||||
hasLess := trl.maxShown > 10
|
hasLess := trl.maxShown > 10
|
||||||
hasMore := trl.HasInvisibleRooms()
|
hasMore := trl.HasInvisibleRooms()
|
||||||
if (hasLess || hasMore) && y+offsetY < yLimit {
|
if (hasLess || hasMore) && y < height {
|
||||||
if hasMore {
|
if hasMore {
|
||||||
widget.WriteLine(screen, tview.AlignRight, "More ↓", x, y+offsetY, width, tcell.StyleDefault)
|
widget.WriteLine(screen, mauview.AlignRight, "More ↓", 0, y, width, tcell.StyleDefault)
|
||||||
}
|
}
|
||||||
if hasLess {
|
if hasLess {
|
||||||
widget.WriteLine(screen, tview.AlignLeft, "↑ Less", x, y+offsetY, width, tcell.StyleDefault)
|
widget.WriteLine(screen, mauview.AlignLeft, "↑ Less", 0, y, width, tcell.StyleDefault)
|
||||||
}
|
}
|
||||||
offsetY++
|
y++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
ui/ui.go
43
ui/ui.go
@ -19,8 +19,8 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
)
|
)
|
||||||
@ -35,16 +35,17 @@ const (
|
|||||||
|
|
||||||
type GomuksUI struct {
|
type GomuksUI struct {
|
||||||
gmx ifc.Gomuks
|
gmx ifc.Gomuks
|
||||||
app *tview.Application
|
app *mauview.Application
|
||||||
views *tview.Pages
|
|
||||||
|
|
||||||
mainView *MainView
|
mainView *MainView
|
||||||
loginView *LoginView
|
loginView *LoginView
|
||||||
|
|
||||||
|
views map[View]mauview.Component
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault
|
mauview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault
|
||||||
tview.Styles.ContrastBackgroundColor = tcell.ColorDarkGreen
|
mauview.Styles.ContrastBackgroundColor = tcell.ColorDarkGreen
|
||||||
if tcellDB := os.Getenv("TCELLDB"); len(tcellDB) == 0 {
|
if tcellDB := os.Getenv("TCELLDB"); len(tcellDB) == 0 {
|
||||||
if info, err := os.Stat("/usr/share/tcell/database"); err == nil && info.IsDir() {
|
if info, err := os.Stat("/usr/share/tcell/database"); err == nil && info.IsDir() {
|
||||||
os.Setenv("TCELLDB", "/usr/share/tcell/database")
|
os.Setenv("TCELLDB", "/usr/share/tcell/database")
|
||||||
@ -55,19 +56,21 @@ func init() {
|
|||||||
func NewGomuksUI(gmx ifc.Gomuks) ifc.GomuksUI {
|
func NewGomuksUI(gmx ifc.Gomuks) ifc.GomuksUI {
|
||||||
ui := &GomuksUI{
|
ui := &GomuksUI{
|
||||||
gmx: gmx,
|
gmx: gmx,
|
||||||
app: tview.NewApplication(),
|
app: mauview.NewApplication(),
|
||||||
views: tview.NewPages(),
|
|
||||||
}
|
}
|
||||||
ui.views.SetChangedFunc(ui.Render)
|
|
||||||
return ui
|
return ui
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) Init() {
|
func (ui *GomuksUI) Init() {
|
||||||
ui.app.SetRoot(ui.InitViews(), true)
|
ui.views = map[View]mauview.Component{
|
||||||
|
ViewLogin: ui.NewLoginView(),
|
||||||
|
ViewMain: ui.NewMainView(),
|
||||||
|
}
|
||||||
|
ui.app.Root = ui.views[ViewLogin]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) Start() error {
|
func (ui *GomuksUI) Start() error {
|
||||||
return ui.app.Run()
|
return ui.app.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) Stop() {
|
func (ui *GomuksUI) Stop() {
|
||||||
@ -75,23 +78,21 @@ func (ui *GomuksUI) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) Finish() {
|
func (ui *GomuksUI) Finish() {
|
||||||
if ui.app.GetScreen() != nil {
|
if ui.app.Screen() != nil {
|
||||||
ui.app.GetScreen().Fini()
|
ui.app.Screen().Fini()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) Render() {
|
func (ui *GomuksUI) Render() {
|
||||||
ui.app.Draw()
|
ui.app.Redraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) OnLogin() {
|
func (ui *GomuksUI) OnLogin() {
|
||||||
ui.SetView(ViewMain)
|
ui.SetView(ViewMain)
|
||||||
ui.app.SetFocus(ui.mainView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) OnLogout() {
|
func (ui *GomuksUI) OnLogout() {
|
||||||
ui.SetView(ViewLogin)
|
ui.SetView(ViewLogin)
|
||||||
ui.app.SetFocus(ui.loginView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) HandleNewPreferences() {
|
func (ui *GomuksUI) HandleNewPreferences() {
|
||||||
@ -99,13 +100,11 @@ func (ui *GomuksUI) HandleNewPreferences() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) SetView(name View) {
|
func (ui *GomuksUI) SetView(name View) {
|
||||||
ui.views.SwitchToPage(string(name))
|
ui.app.Root = ui.views[name]
|
||||||
}
|
focusable, ok := ui.app.Root.(mauview.Focusable)
|
||||||
|
if ok {
|
||||||
func (ui *GomuksUI) InitViews() tview.Primitive {
|
focusable.Focus()
|
||||||
ui.views.AddPage(string(ViewLogin), ui.NewLoginView(), true, true)
|
}
|
||||||
ui.views.AddPage(string(ViewMain), ui.NewMainView(), true, false)
|
|
||||||
return ui.views
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) MainView() ifc.MainView {
|
func (ui *GomuksUI) MainView() ifc.MainView {
|
||||||
|
@ -17,66 +17,97 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/tview"
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"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/widget"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginView struct {
|
type LoginView struct {
|
||||||
*tview.Form
|
*mauview.Form
|
||||||
|
|
||||||
homeserver *widget.AdvancedInputField
|
container *mauview.Centerer
|
||||||
username *widget.AdvancedInputField
|
|
||||||
password *widget.AdvancedInputField
|
homeserverLabel *mauview.TextField
|
||||||
error *widget.FormTextView
|
usernameLabel *mauview.TextField
|
||||||
|
passwordLabel *mauview.TextField
|
||||||
|
|
||||||
|
homeserver *mauview.InputField
|
||||||
|
username *mauview.InputField
|
||||||
|
password *mauview.InputField
|
||||||
|
error *mauview.TextField
|
||||||
|
|
||||||
|
loginButton *mauview.Button
|
||||||
|
quitButton *mauview.Button
|
||||||
|
|
||||||
matrix ifc.MatrixContainer
|
matrix ifc.MatrixContainer
|
||||||
config *config.Config
|
config *config.Config
|
||||||
parent *GomuksUI
|
parent *GomuksUI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) NewLoginView() tview.Primitive {
|
func (ui *GomuksUI) NewLoginView() mauview.Component {
|
||||||
view := &LoginView{
|
view := &LoginView{
|
||||||
Form: tview.NewForm(),
|
Form: mauview.NewForm(),
|
||||||
|
|
||||||
homeserver: widget.NewAdvancedInputField(),
|
usernameLabel: mauview.NewTextField().SetText("Username"),
|
||||||
username: widget.NewAdvancedInputField(),
|
passwordLabel: mauview.NewTextField().SetText("Password"),
|
||||||
password: widget.NewAdvancedInputField(),
|
homeserverLabel: mauview.NewTextField().SetText("Homeserver"),
|
||||||
|
|
||||||
|
username: mauview.NewInputField(),
|
||||||
|
password: mauview.NewInputField(),
|
||||||
|
homeserver: mauview.NewInputField(),
|
||||||
|
|
||||||
|
loginButton: mauview.NewButton("Login"),
|
||||||
|
quitButton: mauview.NewButton("Quit"),
|
||||||
|
|
||||||
matrix: ui.gmx.Matrix(),
|
matrix: ui.gmx.Matrix(),
|
||||||
config: ui.gmx.Config(),
|
config: ui.gmx.Config(),
|
||||||
parent: ui,
|
parent: ui,
|
||||||
}
|
}
|
||||||
|
|
||||||
hs := ui.gmx.Config().HS
|
hs := ui.gmx.Config().HS
|
||||||
if len(hs) == 0 {
|
view.homeserver.SetText(hs)
|
||||||
hs = "https://matrix.org"
|
view.username.SetText(ui.gmx.Config().UserID)
|
||||||
}
|
view.password.SetMaskCharacter('*')
|
||||||
view.homeserver.SetLabel("Homeserver").SetText(hs).SetFieldWidth(30)
|
|
||||||
view.username.SetLabel("Username").SetText(ui.gmx.Config().UserID).SetFieldWidth(30)
|
|
||||||
view.password.SetLabel("Password").SetMaskCharacter('*').SetFieldWidth(30)
|
|
||||||
|
|
||||||
view.
|
view.quitButton.SetOnClick(ui.gmx.Stop).SetBackgroundColor(tcell.ColorBlue)
|
||||||
AddFormItem(view.homeserver).AddFormItem(view.username).AddFormItem(view.password).
|
view.loginButton.SetOnClick(view.Login).SetBackgroundColor(tcell.ColorBlue)
|
||||||
AddButton("Log in", view.Login).
|
|
||||||
AddButton("Quit", ui.gmx.Stop).
|
|
||||||
SetButtonsAlign(tview.AlignCenter).
|
|
||||||
SetBorder(true).SetTitle("Log in to Matrix")
|
|
||||||
|
|
||||||
|
view.SetColumns([]int{1, 10, 1, 9, 1, 9, 1, 10, 1})
|
||||||
|
view.SetRows([]int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1})
|
||||||
|
view.AddFormItem(view.username, 3, 1, 5, 1).
|
||||||
|
AddFormItem(view.password, 3, 3, 5, 1).
|
||||||
|
AddFormItem(view.homeserver, 3, 5, 5, 1).
|
||||||
|
AddFormItem(view.loginButton, 5, 7, 3, 1).
|
||||||
|
AddFormItem(view.quitButton, 1, 7, 3, 1).
|
||||||
|
AddComponent(view.usernameLabel, 1, 1, 1, 1).
|
||||||
|
AddComponent(view.passwordLabel, 1, 3, 1, 1).
|
||||||
|
AddComponent(view.homeserverLabel, 1, 5, 1, 1)
|
||||||
ui.loginView = view
|
ui.loginView = view
|
||||||
|
|
||||||
return widget.Center(45, 13, ui.loginView)
|
view.container = mauview.Center(mauview.NewBox(view).SetTitle("Log in to Matrix"), 45, 13)
|
||||||
|
return view.container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *LoginView) Error(err string) {
|
func (view *LoginView) Error(err string) {
|
||||||
|
if len(err) == 0 {
|
||||||
|
debug.Print("Hiding error")
|
||||||
|
view.RemoveComponent(view.error)
|
||||||
|
view.error = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug.Print("Showing error", err)
|
||||||
if view.error == nil {
|
if view.error == nil {
|
||||||
view.error = &widget.FormTextView{TextView: tview.NewTextView()}
|
view.error = mauview.NewTextField().SetTextColor(tcell.ColorRed)
|
||||||
view.AddFormItem(view.error)
|
view.AddComponent(view.error, 1, 9, 7, 1)
|
||||||
}
|
}
|
||||||
view.error.SetText(err)
|
view.error.SetText(err)
|
||||||
|
|
||||||
|
view.parent.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *LoginView) Login() {
|
func (view *LoginView) Login() {
|
||||||
@ -91,8 +122,8 @@ func (view *LoginView) Login() {
|
|||||||
err = view.matrix.Login(mxid, password)
|
err = view.matrix.Login(mxid, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if httpErr, ok := err.(mautrix.HTTPError); ok {
|
if httpErr, ok := err.(mautrix.HTTPError); ok {
|
||||||
if respErr, ok := httpErr.WrappedError.(mautrix.RespError); ok {
|
if httpErr.RespError != nil {
|
||||||
view.Error(respErr.Err)
|
view.Error(httpErr.RespError.Err)
|
||||||
} else {
|
} else {
|
||||||
view.Error(httpErr.Message)
|
view.Error(httpErr.Message)
|
||||||
}
|
}
|
||||||
|
235
ui/view-main.go
235
ui/view-main.go
@ -26,8 +26,8 @@ import (
|
|||||||
"github.com/kyokomi/emoji"
|
"github.com/kyokomi/emoji"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
@ -40,12 +40,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MainView struct {
|
type MainView struct {
|
||||||
*tview.Flex
|
flex *mauview.Flex
|
||||||
|
|
||||||
roomList *RoomList
|
roomList *RoomList
|
||||||
roomView *tview.Pages
|
roomView *mauview.Box
|
||||||
|
currentRoom *RoomView
|
||||||
rooms map[string]*RoomView
|
rooms map[string]*RoomView
|
||||||
cmdProcessor *CommandProcessor
|
cmdProcessor *CommandProcessor
|
||||||
|
focused mauview.Focusable
|
||||||
|
|
||||||
|
modal mauview.Component
|
||||||
|
|
||||||
lastFocusTime time.Time
|
lastFocusTime time.Time
|
||||||
|
|
||||||
@ -55,11 +59,10 @@ type MainView struct {
|
|||||||
parent *GomuksUI
|
parent *GomuksUI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *GomuksUI) NewMainView() tview.Primitive {
|
func (ui *GomuksUI) NewMainView() mauview.Component {
|
||||||
mainView := &MainView{
|
mainView := &MainView{
|
||||||
Flex: tview.NewFlex(),
|
flex: mauview.NewFlex().SetDirection(mauview.FlexColumn),
|
||||||
roomList: NewRoomList(),
|
roomView: mauview.NewBox(nil).SetBorder(false),
|
||||||
roomView: tview.NewPages(),
|
|
||||||
rooms: make(map[string]*RoomView),
|
rooms: make(map[string]*RoomView),
|
||||||
|
|
||||||
matrix: ui.gmx.Matrix(),
|
matrix: ui.gmx.Matrix(),
|
||||||
@ -67,13 +70,13 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
|
|||||||
config: ui.gmx.Config(),
|
config: ui.gmx.Config(),
|
||||||
parent: ui,
|
parent: ui,
|
||||||
}
|
}
|
||||||
|
mainView.roomList = NewRoomList(mainView)
|
||||||
mainView.cmdProcessor = NewCommandProcessor(mainView)
|
mainView.cmdProcessor = NewCommandProcessor(mainView)
|
||||||
|
|
||||||
mainView.
|
mainView.flex.
|
||||||
SetDirection(tview.FlexColumn).
|
AddFixedComponent(mainView.roomList, 25).
|
||||||
AddItem(mainView.roomList, 25, 0, false).
|
AddFixedComponent(widget.NewBorder(), 1).
|
||||||
AddItem(widget.NewBorder(), 1, 0, false).
|
AddProportionalComponent(mainView.roomView, 1)
|
||||||
AddItem(mainView.roomView, 0, 1, true)
|
|
||||||
mainView.BumpFocus(nil)
|
mainView.BumpFocus(nil)
|
||||||
|
|
||||||
ui.mainView = mainView
|
ui.mainView = mainView
|
||||||
@ -81,18 +84,37 @@ func (ui *GomuksUI) NewMainView() tview.Primitive {
|
|||||||
return mainView
|
return mainView
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) Draw(screen tcell.Screen) {
|
func (view *MainView) ShowModal(modal mauview.Component) {
|
||||||
|
view.modal = modal
|
||||||
|
var ok bool
|
||||||
|
view.focused, ok = modal.(mauview.Focusable)
|
||||||
|
if !ok {
|
||||||
|
view.focused = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *MainView) HideModal() {
|
||||||
|
view.modal = nil
|
||||||
|
view.focused = view.roomView
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *MainView) Draw(screen mauview.Screen) {
|
||||||
if view.config.Preferences.HideRoomList {
|
if view.config.Preferences.HideRoomList {
|
||||||
view.roomView.SetRect(view.GetRect())
|
|
||||||
view.roomView.Draw(screen)
|
view.roomView.Draw(screen)
|
||||||
} else {
|
} else {
|
||||||
view.Flex.Draw(screen)
|
view.flex.Draw(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if view.modal != nil {
|
||||||
|
view.modal.Draw(screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) BumpFocus(roomView *RoomView) {
|
func (view *MainView) BumpFocus(roomView *RoomView) {
|
||||||
|
if roomView != nil {
|
||||||
view.lastFocusTime = time.Now()
|
view.lastFocusTime = time.Now()
|
||||||
view.MarkRead(roomView)
|
view.MarkRead(roomView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) MarkRead(roomView *RoomView) {
|
func (view *MainView) MarkRead(roomView *RoomView) {
|
||||||
@ -167,7 +189,10 @@ func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Messag
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) ShowBare(roomView *RoomView) {
|
func (view *MainView) ShowBare(roomView *RoomView) {
|
||||||
_, height := view.parent.app.GetScreen().Size()
|
if roomView == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, height := view.parent.app.Screen().Size()
|
||||||
view.parent.app.Suspend(func() {
|
view.parent.app.Suspend(func() {
|
||||||
print("\033[2J\033[0;0H")
|
print("\033[2J\033[0;0H")
|
||||||
// We don't know how much space there exactly is. Too few messages looks weird,
|
// We don't know how much space there exactly is. Too few messages looks weird,
|
||||||
@ -181,37 +206,54 @@ func (view *MainView) ShowBare(roomView *RoomView) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) KeyEventHandler(roomView *RoomView, key *tcell.EventKey) *tcell.EventKey {
|
func (view *MainView) OnKeyEvent(event mauview.KeyEvent) bool {
|
||||||
view.BumpFocus(roomView)
|
view.BumpFocus(view.currentRoom)
|
||||||
|
|
||||||
k := key.Key()
|
if view.modal != nil {
|
||||||
c := key.Rune()
|
return view.modal.OnKeyEvent(event)
|
||||||
if key.Modifiers() == tcell.ModCtrl || key.Modifiers() == tcell.ModAlt {
|
}
|
||||||
|
|
||||||
|
k := event.Key()
|
||||||
|
c := event.Rune()
|
||||||
|
if event.Modifiers() == tcell.ModCtrl || event.Modifiers() == tcell.ModAlt {
|
||||||
switch {
|
switch {
|
||||||
case k == tcell.KeyDown:
|
case k == tcell.KeyDown:
|
||||||
view.SwitchRoom(view.roomList.Next())
|
view.SwitchRoom(view.roomList.Next())
|
||||||
case k == tcell.KeyUp:
|
case k == tcell.KeyUp:
|
||||||
view.SwitchRoom(view.roomList.Previous())
|
view.SwitchRoom(view.roomList.Previous())
|
||||||
case k == tcell.KeyEnter:
|
case k == tcell.KeyEnter:
|
||||||
searchModal := NewFuzzySearchModal(view, 42, 12)
|
view.ShowModal(NewFuzzySearchModal(view, 42, 12))
|
||||||
view.parent.views.AddPage("fuzzy-search-modal", searchModal, true, true)
|
case k == tcell.KeyHome:
|
||||||
view.parent.app.SetFocus(searchModal)
|
msgView := view.currentRoom.MessageView()
|
||||||
|
msgView.AddScrollOffset(msgView.TotalHeight())
|
||||||
|
case k == tcell.KeyEnd:
|
||||||
|
msgView := view.currentRoom.MessageView()
|
||||||
|
msgView.AddScrollOffset(-msgView.TotalHeight())
|
||||||
|
case k == tcell.KeyCtrlN:
|
||||||
|
return view.flex.OnKeyEvent(tcell.NewEventKey(tcell.KeyEnter, '\n', event.Modifiers()))
|
||||||
case c == 'a':
|
case c == 'a':
|
||||||
view.SwitchRoom(view.roomList.NextWithActivity())
|
view.SwitchRoom(view.roomList.NextWithActivity())
|
||||||
case c == 'l':
|
case c == 'l':
|
||||||
view.ShowBare(roomView)
|
view.ShowBare(view.currentRoom)
|
||||||
default:
|
default:
|
||||||
return key
|
goto defaultHandler
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
} else if k == tcell.KeyAltDown || k == tcell.KeyCtrlDown {
|
} else if k == tcell.KeyAltDown || k == tcell.KeyCtrlDown {
|
||||||
view.SwitchRoom(view.roomList.Next())
|
view.SwitchRoom(view.roomList.Next())
|
||||||
|
return true
|
||||||
} else if k == tcell.KeyAltUp || k == tcell.KeyCtrlUp {
|
} else if k == tcell.KeyAltUp || k == tcell.KeyCtrlUp {
|
||||||
view.SwitchRoom(view.roomList.Previous())
|
view.SwitchRoom(view.roomList.Previous())
|
||||||
} else if k == tcell.KeyPgUp || k == tcell.KeyPgDn || k == tcell.KeyUp || k == tcell.KeyDown || k == tcell.KeyEnd || k == tcell.KeyHome {
|
return true
|
||||||
msgView := roomView.MessageView()
|
} else if view.currentRoom != nil &&
|
||||||
|
(k == tcell.KeyPgUp || k == tcell.KeyPgDn ||
|
||||||
|
k == tcell.KeyUp || k == tcell.KeyDown ||
|
||||||
|
k == tcell.KeyEnd || k == tcell.KeyHome) {
|
||||||
|
// TODO these should be in the RoomView key handler
|
||||||
|
msgView := view.currentRoom.MessageView()
|
||||||
|
|
||||||
if msgView.IsAtTop() && (k == tcell.KeyPgUp || k == tcell.KeyUp) {
|
if msgView.IsAtTop() && (k == tcell.KeyPgUp || k == tcell.KeyUp) {
|
||||||
go view.LoadHistory(roomView.Room.ID)
|
go view.LoadHistory(view.currentRoom.Room.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch k {
|
switch k {
|
||||||
@ -219,80 +261,72 @@ func (view *MainView) KeyEventHandler(roomView *RoomView, key *tcell.EventKey) *
|
|||||||
msgView.AddScrollOffset(msgView.Height() / 2)
|
msgView.AddScrollOffset(msgView.Height() / 2)
|
||||||
case tcell.KeyPgDn:
|
case tcell.KeyPgDn:
|
||||||
msgView.AddScrollOffset(-msgView.Height() / 2)
|
msgView.AddScrollOffset(-msgView.Height() / 2)
|
||||||
case tcell.KeyUp:
|
default:
|
||||||
msgView.AddScrollOffset(1)
|
goto defaultHandler
|
||||||
case tcell.KeyDown:
|
|
||||||
msgView.AddScrollOffset(-1)
|
|
||||||
case tcell.KeyHome:
|
|
||||||
msgView.AddScrollOffset(msgView.TotalHeight())
|
|
||||||
case tcell.KeyEnd:
|
|
||||||
msgView.AddScrollOffset(-msgView.TotalHeight())
|
|
||||||
}
|
}
|
||||||
} else {
|
return true
|
||||||
return key
|
} else if k == tcell.KeyEnter {
|
||||||
|
view.InputSubmit(view.currentRoom, view.currentRoom.input.GetText())
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
defaultHandler:
|
||||||
|
if view.config.Preferences.HideRoomList {
|
||||||
|
debug.Print("Key event going to default handler (direct to roomview)", event)
|
||||||
|
return view.roomView.OnKeyEvent(event)
|
||||||
|
}
|
||||||
|
debug.Print("Key event going to default handler (flex)", event)
|
||||||
|
return view.flex.OnKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const WheelScrollOffsetDiff = 3
|
const WheelScrollOffsetDiff = 3
|
||||||
|
|
||||||
func isInArea(x, y int, p tview.Primitive) bool {
|
func (view *MainView) OnMouseEvent(event mauview.MouseEvent) bool {
|
||||||
rx, ry, rw, rh := p.GetRect()
|
if view.config.Preferences.HideRoomList {
|
||||||
return x >= rx && y >= ry && x < rx+rw && y < ry+rh
|
return view.roomView.OnMouseEvent(event)
|
||||||
}
|
}
|
||||||
|
return view.flex.OnMouseEvent(event)
|
||||||
func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMouse) *tcell.EventMouse {
|
/*if event.Buttons() == tcell.ButtonNone || event.HasMotion() {
|
||||||
if event.Buttons() == tcell.ButtonNone || event.HasMotion() {
|
return false
|
||||||
return event
|
|
||||||
}
|
}
|
||||||
view.BumpFocus(roomView)
|
|
||||||
|
|
||||||
msgView := roomView.MessageView()
|
view.BumpFocus(view.currentRoom)
|
||||||
|
|
||||||
x, y := event.Position()
|
x, y := event.Position()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case isInArea(x, y, msgView):
|
case x >= 27:
|
||||||
mx, my, _, _ := msgView.GetRect()
|
view.roomView.OnMouseEvent(mauview.OffsetMouseEvent(event, -27, 0))
|
||||||
switch event.Buttons() {
|
view.roomView.Focus()
|
||||||
case tcell.WheelUp:
|
view.focused = view.roomView
|
||||||
if msgView.IsAtTop() {
|
case x <= 25:
|
||||||
go view.LoadHistory(roomView.Room.ID)
|
view.roomList.OnMouseEvent(event)
|
||||||
} else {
|
view.roomList.Focus()
|
||||||
msgView.AddScrollOffset(WheelScrollOffsetDiff)
|
view.focused = view.roomList
|
||||||
|
|
||||||
view.parent.Render()
|
|
||||||
}
|
|
||||||
case tcell.WheelDown:
|
|
||||||
msgView.AddScrollOffset(-WheelScrollOffsetDiff)
|
|
||||||
view.parent.Render()
|
|
||||||
view.MarkRead(roomView)
|
|
||||||
default:
|
|
||||||
if msgView.HandleClick(x-mx, y-my, event.Buttons()) {
|
|
||||||
view.parent.Render()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case isInArea(x, y, view.roomList):
|
|
||||||
switch event.Buttons() {
|
|
||||||
case tcell.WheelUp:
|
|
||||||
view.roomList.AddScrollOffset(-WheelScrollOffsetDiff)
|
|
||||||
view.parent.Render()
|
|
||||||
case tcell.WheelDown:
|
|
||||||
view.roomList.AddScrollOffset(WheelScrollOffsetDiff)
|
|
||||||
view.parent.Render()
|
|
||||||
case tcell.Button1:
|
|
||||||
_, rly, _, _ := msgView.GetRect()
|
|
||||||
line := y - rly + 1
|
|
||||||
switchToTag, switchToRoom := view.roomList.HandleClick(x, line, event.Modifiers() == tcell.ModCtrl)
|
|
||||||
if switchToRoom != nil {
|
|
||||||
view.SwitchRoom(switchToTag, switchToRoom)
|
|
||||||
} else {
|
|
||||||
view.parent.Render()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
debug.Print("Unhandled mouse event:", event.Buttons(), event.Modifiers(), x, y)
|
debug.Print("Unhandled mouse event:", event.Buttons(), event.Modifiers(), x, y)
|
||||||
}
|
}
|
||||||
return event
|
return false*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *MainView) OnPasteEvent(event mauview.PasteEvent) bool {
|
||||||
|
if view.modal != nil {
|
||||||
|
return view.modal.OnPasteEvent(event)
|
||||||
|
} else if view.config.Preferences.HideRoomList {
|
||||||
|
return view.roomView.OnPasteEvent(event)
|
||||||
|
}
|
||||||
|
return view.flex.OnPasteEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *MainView) Focus() {
|
||||||
|
if view.focused != nil {
|
||||||
|
view.focused.Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *MainView) Blur() {
|
||||||
|
if view.focused != nil {
|
||||||
|
view.focused.Blur()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) SwitchRoom(tag string, room *rooms.Room) {
|
func (view *MainView) SwitchRoom(tag string, room *rooms.Room) {
|
||||||
@ -300,29 +334,19 @@ func (view *MainView) SwitchRoom(tag string, room *rooms.Room) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
view.roomView.SwitchToPage(room.ID)
|
|
||||||
roomView := view.rooms[room.ID]
|
roomView := view.rooms[room.ID]
|
||||||
if roomView == nil {
|
if roomView == nil {
|
||||||
debug.Print("Tried to switch to non-nil room with nil roomView!")
|
debug.Print("Tried to switch to non-nil room with nil roomView!")
|
||||||
debug.Print(tag, room)
|
debug.Print(tag, room)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
view.roomView.SetInnerComponent(roomView)
|
||||||
|
view.currentRoom = roomView
|
||||||
view.MarkRead(roomView)
|
view.MarkRead(roomView)
|
||||||
view.roomList.SetSelected(tag, room)
|
view.roomList.SetSelected(tag, room)
|
||||||
view.parent.app.SetFocus(view)
|
|
||||||
view.parent.Render()
|
view.parent.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) Focus(delegate func(p tview.Primitive)) {
|
|
||||||
room := view.roomList.SelectedRoom()
|
|
||||||
if room != nil {
|
|
||||||
roomView, ok := view.rooms[room.ID]
|
|
||||||
if ok {
|
|
||||||
delegate(roomView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *MainView) SaveAllHistory() {
|
func (view *MainView) SaveAllHistory() {
|
||||||
for _, room := range view.rooms {
|
for _, room := range view.rooms {
|
||||||
err := room.SaveHistory(view.config.HistoryDir)
|
err := room.SaveHistory(view.config.HistoryDir)
|
||||||
@ -333,14 +357,11 @@ func (view *MainView) SaveAllHistory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) addRoomPage(room *rooms.Room) {
|
func (view *MainView) addRoomPage(room *rooms.Room) {
|
||||||
if !view.roomView.HasPage(room.ID) {
|
if _, ok := view.rooms[room.ID]; !ok {
|
||||||
roomView := NewRoomView(view, room).
|
roomView := NewRoomView(view, room).
|
||||||
SetInputSubmitFunc(view.InputSubmit).
|
SetInputSubmitFunc(view.InputSubmit).
|
||||||
SetInputChangedFunc(view.InputChanged).
|
SetInputChangedFunc(view.InputChanged)
|
||||||
SetInputCapture(view.KeyEventHandler).
|
|
||||||
SetMouseCapture(view.MouseEventHandler)
|
|
||||||
view.rooms[room.ID] = roomView
|
view.rooms[room.ID] = roomView
|
||||||
view.roomView.AddPage(room.ID, roomView, true, false)
|
|
||||||
roomView.UpdateUserList()
|
roomView.UpdateUserList()
|
||||||
|
|
||||||
_, err := roomView.LoadHistory(view.matrix, view.config.HistoryDir)
|
_, err := roomView.LoadHistory(view.matrix, view.config.HistoryDir)
|
||||||
@ -387,7 +408,6 @@ func (view *MainView) RemoveRoom(room *rooms.Room) {
|
|||||||
view.roomList.Remove(room)
|
view.roomList.Remove(room)
|
||||||
view.SwitchRoom(view.roomList.Selected())
|
view.SwitchRoom(view.roomList.Selected())
|
||||||
|
|
||||||
view.roomView.RemovePage(room.ID)
|
|
||||||
delete(view.rooms, room.ID)
|
delete(view.rooms, room.ID)
|
||||||
|
|
||||||
view.parent.Render()
|
view.parent.Render()
|
||||||
@ -395,7 +415,6 @@ func (view *MainView) RemoveRoom(room *rooms.Room) {
|
|||||||
|
|
||||||
func (view *MainView) SetRooms(rooms map[string]*rooms.Room) {
|
func (view *MainView) SetRooms(rooms map[string]*rooms.Room) {
|
||||||
view.roomList.Clear()
|
view.roomList.Clear()
|
||||||
view.roomView.Clear()
|
|
||||||
view.rooms = make(map[string]*RoomView)
|
view.rooms = make(map[string]*RoomView)
|
||||||
for _, room := range rooms {
|
for _, room := range rooms {
|
||||||
if room.HasLeft {
|
if room.HasLeft {
|
||||||
|
@ -1,562 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Based on https://github.com/rivo/tview/blob/master/inputfield.go
|
|
||||||
|
|
||||||
package widget
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/zyedidia/clipboard"
|
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
|
||||||
"maunium.net/go/tview"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AdvancedInputField is a multi-line user-editable text area.
|
|
||||||
//
|
|
||||||
// Use SetMaskCharacter() to hide input from onlookers (e.g. for password
|
|
||||||
// input).
|
|
||||||
type AdvancedInputField struct {
|
|
||||||
*tview.Box
|
|
||||||
|
|
||||||
// Cursor position
|
|
||||||
cursorOffset int
|
|
||||||
viewOffset int
|
|
||||||
|
|
||||||
// The text that was entered.
|
|
||||||
text string
|
|
||||||
|
|
||||||
// The text to be displayed before the input area.
|
|
||||||
label string
|
|
||||||
|
|
||||||
// The text to be displayed in the input area when "text" is empty.
|
|
||||||
placeholder string
|
|
||||||
|
|
||||||
// The label color.
|
|
||||||
labelColor tcell.Color
|
|
||||||
|
|
||||||
// The background color of the input area.
|
|
||||||
fieldBackgroundColor tcell.Color
|
|
||||||
|
|
||||||
// The text color of the input area.
|
|
||||||
fieldTextColor tcell.Color
|
|
||||||
|
|
||||||
// The text color of the placeholder.
|
|
||||||
placeholderTextColor tcell.Color
|
|
||||||
|
|
||||||
// The screen width of the label area. A value of 0 means use the width of
|
|
||||||
// the label text.
|
|
||||||
labelWidth int
|
|
||||||
|
|
||||||
// The screen width of the input area. A value of 0 means extend as much as
|
|
||||||
// possible.
|
|
||||||
fieldWidth int
|
|
||||||
|
|
||||||
// A character to mask entered text (useful for password fields). A value of 0
|
|
||||||
// disables masking.
|
|
||||||
maskCharacter rune
|
|
||||||
|
|
||||||
// Whether or not to enable vim-style keybindings.
|
|
||||||
vimBindings bool
|
|
||||||
|
|
||||||
// An optional function which may reject the last character that was entered.
|
|
||||||
accept func(text string, ch rune) bool
|
|
||||||
|
|
||||||
// An optional function which is called when the input has changed.
|
|
||||||
changed func(text string)
|
|
||||||
|
|
||||||
// An optional function which is called when the user indicated that they
|
|
||||||
// are done entering text. The key which was pressed is provided (enter, tab, backtab or escape).
|
|
||||||
done func(tcell.Key)
|
|
||||||
|
|
||||||
// An optional function which is called when the user presses tab.
|
|
||||||
tabComplete func(text string, cursorOffset int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAdvancedInputField returns a new input field.
|
|
||||||
func NewAdvancedInputField() *AdvancedInputField {
|
|
||||||
return &AdvancedInputField{
|
|
||||||
Box: tview.NewBox(),
|
|
||||||
labelColor: tview.Styles.SecondaryTextColor,
|
|
||||||
fieldBackgroundColor: tview.Styles.ContrastBackgroundColor,
|
|
||||||
fieldTextColor: tview.Styles.PrimaryTextColor,
|
|
||||||
placeholderTextColor: tview.Styles.ContrastSecondaryTextColor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetText sets the current text of the input field.
|
|
||||||
func (field *AdvancedInputField) SetText(text string) *AdvancedInputField {
|
|
||||||
field.text = text
|
|
||||||
if field.changed != nil {
|
|
||||||
field.changed(text)
|
|
||||||
}
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTextAndMoveCursor sets the current text of the input field and moves the cursor with the width difference.
|
|
||||||
func (field *AdvancedInputField) SetTextAndMoveCursor(text string) *AdvancedInputField {
|
|
||||||
oldWidth := runewidth.StringWidth(field.text)
|
|
||||||
field.text = text
|
|
||||||
newWidth := runewidth.StringWidth(field.text)
|
|
||||||
if oldWidth != newWidth {
|
|
||||||
field.cursorOffset += newWidth - oldWidth
|
|
||||||
}
|
|
||||||
if field.changed != nil {
|
|
||||||
field.changed(field.text)
|
|
||||||
}
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetText returns the current text of the input field.
|
|
||||||
func (field *AdvancedInputField) GetText() string {
|
|
||||||
return field.text
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLabel sets the text to be displayed before the input area.
|
|
||||||
func (field *AdvancedInputField) SetLabel(label string) *AdvancedInputField {
|
|
||||||
field.label = label
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
|
||||||
// primitive to use the width of the label string.
|
|
||||||
func (field *AdvancedInputField) SetLabelWidth(width int) *AdvancedInputField {
|
|
||||||
field.labelWidth = width
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLabel returns the text to be displayed before the input area.
|
|
||||||
func (field *AdvancedInputField) GetLabel() string {
|
|
||||||
return field.label
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPlaceholder sets the text to be displayed when the input text is empty.
|
|
||||||
func (field *AdvancedInputField) SetPlaceholder(text string) *AdvancedInputField {
|
|
||||||
field.placeholder = text
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLabelColor sets the color of the label.
|
|
||||||
func (field *AdvancedInputField) SetLabelColor(color tcell.Color) *AdvancedInputField {
|
|
||||||
field.labelColor = color
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFieldBackgroundColor sets the background color of the input area.
|
|
||||||
func (field *AdvancedInputField) SetFieldBackgroundColor(color tcell.Color) *AdvancedInputField {
|
|
||||||
field.fieldBackgroundColor = color
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFieldTextColor sets the text color of the input area.
|
|
||||||
func (field *AdvancedInputField) SetFieldTextColor(color tcell.Color) *AdvancedInputField {
|
|
||||||
field.fieldTextColor = color
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPlaceholderExtColor sets the text color of placeholder text.
|
|
||||||
func (field *AdvancedInputField) SetPlaceholderExtColor(color tcell.Color) *AdvancedInputField {
|
|
||||||
field.placeholderTextColor = color
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormAttributes sets attributes shared by all form items.
|
|
||||||
func (field *AdvancedInputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem {
|
|
||||||
field.labelWidth = labelWidth
|
|
||||||
field.labelColor = labelColor
|
|
||||||
field.SetBackgroundColor(bgColor)
|
|
||||||
field.fieldTextColor = fieldTextColor
|
|
||||||
field.fieldBackgroundColor = fieldBgColor
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFieldWidth sets the screen width of the input area. A value of 0 means
|
|
||||||
// extend as much as possible.
|
|
||||||
func (field *AdvancedInputField) SetFieldWidth(width int) *AdvancedInputField {
|
|
||||||
field.fieldWidth = width
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFieldWidth returns this primitive's field width.
|
|
||||||
func (field *AdvancedInputField) GetFieldWidth() int {
|
|
||||||
return field.fieldWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMaskCharacter sets a character that masks user input on a screen. A value
|
|
||||||
// of 0 disables masking.
|
|
||||||
func (field *AdvancedInputField) SetMaskCharacter(mask rune) *AdvancedInputField {
|
|
||||||
field.maskCharacter = mask
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAcceptanceFunc sets a handler which may reject the last character that was
|
|
||||||
// entered (by returning false).
|
|
||||||
//
|
|
||||||
// This package defines a number of variables Prefixed with AdvancedInputField which may
|
|
||||||
// be used for common input (e.g. numbers, maximum text length).
|
|
||||||
func (field *AdvancedInputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *AdvancedInputField {
|
|
||||||
field.accept = handler
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetChangedFunc sets a handler which is called whenever the text of the input
|
|
||||||
// field has changed. It receives the current text (after the change).
|
|
||||||
func (field *AdvancedInputField) SetChangedFunc(handler func(text string)) *AdvancedInputField {
|
|
||||||
field.changed = handler
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDoneFunc sets a handler which is called when the user is done entering
|
|
||||||
// text. The callback function is provided with the key that was pressed, which
|
|
||||||
// is one of the following:
|
|
||||||
//
|
|
||||||
// - KeyEnter: Done entering text.
|
|
||||||
// - KeyEscape: Abort text input.
|
|
||||||
// - KeyTab: Tab
|
|
||||||
// - KeyBacktab: Shift + Tab
|
|
||||||
func (field *AdvancedInputField) SetDoneFunc(handler func(key tcell.Key)) *AdvancedInputField {
|
|
||||||
field.done = handler
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) SetTabCompleteFunc(handler func(text string, cursorOffset int)) *AdvancedInputField {
|
|
||||||
field.tabComplete = handler
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFinishedFunc calls SetDoneFunc().
|
|
||||||
func (field *AdvancedInputField) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
|
||||||
return field.SetDoneFunc(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// drawInput calculates the field width and draws the background.
|
|
||||||
func (field *AdvancedInputField) drawInput(screen tcell.Screen, rightLimit, x, y int) (fieldWidth int) {
|
|
||||||
fieldWidth = field.fieldWidth
|
|
||||||
if fieldWidth == 0 {
|
|
||||||
fieldWidth = math.MaxInt32
|
|
||||||
}
|
|
||||||
if rightLimit-x < fieldWidth {
|
|
||||||
fieldWidth = rightLimit - x
|
|
||||||
}
|
|
||||||
fieldStyle := tcell.StyleDefault.Background(field.fieldBackgroundColor)
|
|
||||||
for index := 0; index < fieldWidth; index++ {
|
|
||||||
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareText prepares the text to be displayed and recalculates the view and cursor offsets.
|
|
||||||
func (field *AdvancedInputField) prepareText(screen tcell.Screen, fieldWidth, x, y int) (text string) {
|
|
||||||
text = field.text
|
|
||||||
if text == "" && field.placeholder != "" {
|
|
||||||
tview.Print(screen, field.placeholder, x, y, fieldWidth, tview.AlignLeft, field.placeholderTextColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.maskCharacter > 0 {
|
|
||||||
text = strings.Repeat(string(field.maskCharacter), utf8.RuneCountInString(field.text))
|
|
||||||
}
|
|
||||||
textWidth := runewidth.StringWidth(text)
|
|
||||||
if field.cursorOffset >= textWidth {
|
|
||||||
fieldWidth--
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.cursorOffset < field.viewOffset {
|
|
||||||
field.viewOffset = field.cursorOffset
|
|
||||||
} else if field.cursorOffset > field.viewOffset+fieldWidth {
|
|
||||||
field.viewOffset = field.cursorOffset - fieldWidth
|
|
||||||
} else if textWidth-field.viewOffset < fieldWidth {
|
|
||||||
field.viewOffset = textWidth - fieldWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.viewOffset < 0 {
|
|
||||||
field.viewOffset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// drawText draws the text and the cursor.
|
|
||||||
func (field *AdvancedInputField) drawText(screen tcell.Screen, fieldWidth, x, y int, text string) {
|
|
||||||
runes := []rune(text)
|
|
||||||
relPos := 0
|
|
||||||
for pos := field.viewOffset; pos <= fieldWidth+field.viewOffset && pos < len(runes); pos++ {
|
|
||||||
ch := runes[pos]
|
|
||||||
w := runewidth.RuneWidth(ch)
|
|
||||||
_, _, style, _ := screen.GetContent(x+relPos, y)
|
|
||||||
style = style.Foreground(field.fieldTextColor)
|
|
||||||
for w > 0 {
|
|
||||||
screen.SetContent(x+relPos, y, ch, nil, style)
|
|
||||||
relPos++
|
|
||||||
w--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set cursor.
|
|
||||||
if field.GetFocusable().HasFocus() {
|
|
||||||
field.setCursor(screen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw draws this primitive onto the screen.
|
|
||||||
func (field *AdvancedInputField) Draw(screen tcell.Screen) {
|
|
||||||
field.Box.Draw(screen)
|
|
||||||
|
|
||||||
x, y, width, height := field.GetInnerRect()
|
|
||||||
rightLimit := x + width
|
|
||||||
if height < 1 || rightLimit <= x {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw label.
|
|
||||||
if field.labelWidth > 0 {
|
|
||||||
labelWidth := field.labelWidth
|
|
||||||
if labelWidth > rightLimit-x {
|
|
||||||
labelWidth = rightLimit - x
|
|
||||||
}
|
|
||||||
tview.Print(screen, field.label, x, y, labelWidth, tview.AlignLeft, field.labelColor)
|
|
||||||
x += labelWidth
|
|
||||||
} else {
|
|
||||||
_, drawnWidth := tview.Print(screen, field.label, x, y, rightLimit-x, tview.AlignLeft, field.labelColor)
|
|
||||||
x += drawnWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldWidth := field.drawInput(screen, rightLimit, x, y)
|
|
||||||
text := field.prepareText(screen, fieldWidth, x, y)
|
|
||||||
field.drawText(screen, fieldWidth, x, y, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) GetCursorOffset() int {
|
|
||||||
return field.cursorOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) SetCursorOffset(offset int) *AdvancedInputField {
|
|
||||||
if offset < 0 {
|
|
||||||
offset = 0
|
|
||||||
} else {
|
|
||||||
width := runewidth.StringWidth(field.text)
|
|
||||||
if offset >= width {
|
|
||||||
offset = width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field.cursorOffset = offset
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// setCursor sets the cursor position.
|
|
||||||
func (field *AdvancedInputField) setCursor(screen tcell.Screen) {
|
|
||||||
x, y, width, _ := field.GetRect()
|
|
||||||
origX, origY := x, y
|
|
||||||
rightLimit := x + width
|
|
||||||
if field.HasBorder() {
|
|
||||||
x++
|
|
||||||
y++
|
|
||||||
rightLimit -= 2
|
|
||||||
}
|
|
||||||
labelWidth := field.labelWidth
|
|
||||||
if labelWidth == 0 {
|
|
||||||
labelWidth = tview.StringWidth(field.label)
|
|
||||||
}
|
|
||||||
x = x + labelWidth + field.cursorOffset - field.viewOffset
|
|
||||||
if x >= rightLimit {
|
|
||||||
x = rightLimit - 1
|
|
||||||
} else if x < origX {
|
|
||||||
x = origY
|
|
||||||
}
|
|
||||||
screen.ShowCursor(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
lastWord = regexp.MustCompile(`\S+\s*$`)
|
|
||||||
firstWord = regexp.MustCompile(`^\s*\S+`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func SubstringBefore(s string, w int) string {
|
|
||||||
return runewidth.Truncate(s, w, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) TypeRune(ch rune) {
|
|
||||||
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
newText := leftPart + string(ch) + field.text[len(leftPart):]
|
|
||||||
if field.accept != nil {
|
|
||||||
if !field.accept(newText, ch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field.text = newText
|
|
||||||
field.cursorOffset += runewidth.RuneWidth(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) PasteClipboard() {
|
|
||||||
clip, _ := clipboard.ReadAll("clipboard")
|
|
||||||
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
field.text = leftPart + clip + field.text[len(leftPart):]
|
|
||||||
field.cursorOffset += runewidth.StringWidth(clip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) MoveCursorLeft(moveWord bool) {
|
|
||||||
before := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
if moveWord {
|
|
||||||
found := lastWord.FindString(before)
|
|
||||||
field.cursorOffset -= runewidth.StringWidth(found)
|
|
||||||
} else if len(before) > 0 {
|
|
||||||
beforeRunes := []rune(before)
|
|
||||||
char := beforeRunes[len(beforeRunes)-1]
|
|
||||||
field.cursorOffset -= runewidth.RuneWidth(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) MoveCursorRight(moveWord bool) {
|
|
||||||
before := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
after := field.text[len(before):]
|
|
||||||
if moveWord {
|
|
||||||
found := firstWord.FindString(after)
|
|
||||||
field.cursorOffset += runewidth.StringWidth(found)
|
|
||||||
} else if len(after) > 0 {
|
|
||||||
char := []rune(after)[0]
|
|
||||||
field.cursorOffset += runewidth.RuneWidth(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) RemoveNextCharacter() {
|
|
||||||
if field.cursorOffset >= runewidth.StringWidth(field.text) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
// Take everything after the left part minus the first character.
|
|
||||||
rightPart := string([]rune(field.text[len(leftPart):])[1:])
|
|
||||||
|
|
||||||
field.text = leftPart + rightPart
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) Clear() {
|
|
||||||
field.text = ""
|
|
||||||
field.cursorOffset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) RemovePreviousWord() {
|
|
||||||
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
rightPart := field.text[len(leftPart):]
|
|
||||||
replacement := lastWord.ReplaceAllString(leftPart, "")
|
|
||||||
field.text = replacement + rightPart
|
|
||||||
|
|
||||||
field.cursorOffset -= runewidth.StringWidth(leftPart) - runewidth.StringWidth(replacement)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) RemovePreviousCharacter() {
|
|
||||||
if field.cursorOffset == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
rightPart := field.text[len(leftPart):]
|
|
||||||
|
|
||||||
// Take everything before the right part minus the last character.
|
|
||||||
leftPartRunes := []rune(leftPart)
|
|
||||||
leftPartRunes = leftPartRunes[0 : len(leftPartRunes)-1]
|
|
||||||
leftPart = string(leftPartRunes)
|
|
||||||
|
|
||||||
// Figure out what character was removed to correctly decrease cursorOffset.
|
|
||||||
removedChar := field.text[len(leftPart) : len(field.text)-len(rightPart)]
|
|
||||||
|
|
||||||
field.text = leftPart + rightPart
|
|
||||||
|
|
||||||
field.cursorOffset -= runewidth.StringWidth(removedChar)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) TriggerTabComplete() bool {
|
|
||||||
if field.tabComplete != nil {
|
|
||||||
field.tabComplete(field.text, field.cursorOffset)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) handleInputChanges(originalText string) {
|
|
||||||
// Trigger changed events.
|
|
||||||
if field.text != originalText && field.changed != nil {
|
|
||||||
field.changed(field.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure cursor offset is valid
|
|
||||||
if field.cursorOffset < 0 {
|
|
||||||
field.cursorOffset = 0
|
|
||||||
}
|
|
||||||
width := runewidth.StringWidth(field.text)
|
|
||||||
if field.cursorOffset > width {
|
|
||||||
field.cursorOffset = width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputHandler returns the handler for this primitive.
|
|
||||||
func (field *AdvancedInputField) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
|
||||||
return field.WrapInputHandler(field.inputHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) PasteHandler() func(event *tcell.EventPaste) {
|
|
||||||
return field.WrapPasteHandler(field.pasteHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) pasteHandler(event *tcell.EventPaste) {
|
|
||||||
defer field.handleInputChanges(field.text)
|
|
||||||
clip := event.Text()
|
|
||||||
leftPart := SubstringBefore(field.text, field.cursorOffset)
|
|
||||||
field.text = leftPart + clip + field.text[len(leftPart):]
|
|
||||||
field.cursorOffset += runewidth.StringWidth(clip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (field *AdvancedInputField) inputHandler(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
|
||||||
defer field.handleInputChanges(field.text)
|
|
||||||
|
|
||||||
// Process key event.
|
|
||||||
switch key := event.Key(); key {
|
|
||||||
case tcell.KeyRune:
|
|
||||||
field.TypeRune(event.Rune())
|
|
||||||
case tcell.KeyCtrlV:
|
|
||||||
field.PasteClipboard()
|
|
||||||
case tcell.KeyLeft:
|
|
||||||
field.MoveCursorLeft(event.Modifiers() == tcell.ModCtrl)
|
|
||||||
case tcell.KeyRight:
|
|
||||||
field.MoveCursorRight(event.Modifiers() == tcell.ModCtrl)
|
|
||||||
case tcell.KeyDelete:
|
|
||||||
field.RemoveNextCharacter()
|
|
||||||
case tcell.KeyCtrlU:
|
|
||||||
if field.vimBindings {
|
|
||||||
field.Clear()
|
|
||||||
}
|
|
||||||
case tcell.KeyCtrlW:
|
|
||||||
if field.vimBindings {
|
|
||||||
field.RemovePreviousWord()
|
|
||||||
}
|
|
||||||
case tcell.KeyBackspace:
|
|
||||||
field.RemovePreviousWord()
|
|
||||||
case tcell.KeyBackspace2:
|
|
||||||
field.RemovePreviousCharacter()
|
|
||||||
case tcell.KeyTab:
|
|
||||||
if field.TriggerTabComplete() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case tcell.KeyEnter, tcell.KeyEscape, tcell.KeyBacktab:
|
|
||||||
if field.done != nil {
|
|
||||||
field.done(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,8 +17,8 @@
|
|||||||
package widget
|
package widget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
"maunium.net/go/tview"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Border is a simple tview widget that renders a horizontal or vertical bar.
|
// Border is a simple tview widget that renders a horizontal or vertical bar.
|
||||||
@ -27,24 +27,37 @@ import (
|
|||||||
// If the height is 1, the bar will be horizontal.
|
// If the height is 1, the bar will be horizontal.
|
||||||
// If the width nor the height are 1, nothing will be rendered.
|
// If the width nor the height are 1, nothing will be rendered.
|
||||||
type Border struct {
|
type Border struct {
|
||||||
*tview.Box
|
Style tcell.Style
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBorder wraps a new tview Box into a new Border.
|
// NewBorder wraps a new tview Box into a new Border.
|
||||||
func NewBorder() *Border {
|
func NewBorder() *Border {
|
||||||
return &Border{tview.NewBox()}
|
return &Border{
|
||||||
|
Style: tcell.StyleDefault.Foreground(mauview.Styles.BorderColor),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *Border) Draw(screen tcell.Screen) {
|
func (border *Border) Draw(screen mauview.Screen) {
|
||||||
background := tcell.StyleDefault.Background(border.GetBackgroundColor()).Foreground(border.GetBorderColor())
|
width, height := screen.Size()
|
||||||
x, y, width, height := border.GetRect()
|
|
||||||
if width == 1 {
|
if width == 1 {
|
||||||
for borderY := y; borderY < y+height; borderY++ {
|
for borderY := 0; borderY < height; borderY++ {
|
||||||
screen.SetContent(x, borderY, tview.Borders.Vertical, nil, background)
|
screen.SetContent(0, borderY, mauview.Borders.Vertical, nil, border.Style)
|
||||||
}
|
}
|
||||||
} else if height == 1 {
|
} else if height == 1 {
|
||||||
for borderX := x; borderX < x+width; borderX++ {
|
for borderX := 0; borderX < width; borderX++ {
|
||||||
screen.SetContent(borderX, y, tview.Borders.Horizontal, nil, background)
|
screen.SetContent(borderX, 0, mauview.Borders.Horizontal, nil, border.Style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (border *Border) OnKeyEvent(event mauview.KeyEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (border *Border) OnPasteEvent(event mauview.PasteEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (border *Border) OnMouseEvent(event mauview.MouseEvent) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package widget
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maunium.net/go/tcell"
|
|
||||||
"maunium.net/go/tview"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Center wraps the given tview primitive into a Flex element in order to
|
|
||||||
// vertically and horizontally center the given primitive.
|
|
||||||
func Center(width, height int, p tview.Primitive) tview.Primitive {
|
|
||||||
return tview.NewFlex().
|
|
||||||
AddItem(nil, 0, 1, false).
|
|
||||||
AddItem(tview.NewFlex().
|
|
||||||
SetDirection(tview.FlexRow).
|
|
||||||
AddItem(nil, 0, 1, false).
|
|
||||||
AddItem(p, height, 1, true).
|
|
||||||
AddItem(nil, 0, 1, false), width, 1, true).
|
|
||||||
AddItem(nil, 0, 1, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
type transparentCenter struct {
|
|
||||||
*tview.Box
|
|
||||||
prefWidth, prefHeight int
|
|
||||||
p tview.Primitive
|
|
||||||
}
|
|
||||||
|
|
||||||
func TransparentCenter(width, height int, p tview.Primitive) tview.Primitive {
|
|
||||||
return &transparentCenter{
|
|
||||||
Box: tview.NewBox(),
|
|
||||||
prefWidth: width,
|
|
||||||
prefHeight: height,
|
|
||||||
p: p,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *transparentCenter) Draw(screen tcell.Screen) {
|
|
||||||
x, y, width, height := tc.GetRect()
|
|
||||||
if width > tc.prefWidth {
|
|
||||||
x += (width - tc.prefWidth) / 2
|
|
||||||
width = tc.prefWidth
|
|
||||||
}
|
|
||||||
if height > tc.prefHeight {
|
|
||||||
y += (height - tc.prefHeight) / 2
|
|
||||||
height = tc.prefHeight
|
|
||||||
}
|
|
||||||
tc.p.SetRect(x, y, width, height)
|
|
||||||
tc.p.Draw(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *transparentCenter) Focus(delegate func(p tview.Primitive)) {
|
|
||||||
if delegate != nil {
|
|
||||||
delegate(tc.p)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package widget
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maunium.net/go/tcell"
|
|
||||||
"maunium.net/go/tview"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FormTextView struct {
|
|
||||||
*tview.TextView
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ftv *FormTextView) GetLabel() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ftv *FormTextView) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem {
|
|
||||||
return ftv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ftv *FormTextView) GetFieldWidth() int {
|
|
||||||
_, _, w, _ := ftv.TextView.GetRect()
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ftv *FormTextView) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
|
||||||
ftv.SetDoneFunc(handler)
|
|
||||||
return ftv
|
|
||||||
}
|
|
@ -18,28 +18,31 @@ package widget
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"maunium.net/go/tcell"
|
|
||||||
"maunium.net/go/tview"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
|
"maunium.net/go/mauview"
|
||||||
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriteLineSimple(screen tcell.Screen, line string, x, y int) {
|
func WriteLineSimple(screen mauview.Screen, line string, x, y int) {
|
||||||
WriteLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault)
|
WriteLine(screen, mauview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLineSimpleColor(screen tcell.Screen, line string, x, y int, color tcell.Color) {
|
func WriteLineSimpleColor(screen mauview.Screen, line string, x, y int, color tcell.Color) {
|
||||||
WriteLine(screen, tview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault.Foreground(color))
|
WriteLine(screen, mauview.AlignLeft, line, x, y, 1<<30, tcell.StyleDefault.Foreground(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLineColor(screen tcell.Screen, align int, line string, x, y, maxWidth int, color tcell.Color) {
|
func WriteLineColor(screen mauview.Screen, align int, line string, x, y, maxWidth int, color tcell.Color) {
|
||||||
WriteLine(screen, align, line, x, y, maxWidth, tcell.StyleDefault.Foreground(color))
|
WriteLine(screen, align, line, x, y, maxWidth, tcell.StyleDefault.Foreground(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLine(screen tcell.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) {
|
func WriteLine(screen mauview.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) {
|
||||||
offsetX := 0
|
offsetX := 0
|
||||||
if align == tview.AlignRight {
|
if align == mauview.AlignRight {
|
||||||
offsetX = maxWidth - runewidth.StringWidth(line)
|
// TODO is mauview.StringWidth correct here?
|
||||||
|
offsetX = maxWidth - mauview.StringWidth(line)
|
||||||
}
|
}
|
||||||
if offsetX < 0 {
|
if offsetX < 0 {
|
||||||
offsetX = 0
|
offsetX = 0
|
||||||
@ -60,12 +63,12 @@ func WriteLine(screen tcell.Screen, align int, line string, x, y, maxWidth int,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLinePadded(screen tcell.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) {
|
func WriteLinePadded(screen mauview.Screen, align int, line string, x, y, maxWidth int, style tcell.Style) {
|
||||||
padding := strconv.Itoa(maxWidth)
|
padding := strconv.Itoa(maxWidth)
|
||||||
if align == tview.AlignRight {
|
if align == mauview.AlignRight {
|
||||||
line = fmt.Sprintf("%"+padding+"s", line)
|
line = fmt.Sprintf("%"+padding+"s", line)
|
||||||
} else {
|
} else {
|
||||||
line = fmt.Sprintf("%-"+padding+"s", line)
|
line = fmt.Sprintf("%-"+padding+"s", line)
|
||||||
}
|
}
|
||||||
WriteLine(screen, tview.AlignLeft, line, x, y, maxWidth, style)
|
WriteLine(screen, mauview.AlignLeft, line, x, y, maxWidth, style)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user