//go:build ignore
// +build ignore
// Copyright 2025 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// mouse displays a text box and tests mouse interaction. As you click
// and drag, boxes are displayed on screen. Other events are reported in
// the box. Press ESC twice to exit the program.
package main
import (
"fmt"
"os"
"os/exec"
"runtime"
"github.com/gdamore/tcell/v3"
"github.com/gdamore/tcell/v3/color"
)
var defStyle tcell.Style
func isCtrlRune(ev *tcell.EventKey, r string) bool {
return ev.Key() == tcell.KeyRune && ev.Str() == r && ev.Modifiers()&tcell.ModCtrl != 0
}
func keyboardProtocolName(p tcell.KeyProtocol) string {
switch p {
case tcell.LegacyKeyboard:
return "Legacy"
case tcell.KittyKeyboard:
return "Kitty"
case tcell.Win32Keyboard:
return "Win32"
case tcell.XTermKeyboard:
return "XTerm"
default:
return "Unknown"
}
}
func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, r rune) {
rs := string(r)
if y2 < y1 {
y1, y2 = y2, y1
}
if x2 < x1 {
x1, x2 = x2, x1
}
for col := x1; col <= x2; col++ {
s.Put(col, y1, string(tcell.RuneHLine), style)
s.Put(col, y2, string(tcell.RuneHLine), style)
}
for row := y1 + 1; row < y2; row++ {
s.Put(x1, row, string(tcell.RuneVLine), style)
s.Put(x2, row, string(tcell.RuneVLine), style)
}
if y1 != y2 && x1 != x2 {
// Only add corners if we need to
s.Put(x1, y1, string(tcell.RuneULCorner), style)
s.Put(x2, y1, string(tcell.RuneURCorner), style)
s.Put(x1, y2, string(tcell.RuneLLCorner), style)
s.Put(x2, y2, string(tcell.RuneLRCorner), style)
}
for row := y1 + 1; row < y2; row++ {
for col := x1 + 1; col < x2; col++ {
s.Put(col, row, rs, style)
}
}
}
func drawSelect(s tcell.Screen, x1, y1, x2, y2 int, sel bool) {
if y2 < y1 {
y1, y2 = y2, y1
}
if x2 < x1 {
x1, x2 = x2, x1
}
for row := y1; row <= y2; row++ {
for col := x1; col <= x2; col++ {
str, style, width := s.Get(col, row)
if style == tcell.StyleDefault {
style = defStyle
}
style = style.Reverse(sel)
s.Put(col, row, str, style)
col += width - 1 // add an extra column if 2 cells
}
}
}
type savedMouseMarker struct {
x, y int
str string
style tcell.Style
valid bool
}
func drawMouseMarker(s tcell.Screen, marker *savedMouseMarker, x, y int, style tcell.Style) {
str, cellStyle, width := s.Get(x, y)
if width == 0 {
return
}
marker.x = x
marker.y = y
marker.str = str
marker.style = cellStyle
marker.valid = true
s.Put(x, y, string(tcell.RuneBullet), style)
}
func restoreMouseMarker(s tcell.Screen, marker *savedMouseMarker) {
if !marker.valid {
return
}
s.Put(marker.x, marker.y, marker.str, marker.style)
marker.valid = false
}
// This program just shows simple mouse and keyboard events. Press ESC twice to
// exit.
func main() {
shell := os.Getenv("SHELL")
if shell == "" {
if runtime.GOOS == "windows" {
shell = "CMD.EXE"
} else {
shell = "/bin/sh"
}
}
s, e := tcell.NewScreen(tcell.OptAdvancedKeys(true))
if e != nil {
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}
if e := s.Init(); e != nil {
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}
s.SetTitle("Tcell Mouse Demonstration")
defStyle = tcell.StyleDefault.
Background(color.Reset).
Foreground(color.Reset)
s.SetStyle(defStyle)
s.EnableMouse()
s.EnablePaste()
s.EnableFocus()
s.Clear()
posfmt := "Mouse: %d, %d "
btnfmt := "Buttons: %s"
keyfmt := "Keys: %s"
pastefmt := "Paste: [%d] %s"
focusfmt := "Focus: %s"
termFmt := "Term: %s (%s)"
kbdFmt := "Keyboard: %s"
style := tcell.StyleDefault.
Foreground(color.MidnightBlue).Background(color.LightCoral)
keyDownStyle := style.Foreground(color.Green)
keyUpStyle := style
mouseMarkerStyle := tcell.StyleDefault.
Foreground(color.White).Background(color.Red)
mx, my := -1, -1
ox, oy := -1, -1
bx, by := -1, -1
w, h := s.Size()
lchar := '*'
bstr := ""
mouseButton := tcell.ButtonNone
var mouseMarker savedMouseMarker
lks := ""
lkey := ""
kcnt := 0
pstr := ""
ecnt := 0
pasting := false
focus := true // assume we are focused when we start
keyStyle := keyUpStyle
for {
drawBox(s, 1, 1, 42, 10, style, ' ')
s.PutStrStyled(2, 2, "Move/click/drag mouse. Ctrl-Q quits.", style)
s.PutStrStyled(2, 3, fmt.Sprintf(posfmt, mx, my), style)
s.PutStrStyled(2, 4, fmt.Sprintf(btnfmt, bstr), style)
s.PutStrStyled(2, 5, fmt.Sprintf(keyfmt, ""), style)
s.PutStrStyled(8, 5, lks, keyStyle)
ps := pstr
if len(ps) > 26 {
ps = "..." + ps[len(ps)-24:]
}
s.PutStrStyled(2, 6, fmt.Sprintf(pastefmt, len(pstr), ps), style)
fstr := "false"
if focus {
fstr = "true"
}
s.PutStrStyled(2, 7, fmt.Sprintf(focusfmt, fstr), style)
n, v := s.Terminal()
s.PutStrStyled(2, 8, fmt.Sprintf(termFmt, n, v), style)
s.PutStrStyled(2, 9, fmt.Sprintf(kbdFmt, keyboardProtocolName(s.KeyboardProtocol())), style)
if mx >= 0 && my >= 0 && mouseButton == tcell.ButtonNone {
drawMouseMarker(s, &mouseMarker, mx, my, mouseMarkerStyle)
}
s.Show()
restoreMouseMarker(s, &mouseMarker)
bstr = ""
ev := <-s.EventQ()
st := tcell.StyleDefault.Background(color.Red)
up := tcell.StyleDefault.
Background(color.Blue).
Foreground(color.Black)
w, h = s.Size()
// always clear any old selection box
if ox >= 0 && oy >= 0 && bx >= 0 {
drawSelect(s, ox, oy, bx, by, false)
}
switch ev := ev.(type) {
case *tcell.EventResize:
s.Sync()
s.Put(w-1, h-1, "R", st)
case *tcell.EventKey:
s.Put(w-2, h-2, ev.Str(), st)
if !ev.Pressed() {
pstr = ""
lks = ev.Name()
keyStyle = keyUpStyle
for x := range w - 1 {
s.Put(x, h-1, " ", tcell.StyleDefault)
}
s.Put(w-1, h-1, "U", st)
continue
}
keyStyle = keyDownStyle
if ev.Name() == lkey {
kcnt++
} else {
lkey = ev.Name()
kcnt = 1
}
if pasting {
s.Put(w-1, h-1, "P", st)
if ev.Key() == tcell.KeyRune {
pstr = pstr + ev.Str()
} else {
pstr = pstr + "\ufffd" // replacement for now
}
lks = ""
continue
}
pstr = ""
s.Put(w-1, h-1, "K", st)
if ev.Key() == tcell.KeyEscape {
ecnt++
if ecnt > 1 {
s.Fini()
os.Exit(0)
}
} else if ev.Key() == tcell.KeyCtrlL || isCtrlRune(ev, "l") {
s.Sync()
} else if ev.Key() == tcell.KeyCtrlQ || isCtrlRune(ev, "q") {
s.Fini()
os.Exit(0)
} else if ev.Key() == tcell.KeyCtrlZ || isCtrlRune(ev, "z") {
// CtrlZ doesn't really suspend the process, but we use it to execute a subshell.
if err := s.Suspend(); err == nil {
fmt.Printf("Executing shell (%s -l)...\n", shell)
fmt.Printf("Exit the shell to return to the demo.\n")
c := exec.Command(shell, "-l") // NB: -l works for cmd.exe too (ignored)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Run()
if err := s.Resume(); err != nil {
panic("failed to resume: " + err.Error())
}
}
} else {
ecnt = 0
if ev.Str() == "C" || ev.Str() == "c" {
s.Clear()
}
}
if ks := fmt.Sprintf("K%d", kcnt); w >= len(ks) {
s.PutStrStyled(w-len(ks), h-1, ks, st)
} else {
s.PutStrStyled(0, h-1, ks, st)
}
lks = ev.Name()
case *tcell.EventPaste:
pasting = ev.Start()
if pasting {
pstr = ""
}
case *tcell.EventMouse:
x, y := ev.Position()
button := ev.Buttons()
mods := ev.Modifiers()
if mods&tcell.ModShift != 0 {
bstr += " Shift"
}
if mods&tcell.ModCtrl != 0 {
bstr += " Ctrl"
}
if mods&tcell.ModAlt != 0 {
bstr += " Alt"
}
if mods&tcell.ModMeta != 0 {
bstr += " Meta"
}
for i := uint(0); i < 8; i++ {
if int(button)&(1<<i) != 0 {
bstr += fmt.Sprintf(" Button%d", i+1)
}
}
if button&tcell.WheelUp != 0 {
bstr += " WheelUp"
}
if button&tcell.WheelDown != 0 {
bstr += " WheelDown"
}
if button&tcell.WheelLeft != 0 {
bstr += " WheelLeft"
}
if button&tcell.WheelRight != 0 {
bstr += " WheelRight"
}
// Only buttons, not wheel events. We keep this separately so
// bare motion has a visible marker even when GPM or the terminal
// does not draw a mouse pointer of its own.
button &= tcell.ButtonMask(0xff)
mouseButton = button
ch := '*'
if button != tcell.ButtonNone && ox < 0 {
ox, oy = x, y
}
theme := []color.Color{
color.Gray,
color.Red,
color.Lime,
color.Yellow,
color.Fuchsia,
color.Blue,
color.Aqua,
color.Silver,
}
switch ev.Buttons() {
case tcell.ButtonNone:
if ox >= 0 {
bg := theme[lchar%8]
fg := color.Black
drawBox(s, ox, oy, x, y,
up.Background(bg).Foreground(fg),
lchar)
ox, oy = -1, -1
bx, by = -1, -1
}
case tcell.Button1:
ch = '1'
case tcell.Button2:
ch = '2'
case tcell.Button3:
ch = '3'
case tcell.Button4:
ch = '4'
case tcell.Button5:
ch = '5'
case tcell.Button6:
ch = '6'
case tcell.Button7:
ch = '7'
case tcell.Button8:
ch = '8'
default:
ch = '*'
}
if button != tcell.ButtonNone {
bx, by = x, y
}
lchar = ch
s.Put(w-1, h-1, "M", st)
mx, my = x, y
case *tcell.EventFocus:
focus = ev.Focused
default:
s.Put(w-1, h-1, "X", st)
}
if ox >= 0 && bx >= 0 {
drawSelect(s, ox, oy, bx, by, true)
}
}
}