@ 261030.1
current
//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)
		}
	}
}