1// +build ignore
2
3// Copyright 2015 The TCell Authors
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use file except in compliance with the License.
7// You may obtain a copy of the license at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// mouse displays a text box and tests mouse interaction.  As you click
18// and drag, boxes are displayed on screen.  Other events are reported in
19// the box.  Press ESC twice to exit the program.
20package main
21
22import (
23	"fmt"
24	"os"
25	"os/exec"
26	"runtime"
27
28	"github.com/gdamore/tcell/v2"
29	"github.com/gdamore/tcell/v2/encoding"
30
31	"github.com/mattn/go-runewidth"
32)
33
34var defStyle tcell.Style
35
36func emitStr(s tcell.Screen, x, y int, style tcell.Style, str string) {
37	for _, c := range str {
38		var comb []rune
39		w := runewidth.RuneWidth(c)
40		if w == 0 {
41			comb = []rune{c}
42			c = ' '
43			w = 1
44		}
45		s.SetContent(x, y, c, comb, style)
46		x += w
47	}
48}
49
50func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, r rune) {
51	if y2 < y1 {
52		y1, y2 = y2, y1
53	}
54	if x2 < x1 {
55		x1, x2 = x2, x1
56	}
57
58	for col := x1; col <= x2; col++ {
59		s.SetContent(col, y1, tcell.RuneHLine, nil, style)
60		s.SetContent(col, y2, tcell.RuneHLine, nil, style)
61	}
62	for row := y1 + 1; row < y2; row++ {
63		s.SetContent(x1, row, tcell.RuneVLine, nil, style)
64		s.SetContent(x2, row, tcell.RuneVLine, nil, style)
65	}
66	if y1 != y2 && x1 != x2 {
67		// Only add corners if we need to
68		s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
69		s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
70		s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
71		s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
72	}
73	for row := y1 + 1; row < y2; row++ {
74		for col := x1 + 1; col < x2; col++ {
75			s.SetContent(col, row, r, nil, style)
76		}
77	}
78}
79
80func drawSelect(s tcell.Screen, x1, y1, x2, y2 int, sel bool) {
81
82	if y2 < y1 {
83		y1, y2 = y2, y1
84	}
85	if x2 < x1 {
86		x1, x2 = x2, x1
87	}
88	for row := y1; row <= y2; row++ {
89		for col := x1; col <= x2; col++ {
90			mainc, combc, style, width := s.GetContent(col, row)
91			if style == tcell.StyleDefault {
92				style = defStyle
93			}
94			style = style.Reverse(sel)
95			s.SetContent(col, row, mainc, combc, style)
96			col += width - 1
97		}
98	}
99}
100
101// This program just shows simple mouse and keyboard events.  Press ESC twice to
102// exit.
103func main() {
104
105	shell := os.Getenv("SHELL")
106	if shell == "" {
107		if runtime.GOOS == "windows" {
108			shell = "CMD.EXE"
109		} else {
110			shell = "/bin/sh"
111		}
112	}
113
114	encoding.Register()
115
116	s, e := tcell.NewScreen()
117	if e != nil {
118		fmt.Fprintf(os.Stderr, "%v\n", e)
119		os.Exit(1)
120	}
121	if e := s.Init(); e != nil {
122		fmt.Fprintf(os.Stderr, "%v\n", e)
123		os.Exit(1)
124	}
125	defStyle = tcell.StyleDefault.
126		Background(tcell.ColorReset).
127		Foreground(tcell.ColorReset)
128	s.SetStyle(defStyle)
129	s.EnableMouse()
130	s.EnablePaste()
131	s.Clear()
132
133	posfmt := "Mouse: %d, %d  "
134	btnfmt := "Buttons: %s"
135	keyfmt := "Keys: %s"
136	pastefmt := "Paste: [%d] %s"
137	white := tcell.StyleDefault.
138		Foreground(tcell.ColorWhite).Background(tcell.ColorRed)
139
140	mx, my := -1, -1
141	ox, oy := -1, -1
142	bx, by := -1, -1
143	w, h := s.Size()
144	lchar := '*'
145	bstr := ""
146	lks := ""
147	pstr := ""
148	ecnt := 0
149	pasting := false
150
151	for {
152		drawBox(s, 1, 1, 42, 7, white, ' ')
153		emitStr(s, 2, 2, white, "Press ESC twice to exit, C to clear.")
154		emitStr(s, 2, 3, white, fmt.Sprintf(posfmt, mx, my))
155		emitStr(s, 2, 4, white, fmt.Sprintf(btnfmt, bstr))
156		emitStr(s, 2, 5, white, fmt.Sprintf(keyfmt, lks))
157
158		ps := pstr
159		if len(ps) > 26 {
160			ps = "..." + ps[len(ps)-24:]
161		}
162		emitStr(s, 2, 6, white, fmt.Sprintf(pastefmt, len(pstr), ps))
163
164		s.Show()
165		bstr = ""
166		ev := s.PollEvent()
167		st := tcell.StyleDefault.Background(tcell.ColorRed)
168		up := tcell.StyleDefault.
169			Background(tcell.ColorBlue).
170			Foreground(tcell.ColorBlack)
171		w, h = s.Size()
172
173		// always clear any old selection box
174		if ox >= 0 && oy >= 0 && bx >= 0 {
175			drawSelect(s, ox, oy, bx, by, false)
176		}
177
178		switch ev := ev.(type) {
179		case *tcell.EventResize:
180			s.Sync()
181			s.SetContent(w-1, h-1, 'R', nil, st)
182		case *tcell.EventKey:
183			s.SetContent(w-2, h-2, ev.Rune(), nil, st)
184			if pasting {
185				s.SetContent(w-1, h-1, 'P', nil, st)
186				if ev.Key() == tcell.KeyRune {
187					pstr = pstr + string(ev.Rune())
188				} else {
189					pstr = pstr + "\ufffd" // replacement for now
190				}
191				lks = ""
192				continue
193			}
194			pstr = ""
195			s.SetContent(w-1, h-1, 'K', nil, st)
196			if ev.Key() == tcell.KeyEscape {
197				ecnt++
198				if ecnt > 1 {
199					s.Fini()
200					os.Exit(0)
201				}
202			} else if ev.Key() == tcell.KeyCtrlL {
203				s.Sync()
204			} else if ev.Key() == tcell.KeyCtrlZ {
205				// CtrlZ doesn't really suspend the process, but we use it to execute a subshell.
206				if err := s.Suspend(); err == nil {
207					fmt.Printf("Executing shell (%s -l)...\n", shell)
208					fmt.Printf("Exit the shell to return to the demo.\n")
209					c := exec.Command(shell, "-l" ) // NB: -l works for cmd.exe too (ignored)
210					c.Stdin = os.Stdin
211					c.Stdout = os.Stdout
212					c.Stderr = os.Stderr
213					c.Run()
214					if err := s.Resume(); err != nil {
215						panic("failed to resume: " + err.Error())
216					}
217				}
218			} else {
219				ecnt = 0
220				if ev.Rune() == 'C' || ev.Rune() == 'c' {
221					s.Clear()
222				}
223			}
224			lks = ev.Name()
225		case *tcell.EventPaste:
226			pasting = ev.Start()
227			if pasting {
228				pstr = ""
229			}
230		case *tcell.EventMouse:
231			x, y := ev.Position()
232			button := ev.Buttons()
233			for i := uint(0); i < 8; i++ {
234				if int(button)&(1<<i) != 0 {
235					bstr += fmt.Sprintf(" Button%d", i+1)
236				}
237			}
238			if button&tcell.WheelUp != 0 {
239				bstr += " WheelUp"
240			}
241			if button&tcell.WheelDown != 0 {
242				bstr += " WheelDown"
243			}
244			if button&tcell.WheelLeft != 0 {
245				bstr += " WheelLeft"
246			}
247			if button&tcell.WheelRight != 0 {
248				bstr += " WheelRight"
249			}
250			// Only buttons, not wheel events
251			button &= tcell.ButtonMask(0xff)
252			ch := '*'
253
254			if button != tcell.ButtonNone && ox < 0 {
255				ox, oy = x, y
256			}
257			switch ev.Buttons() {
258			case tcell.ButtonNone:
259				if ox >= 0 {
260					bg := tcell.Color((lchar-'0')*2) | tcell.ColorValid
261					drawBox(s, ox, oy, x, y,
262						up.Background(bg),
263						lchar)
264					ox, oy = -1, -1
265					bx, by = -1, -1
266				}
267			case tcell.Button1:
268				ch = '1'
269			case tcell.Button2:
270				ch = '2'
271			case tcell.Button3:
272				ch = '3'
273			case tcell.Button4:
274				ch = '4'
275			case tcell.Button5:
276				ch = '5'
277			case tcell.Button6:
278				ch = '6'
279			case tcell.Button7:
280				ch = '7'
281			case tcell.Button8:
282				ch = '8'
283			default:
284				ch = '*'
285
286			}
287			if button != tcell.ButtonNone {
288				bx, by = x, y
289			}
290			lchar = ch
291			s.SetContent(w-1, h-1, 'M', nil, st)
292			mx, my = x, y
293		default:
294			s.SetContent(w-1, h-1, 'X', nil, st)
295		}
296
297		if ox >= 0 && bx >= 0 {
298			drawSelect(s, ox, oy, bx, by, true)
299		}
300	}
301}
302