diff options
| author | citrons <citrons@mondecitronne.com> | 2025-06-01 15:38:17 -0500 |
|---|---|---|
| committer | citrons <citrons@mondecitronne.com> | 2025-06-01 15:38:17 -0500 |
| commit | e2dc5b6fbb6adf6f379ef0123c214a196211c305 (patch) | |
| tree | cc976665dd2e0916af100b5c0f7f629cc1936b01 /client | |
| parent | e740e5478a43358fbbd79636483d000e01f88b7e (diff) | |
joining channels
Diffstat (limited to 'client')
| -rw-r--r-- | client/application.go | 159 | ||||
| -rw-r--r-- | client/buffer/buffer.go | 31 | ||||
| -rw-r--r-- | client/channel_window.go | 189 | ||||
| -rw-r--r-- | client/cmd_buffer.go | 77 | ||||
| -rw-r--r-- | client/cmd_window.go | 134 | ||||
| -rw-r--r-- | client/command.go | 78 | ||||
| -rw-r--r-- | client/empty_window.go | 29 | ||||
| -rw-r--r-- | client/main.go | 23 | ||||
| -rw-r--r-- | client/object/object.go | 1 | ||||
| -rw-r--r-- | client/ui.go | 86 | ||||
| -rw-r--r-- | client/window/window.go | 38 |
11 files changed, 713 insertions, 132 deletions
diff --git a/client/application.go b/client/application.go index 72ec078..b1c5865 100644 --- a/client/application.go +++ b/client/application.go @@ -3,45 +3,49 @@ package main import ( "citrons.xyz/talk/client/client" "citrons.xyz/talk/client/object" - "citrons.xyz/talk/client/buffer" + "citrons.xyz/talk/client/window" "citrons.xyz/talk/proto" - "citrons.xyz/talk/tui" - "zgo.at/termfo/keys" - "os" ) type application struct { client.Client + quit bool + connected bool reconnecting bool authenticated bool uid string cache object.ObjCache - currentBuffer *buffer.Buffer - cmdBuffer cmdBuffer + windowCache window.WindowCache + currentWindow window.Location + cmdWindow cmdWindow } func newApplication(serverAddress string) *application { var app application app.Client = client.New(serverAddress) + app.currentWindow = app.cmdWindow.Location() app.cache = object.NewCache(&app) - app.currentBuffer = &app.cmdBuffer.Buffer + app.windowCache = window.NewCache() - app.cmdBuffer.info("connecting to %s", app.Client.Address) + app.cmdWindow.info("connecting to %s", app.Client.Address) return &app } func (a *application) OnConnect() { + a.connected = true a.reconnecting = false - a.cmdBuffer.info("connected to %s", a.Client.Address) - - a.auth("test user") + a.cache = object.NewCache(a) + a.windowCache = window.NewCache() + a.cmdWindow.info("connected to %s", a.Client.Address) + a.cmdWindow.loginMode() } func (a *application) OnDisconnect(err error) { + a.connected = false a.authenticated = false a.uid = "" if !a.reconnecting { - a.cmdBuffer.err( + a.cmdWindow.err( "disconnected from %s: %s\nreconnecting...", a.Client.Address, err, ) a.reconnecting = true @@ -50,6 +54,11 @@ func (a *application) OnDisconnect(err error) { func (a *application) OnEvent(cmd proto.Command) { switch cmd.Kind { + case "p": + cw := a.windowCache.Get(channelLocation {cmd.Target}).(*channelWindow) + if cw != nil && len(cmd.Args) > 0 { + cw.put(cmd.Args[0]) + } case "update": if len(cmd.Args) > 0 { a.cache.Update(cmd.Target, cmd.Args[0]) @@ -63,7 +72,7 @@ func (a *application) OnResponse(requestId string, cmd proto.Command) { switch cmd.Kind { case "you-are": if len(cmd.Args) > 0 { - a.cmdBuffer.info("your name is: %s", cmd.Args[0].Fields[""]) + a.cmdWindow.info("your name is: %s", cmd.Args[0].Fields[""]) } } } @@ -84,19 +93,28 @@ func (a *application) GetInfo(id string, callback func(proto.Command)) { a.Request(proto.NewCmd("i", id), callback) } -func (a *application) auth(name string) { +func (a *application) auth(name string, authCallback func(success bool)) { callback := func(response proto.Command) { switch response.Kind { case "you-are": if len(response.Args) == 0 { + authCallback(false) break } me := response.Args[0] a.authenticated = true a.uid = me.Id a.cache.Watch(a.uid) + if authCallback != nil { + authCallback(true) + } case "fail": - // todo + if len(response.Args) != 0 { + a.cmdWindow.err(proto.Strfail(response.Args[0])) + } + if authCallback != nil { + authCallback(false) + } } } a.Request(proto.NewCmd("auth", "", proto.Object { @@ -104,39 +122,98 @@ func (a *application) auth(name string) { }), callback) } -func (a *application) onInput(ev tui.Event) { - tui.Selected = "input" +func (a *application) sendUpdate(o proto.Object, cb func(proto.Command)) { + a.Request(proto.NewCmd("update", o.Id, o), func(response proto.Command) { + if response.Kind == "ok" { + a.cache.Update(o.Id, o) + } + cb(response) + }) +} - a.currentBuffer.Scroll(-ev.Mouse.Scroll * 2) - scroll := tui.Size().Height - 5 - switch ev.Key { - case keys.PageUp: - a.currentBuffer.Scroll(scroll) - case keys.PageDown: - a.currentBuffer.Scroll(-scroll) +func (a *application) lookup( + name string, kind string, callback func(*proto.Object, *proto.Fail)) { + cb := func(response proto.Command) { + switch response.Kind { + case "i": + if len(response.Args) > 0 { + callback(&response.Args[0], nil) + } + case "fail": + if len(response.Args) > 0 { + f := proto.Fail(response.Args[0]) + callback(nil, &f) + } + } } - - a.currentBuffer.TextInput.Update(ev) + o := proto.Object {kind, "", map[string]string {"": name}} + a.Request(proto.NewCmd("lookup", "", o), cb) } -func (a *application) show() { - tui.Clear() - s := tui.Size() - tui.Push("", tui.Box { - Width: tui.BoxSize(s.Width), Height: tui.BoxSize(s.Height), +func (a *application) join(channelName string) { + a.lookup(channelName, "channel", func(ch *proto.Object, f *proto.Fail) { + if f != nil { + a.cmdWindow.err(f.Error()) + return + } + a.Request(proto.NewCmd("join", ch.Id), func(response proto.Command) { + switch response.Kind { + case "ok": + a.currentWindow = channelLocation {id: ch.Id} + case "fail": + if len(response.Args) > 0 { + f := proto.Fail(response.Args[0]) + a.cmdWindow.err(f.Error()) + } + } + }) }) +} - a.currentBuffer.Show("buffer") - tui.Push("status", tui.Box { - Width: tui.Fill, Height: tui.BoxSize(1), Dir: tui.Right, - Style: &tui.Style {Bg: tui.White, Fg: tui.Black}, +func (a *application) createChannel(name string) { + ch := proto.Object {"channel", "", map[string]string {"": name}} + a.Request(proto.NewCmd("create", "", ch), func(response proto.Command) { + switch response.Kind { + case "create": + if len(response.Args) > 0 { + ch := response.Args[0] + a.currentWindow = channelLocation {id: ch.Id} + } + case "fail": + if len(response.Args) > 0 { + f := proto.Fail(response.Args[0]) + a.cmdWindow.err(f.Error()) + } + } }) - tui.Pop() - a.currentBuffer.TextInput.Show("input") +} + +func (a *application) getNick() string { + if !a.authenticated { + return "" + } + u := a.cache.Get(a.uid) + if u != nil { + return u.Fields[""] + } + return "" +} - tui.Pop() - tui.DrawLayout() - if tui.Present() != nil { - os.Exit(-1) +func (a *application) setNick(newName string) { + if !a.authenticated { + return + } + callback := func(response proto.Command) { + switch response.Kind { + case "fail": + if len(response.Args) != 0 { + a.cmdWindow.err(proto.Strfail(response.Args[0])) + } + case "ok": + a.cmdWindow.info("your name is: %s", newName) + } } + a.sendUpdate( + proto.Object {"u", a.uid, map[string]string {"": newName}}, callback, + ) } diff --git a/client/buffer/buffer.go b/client/buffer/buffer.go index 230fba9..fea88a9 100644 --- a/client/buffer/buffer.go +++ b/client/buffer/buffer.go @@ -5,10 +5,9 @@ import ( ) type Buffer struct { - top *bufList - bottom *bufList + top *List + bottom *List scroll tui.ScrollState - TextInput tui.TextInput Closed bool } @@ -17,15 +16,31 @@ type Message interface { Show(odd bool) } -type bufList struct { +type List struct { msg Message odd bool - prev *bufList - next *bufList + prev *List + next *List +} + +func (l List) Prev() *List { + return l.prev +} + +func (l List) Next() *List { + return l.next +} + +func (b *Buffer) Top() *List { + return b.top +} + +func (b *Buffer) Bottom() *List { + return b.bottom } func (b *Buffer) Add(msg Message) { - l := &bufList {msg: msg} + l := &List {msg: msg} if b.bottom != nil { b.bottom.next = l l.prev = b.bottom @@ -38,7 +53,7 @@ func (b *Buffer) Add(msg Message) { } func (b *Buffer) AddTop(msg Message) { - l := bufList {msg: msg} + l := List {msg: msg} if b.top != nil { b.top.prev = &l l.next = b.top diff --git a/client/channel_window.go b/client/channel_window.go new file mode 100644 index 0000000..abeaa77 --- /dev/null +++ b/client/channel_window.go @@ -0,0 +1,189 @@ +package main + +import ( + "citrons.xyz/talk/client/buffer" + "citrons.xyz/talk/client/window" + "citrons.xyz/talk/proto" + "citrons.xyz/talk/tui" + "strconv" + "time" +) + +type channelLocation struct { + id string +} + +type channelWindow struct { + location channelLocation + buf buffer.Buffer + input tui.TextInput + watchedUsers map[string]bool + loadingHistory bool +} + +type channelMsg struct { + proto.Object + window *channelWindow +} + +func (cl channelLocation) CreateWindow() window.Window { + if !globalApp.connected { + return nil + } + cw := &channelWindow { + location: cl, watchedUsers: make(map[string]bool), + } + globalApp.cache.Watch(cl.id) + return cw +} + +func (cw *channelWindow) getChannel() *proto.Object { + return globalApp.cache.Get(cw.location.id) +} + +func (cw *channelWindow) watchUser(uid string) { + if !cw.watchedUsers[uid] { + globalApp.cache.Watch(uid) + cw.watchedUsers[uid] = true + } +} + +func (cw *channelWindow) username(uid string) string { + cw.watchUser(uid) + u := globalApp.cache.Get(uid) + if u != nil { + return u.Fields[""] + } + return "..." +} + +func (cw *channelWindow) put(msg proto.Object) { + cw.buf.Add(channelMsg {msg, cw}) +} + +func (cw *channelWindow) Location() window.Location { + return cw.location +} + +func (cw *channelWindow) Kill() { + globalApp.cache.Unwatch(cw.location.id) + for u := range cw.watchedUsers { + globalApp.cache.Watch(u) + } +} + +func (cw *channelWindow) Buffer() *buffer.Buffer { + return &cw.buf +} + +func (cw *channelWindow) Input() *tui.TextInput { + return &cw.input +} + +func (cw *channelWindow) Send(text string) { + if !globalApp.connected || !globalApp.authenticated { + return + } + cb := func(response proto.Command) { + switch response.Kind { + case "p": + if len(response.Args) > 0 { + cw.put(response.Args[0]) + } + case "fail": + if len(response.Args) > 0 { + f := proto.Fail(response.Args[0]) + globalApp.cmdWindow.err(f.Error()) + } + } + } + msg := proto.Object {"m", "", map[string]string {"": text}} + globalApp.Request(proto.NewCmd("p", cw.location.id, msg), cb) + cw.input.SetText("") +} + +func (cw *channelWindow) ShowStatusLine() { + ch := cw.getChannel() + if ch == nil { + return + } + tui.Text(ch.Fields[""], nil) + if cw.loadingHistory { + tui.Text("...", nil) + } +} + +func (m channelMsg) Id() string { + return m.Object.Id +} + +func (m channelMsg) showDate(bg int32) { + unix, _ := strconv.Atoi(m.Fields["t"]) + time := time.Unix(int64(unix), 0).Format(time.DateTime) + tui.Push("", tui.Box {Width: tui.TextSize, Height: 1, NoWrap: true}) + tui.Text(" " + time, &tui.Style {Fg: tui.BrightBlack, Bg: bg}) + tui.Pop() + tui.Push("", tui.Box {Width: tui.Fill, Height: 1}) + tui.Pop() +} + +func (m channelMsg) Show(odd bool) { + var bg int32 = tui.Black + if odd { + bg = 236 + } + + switch m.Kind { + case "join": + tui.Push("", tui.Box { + Width: tui.Fill, Height: tui.Children, Dir: tui.Left, + }) + m.showDate(bg) + tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) + tui.Text("-> ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) + tui.Text("join", &tui.Style {Fg: tui.Blue, Bg: bg, Bold: true}) + tui.Text(": ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) + tui.Text(m.window.username(m.Fields[""]), nil) + tui.Pop() + tui.Pop() + case "leave": + tui.Push("", tui.Box { + Width: tui.Fill, Height: tui.Children, Dir: tui.Left, + }) + m.showDate(bg) + tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) + tui.Text("<- ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) + tui.Text("leave", &tui.Style {Fg: tui.Blue, Bg: bg, Bold: true}) + tui.Text(": ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) + tui.Text(m.window.username(m.Fields[""]), nil) + tui.Pop() + tui.Pop() + case "m": + var usernameFg int32 = tui.White + if m.Fields["f"] == globalApp.uid { + usernameFg = tui.Cyan + } + + tui.Push("", tui.Box {Width: tui.Fill, Height: 1, Dir: tui.Left}) + m.showDate(bg) + tui.Push("", tui.Box {Width: tui.TextSize, Height: 1, NoWrap: true}) + tui.Text(m.window.username(m.Fields["f"]), &tui.Style { + Fg: usernameFg, Bg: bg, Bold: true, + }) + tui.Pop() + tui.Pop() + + tui.Push("", tui.Box { + Width: tui.TextSize, Height: tui.TextSize, + Margins: [4]int {1, 0, 0, 0}, + }) + tui.Text(m.Fields[""], nil) + tui.Pop() + default: + tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) + tui.Text("[", nil) + tui.Text(m.Kind, &tui.Style {Fg: tui.White, Bg: bg, Italic: true}) + tui.Text("]", nil) + tui.Pop() + } +} diff --git a/client/cmd_buffer.go b/client/cmd_buffer.go deleted file mode 100644 index ae96a0f..0000000 --- a/client/cmd_buffer.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "citrons.xyz/talk/client/buffer" - "citrons.xyz/talk/tui" - "fmt" -) - -type cmdBuffer struct { - buffer.Buffer -} - -type logMsg struct { - index int - text string - logType logType -} -var lastIndex = 0 - -type logType int -const ( - logInfo = iota - logErr - logCmd -) - -func (m logMsg) Id() string { - return fmt.Sprintf("log.%d", m.index) -} - -func (m logMsg) Show(odd bool) { - var style *tui.Style - switch m.logType { - case logErr: - var bg int32 = tui.Red - if odd { - bg = tui.BrightRed - } - style = &tui.Style {Bg: bg, Fg: tui.Black} - case logCmd: - var bg int32 = tui.Blue - if odd { - bg = tui.BrightBlue - } - style = &tui.Style {Bg: bg, Fg: tui.Black} - default: - } - - tui.Push("", tui.Box { - Width: tui.Fill, Height: tui.Children, Style: style, Dir: tui.Right, - }) - - tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) - tui.Text("* ", nil) - tui.Pop() - - tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) - tui.Text(m.text, nil) - tui.Pop() - - tui.Pop() -} - -func (b *cmdBuffer) info(f string, a ...any) { - lastIndex++ - b.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logInfo}) -} - -func (b *cmdBuffer) err(f string, a ...any) { - lastIndex++ - b.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logErr}) -} - -func (b *cmdBuffer) cmd(f string, a ...any) { - lastIndex++ - b.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logCmd}) -} diff --git a/client/cmd_window.go b/client/cmd_window.go new file mode 100644 index 0000000..6a69bb9 --- /dev/null +++ b/client/cmd_window.go @@ -0,0 +1,134 @@ +package main + +import ( + "citrons.xyz/talk/client/buffer" + "citrons.xyz/talk/client/window" + "citrons.xyz/talk/tui" + "fmt" +) + +type cmdWindowLocation struct {} + +type cmdWindow struct { + buf buffer.Buffer + input tui.TextInput + login bool +} + +type logMsg struct { + index int + text string + logType logType +} +var lastIndex = 0 + +type logType int +const ( + logInfo = iota + logErr + logCmd +) + +func (m logMsg) Id() string { + return fmt.Sprintf("log.%d", m.index) +} + +func (m logMsg) Show(odd bool) { + var style *tui.Style + switch m.logType { + case logErr: + var bg int32 = tui.Red + if odd { + bg = tui.BrightRed + } + style = &tui.Style {Bg: bg, Fg: tui.White} + case logCmd: + var bg int32 = tui.Blue + if odd { + bg = tui.BrightBlue + } + style = &tui.Style {Bg: bg, Fg: tui.Black} + default: + } + + tui.Push("", tui.Box { + Width: tui.Fill, Height: tui.Children, Style: style, Dir: tui.Right, + }) + + tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) + tui.Text("* ", nil) + tui.Pop() + + tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) + tui.Text(m.text, nil) + tui.Pop() + + tui.Pop() +} + +func (l cmdWindowLocation) CreateWindow() window.Window { + return &globalApp.cmdWindow +} + +func (w *cmdWindow) Location() window.Location { + return cmdWindowLocation {} +} + +func (w *cmdWindow) Kill() {} + +func (w *cmdWindow) Buffer() *buffer.Buffer { + return &w.buf +} + +func (w *cmdWindow) Input() *tui.TextInput { + return &w.input +} + +func (w *cmdWindow) Send(text string) { + if w.login { + w.login = false + previousText := w.input.Text() + w.input.SetText("") + globalApp.auth(text, func(success bool) { + if !success { + w.loginMode() + w.input.SetText(previousText) + } + }) + } +} + +func (w *cmdWindow) ShowStatusLine() { + if !w.login { + tui.Text("command window", &tui.Style { + Bg: tui.White, Fg: tui.Black, Italic: true, + }) + } else { + tui.Text("[", nil) + tui.Text("login", &tui.Style { + Bg: tui.White, Fg: tui.Blue, Bold: true, + }) + tui.Text("]", nil) + tui.Text(" username:", nil) + } +} + +func (w *cmdWindow) loginMode() { + w.login = true + w.input.SetText("") +} + +func (w *cmdWindow) info(f string, a ...any) { + lastIndex++ + w.buf.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logInfo}) +} + +func (w *cmdWindow) err(f string, a ...any) { + lastIndex++ + w.buf.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logErr}) +} + +func (w *cmdWindow) cmd(f string, a ...any) { + lastIndex++ + w.buf.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logCmd}) +} diff --git a/client/command.go b/client/command.go new file mode 100644 index 0000000..f505f9a --- /dev/null +++ b/client/command.go @@ -0,0 +1,78 @@ +package main + +func isCommand(text string) (bool, string) { + if text[0] == '/' { + if len(text) > 1 && text[1] == '/' { + text = text[1:] + return false, text + } + return true, text + } + return false, text +} + +func (a *application) processCommand(text string) { + text = text[1:] + args := []string {""} + escaped := false + quoted := false + for _, c := range text { + if escaped { + args[len(args) - 1] += string(c) + continue + } + switch c { + case '\\': + escaped = true + case '"': + quoted = !quoted + case ' ': + if !quoted { + if args[len(args) - 1] != "" { + args = append(args, "") + } + break + } + fallthrough + default: + args[len(args) - 1] += string(c) + } + } + if len(text) > len(args[0]) { + text = text[len(args[0]) + 1:] + } else { + text = "" + } + a.doCommand(args[0], args[1:], text) +} + +func (a *application) doCommand(command string, args []string, text string) { + if !a.connected { + return + } + argN := func(n int) { + if len(args) != n { + a.cmdWindow.err( + "%s: expected %d arguments, was %d", command, n, len(args), + ) + } + } + switch command { + case "nick": + a.setNick(text) + case "join": + argN(1) + if a.authenticated { + a.join(args[0]) + } + case "create": + argN(1) + if a.authenticated { + a.createChannel(args[0]) + } + case "quit": + a.quit = true + default: + a.cmdWindow.err("unknown command: /" + command) + } +} diff --git a/client/empty_window.go b/client/empty_window.go new file mode 100644 index 0000000..e40ab96 --- /dev/null +++ b/client/empty_window.go @@ -0,0 +1,29 @@ +package main + +import ( + "citrons.xyz/talk/client/window" + "citrons.xyz/talk/client/buffer" + "citrons.xyz/talk/tui" +) + +type emptyWindow struct { + location window.Location +} + +func (w emptyWindow) Location() window.Location { + return w.location +} + +func (w emptyWindow) Kill() {} + +func (w emptyWindow) Buffer() *buffer.Buffer { + return &buffer.Buffer {} +} + +func (w emptyWindow) Input() *tui.TextInput { + return &tui.TextInput {} +} + +func (w emptyWindow) Send(text string) {} + +func (w emptyWindow) ShowStatusLine() {} diff --git a/client/main.go b/client/main.go index 4c65752..a9929be 100644 --- a/client/main.go +++ b/client/main.go @@ -7,30 +7,41 @@ import ( "os" ) +var globalApp *application + func main() { err := tui.Start() if err != nil { fmt.Fprintln(os.Stderr, "error initializing terminal: ", err) + os.Exit(-1) } + defer func() { + tui.End() + fmt.Println("bye!") + }() - app := newApplication("localhost:27508") - go app.RunClient() + globalApp = newApplication("localhost:27508") + go globalApp.RunClient() + defer globalApp.Stop() drawTick := time.Tick(time.Second / 60) redraw := true for { select { - case m := <-app.Messages(): - m.Handle(app) + case m := <-globalApp.Messages(): + m.Handle(globalApp) redraw = true case e := <-tui.Events(): - app.onInput(e) + globalApp.onInput(e) redraw = true case <-drawTick: if redraw { - app.show() + globalApp.show() redraw = false } } + if globalApp.quit == true { + return + } } } diff --git a/client/object/object.go b/client/object/object.go index f42eaf8..5215993 100644 --- a/client/object/object.go +++ b/client/object/object.go @@ -52,6 +52,7 @@ func (oc *ObjCache) Watch(id string) { if !ok { oc.stream.Sub(id) } + entry.refCount++ oc.objects[id] = entry } diff --git a/client/ui.go b/client/ui.go new file mode 100644 index 0000000..386645e --- /dev/null +++ b/client/ui.go @@ -0,0 +1,86 @@ +package main + +import ( + "citrons.xyz/talk/client/window" + "citrons.xyz/talk/tui" + "zgo.at/termfo/keys" + "os" +) + +func (a *application) getWin() window.Window { + win := a.windowCache.Open(a.currentWindow) + if win == nil { + return emptyWindow {a.currentWindow} + } + return win +} + +func (a *application) onInput(ev tui.Event) { + tui.Selected = "input" + + win := a.getWin() + win.Input().Update(ev) + + buf := win.Buffer() + buf.Scroll(-ev.Mouse.Scroll * 2) + scroll := tui.Size().Height - 5 + switch ev.Key { + case keys.PageUp: + buf.Scroll(scroll) + case keys.PageDown: + buf.Scroll(-scroll) + case keys.Enter: + input := win.Input() + if !input.IsEmpty() { + is, text := isCommand(input.Text()) + if !is { + win.Send(text) + } else { + a.processCommand(text) + input.SetText("") + } + } + case 'h' | keys.Ctrl: + a.currentWindow = cmdWindowLocation {} + } + +} + +func (a *application) showNickBox() { + tui.Push("username", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) + tui.Text("[", nil) + name := a.getNick() + name = string([]rune(name)[:8]) + tui.Text(name, nil) + tui.Text("] ", nil) + tui.Pop() +} + +func (a *application) show() { + tui.Clear() + s := tui.Size() + tui.Push("", tui.Box { + Width: tui.BoxSize(s.Width), Height: tui.BoxSize(s.Height), + }) + + a.getWin().Buffer().Show("buffer") + tui.Push("status", tui.Box { + Width: tui.Fill, Height: tui.BoxSize(1), Dir: tui.Right, + Style: &tui.Style {Bg: tui.White, Fg: tui.Black}, + }) + a.getWin().ShowStatusLine() + tui.Pop() + + tui.Push("input container", tui.Box { + Width: tui.Fill, Height: tui.Children, Dir: tui.Right, + }) + a.showNickBox() + a.getWin().Input().Show("input") + tui.Pop() + + tui.Pop() + tui.DrawLayout() + if tui.Present() != nil { + os.Exit(-1) + } +}
\ No newline at end of file diff --git a/client/window/window.go b/client/window/window.go new file mode 100644 index 0000000..a877f7b --- /dev/null +++ b/client/window/window.go @@ -0,0 +1,38 @@ +package window + +import ( + "citrons.xyz/talk/client/buffer" + "citrons.xyz/talk/tui" +) + +type Location interface { + CreateWindow() Window +} + +type Window interface { + Location() Location + Kill() + Buffer() *buffer.Buffer + Input() *tui.TextInput + Send(text string) + ShowStatusLine() +} + +type WindowCache struct { + windows map[Location]Window +} + +func NewCache() WindowCache { + return WindowCache {make(map[Location]Window)} +} + +func (wc *WindowCache) Open(l Location) Window { + if wc.windows[l] == nil { + wc.windows[l] = l.CreateWindow() + } + return wc.windows[l] +} + +func (wc *WindowCache) Get(l Location) Window { + return wc.windows[l] +} |
