github.com/Seikaijyu/gio@v0.0.1/io/key/key.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  /*
     4  Package key implements key and text events and operations.
     5  
     6  The InputOp operations is used for declaring key input handlers. Use
     7  an implementation of the Queue interface from package ui to receive
     8  events.
     9  */
    10  package key
    11  
    12  import (
    13  	"encoding/binary"
    14  	"fmt"
    15  	"math"
    16  	"strings"
    17  
    18  	"github.com/Seikaijyu/gio/f32"
    19  	"github.com/Seikaijyu/gio/internal/ops"
    20  	"github.com/Seikaijyu/gio/io/event"
    21  	"github.com/Seikaijyu/gio/op"
    22  )
    23  
    24  // InputOp declares a handler ready for key events.
    25  // Key events are in general only delivered to the
    26  // focused key handler.
    27  type InputOp struct {
    28  	Tag event.Tag
    29  	// Hint describes the type of text expected by Tag.
    30  	Hint InputHint
    31  	// Keys is the set of keys Tag can handle. That is, Tag will only
    32  	// receive an Event if its key and modifiers are accepted by Keys.Contains.
    33  	// As a special case, the topmost (first added) InputOp handler receives all
    34  	// unhandled events.
    35  	Keys Set
    36  }
    37  
    38  // Set is an expression that describes a set of key combinations, in the form
    39  // "<modifiers>-<keyset>|...".  Modifiers are separated by dashes, optional
    40  // modifiers are enclosed by parentheses.  A key set is either a literal key
    41  // name or a list of key names separated by commas and enclosed in brackets.
    42  //
    43  // The "Short" modifier matches the shortcut modifier (ModShortcut) and
    44  // "ShortAlt" matches the alternative modifier (ModShortcutAlt).
    45  //
    46  // Examples:
    47  //
    48  //   - A|B matches the A and B keys
    49  //   - [A,B] also matches the A and B keys
    50  //   - Shift-A matches A key if shift is pressed, and no other modifier.
    51  //   - Shift-(Ctrl)-A matches A if shift is pressed, and optionally ctrl.
    52  type Set string
    53  
    54  // SoftKeyboardOp shows or hide the on-screen keyboard, if available.
    55  // It replaces any previous SoftKeyboardOp.
    56  type SoftKeyboardOp struct {
    57  	Show bool
    58  }
    59  
    60  // FocusOp sets or clears the keyboard focus. It replaces any previous
    61  // FocusOp in the same frame.
    62  type FocusOp struct {
    63  	// Tag is the new focus. The focus is cleared if Tag is nil, or if Tag
    64  	// has no InputOp in the same frame.
    65  	Tag event.Tag
    66  }
    67  
    68  // SelectionOp updates the selection for an input handler.
    69  type SelectionOp struct {
    70  	Tag event.Tag
    71  	Range
    72  	Caret
    73  }
    74  
    75  // SnippetOp updates the content snippet for an input handler.
    76  type SnippetOp struct {
    77  	Tag event.Tag
    78  	Snippet
    79  }
    80  
    81  // Range represents a range of text, such as an editor's selection.
    82  // Start and End are in runes.
    83  type Range struct {
    84  	Start int
    85  	End   int
    86  }
    87  
    88  // Snippet represents a snippet of text content used for communicating between
    89  // an editor and an input method.
    90  type Snippet struct {
    91  	Range
    92  	Text string
    93  }
    94  
    95  // Caret represents the position of a caret.
    96  type Caret struct {
    97  	// Pos is the intersection point of the caret and its baseline.
    98  	Pos f32.Point
    99  	// Ascent is the length of the caret above its baseline.
   100  	Ascent float32
   101  	// Descent is the length of the caret below its baseline.
   102  	Descent float32
   103  }
   104  
   105  // SelectionEvent is generated when an input method changes the selection.
   106  type SelectionEvent Range
   107  
   108  // SnippetEvent is generated when the snippet range is updated by an
   109  // input method.
   110  type SnippetEvent Range
   111  
   112  // A FocusEvent is generated when a handler gains or loses
   113  // focus.
   114  type FocusEvent struct {
   115  	Focus bool
   116  }
   117  
   118  // An Event is generated when a key is pressed. For text input
   119  // use EditEvent.
   120  type Event struct {
   121  	// Name of the key. For letters, the upper case form is used, via
   122  	// unicode.ToUpper. The shift modifier is taken into account, all other
   123  	// modifiers are ignored. For example, the "shift-1" and "ctrl-shift-1"
   124  	// combinations both give the Name "!" with the US keyboard layout.
   125  	Name string
   126  	// Modifiers is the set of active modifiers when the key was pressed.
   127  	Modifiers Modifiers
   128  	// State is the state of the key when the event was fired.
   129  	State State
   130  }
   131  
   132  // An EditEvent requests an edit by an input method.
   133  type EditEvent struct {
   134  	// Range specifies the range to replace with Text.
   135  	Range Range
   136  	Text  string
   137  }
   138  
   139  // InputHint changes the on-screen-keyboard type. That hints the
   140  // type of data that might be entered by the user.
   141  type InputHint uint8
   142  
   143  const (
   144  	// HintAny hints that any input is expected.
   145  	HintAny InputHint = iota
   146  	// HintText hints that text input is expected. It may activate auto-correction and suggestions.
   147  	HintText
   148  	// HintNumeric hints that numeric input is expected. It may activate shortcuts for 0-9, "." and ",".
   149  	HintNumeric
   150  	// HintEmail hints that email input is expected. It may activate shortcuts for common email characters, such as "@" and ".com".
   151  	HintEmail
   152  	// HintURL hints that URL input is expected. It may activate shortcuts for common URL fragments such as "/" and ".com".
   153  	HintURL
   154  	// HintTelephone hints that telephone number input is expected. It may activate shortcuts for 0-9, "#" and "*".
   155  	HintTelephone
   156  	// HintPassword hints that password input is expected. It may disable autocorrection and enable password autofill.
   157  	HintPassword
   158  )
   159  
   160  // State is the state of a key during an event.
   161  type State uint8
   162  
   163  const (
   164  	// Press is the state of a pressed key.
   165  	Press State = iota
   166  	// Release is the state of a key that has been released.
   167  	//
   168  	// Note: release events are only implemented on the following platforms:
   169  	// macOS, Linux, Windows, WebAssembly.
   170  	Release
   171  )
   172  
   173  // Modifiers
   174  type Modifiers uint32
   175  
   176  const (
   177  	// ModCtrl is the ctrl modifier key.
   178  	ModCtrl Modifiers = 1 << iota
   179  	// ModCommand is the command modifier key
   180  	// found on Apple keyboards.
   181  	ModCommand
   182  	// ModShift is the shift modifier key.
   183  	ModShift
   184  	// ModAlt is the alt modifier key, or the option
   185  	// key on Apple keyboards.
   186  	ModAlt
   187  	// ModSuper is the "logo" modifier key, often
   188  	// represented by a Windows logo.
   189  	ModSuper
   190  )
   191  
   192  const (
   193  	// Names for special keys.
   194  	NameLeftArrow      = "←"
   195  	NameRightArrow     = "→"
   196  	NameUpArrow        = "↑"
   197  	NameDownArrow      = "↓"
   198  	NameReturn         = "⏎"
   199  	NameEnter          = "⌤"
   200  	NameEscape         = "⎋"
   201  	NameHome           = "⇱"
   202  	NameEnd            = "⇲"
   203  	NameDeleteBackward = "⌫"
   204  	NameDeleteForward  = "⌦"
   205  	NamePageUp         = "⇞"
   206  	NamePageDown       = "⇟"
   207  	NameTab            = "Tab"
   208  	NameSpace          = "Space"
   209  	NameCtrl           = "Ctrl"
   210  	NameShift          = "Shift"
   211  	NameAlt            = "Alt"
   212  	NameSuper          = "Super"
   213  	NameCommand        = "⌘"
   214  	NameF1             = "F1"
   215  	NameF2             = "F2"
   216  	NameF3             = "F3"
   217  	NameF4             = "F4"
   218  	NameF5             = "F5"
   219  	NameF6             = "F6"
   220  	NameF7             = "F7"
   221  	NameF8             = "F8"
   222  	NameF9             = "F9"
   223  	NameF10            = "F10"
   224  	NameF11            = "F11"
   225  	NameF12            = "F12"
   226  	NameBack           = "Back"
   227  )
   228  
   229  // Contain reports whether m contains all modifiers
   230  // in m2.
   231  func (m Modifiers) Contain(m2 Modifiers) bool {
   232  	return m&m2 == m2
   233  }
   234  
   235  func (k Set) Contains(name string, mods Modifiers) bool {
   236  	ks := string(k)
   237  	for len(ks) > 0 {
   238  		// Cut next key expression.
   239  		chord, rest, _ := cut(ks, "|")
   240  		ks = rest
   241  		// Separate key set and modifier set.
   242  		var modSet, keySet string
   243  		sep := strings.LastIndex(chord, "-")
   244  		if sep != -1 {
   245  			modSet, keySet = chord[:sep], chord[sep+1:]
   246  		} else {
   247  			modSet, keySet = "", chord
   248  		}
   249  		if !keySetContains(keySet, name) {
   250  			continue
   251  		}
   252  		if modSetContains(modSet, mods) {
   253  			return true
   254  		}
   255  	}
   256  	return false
   257  }
   258  
   259  func keySetContains(keySet, name string) bool {
   260  	// Check for single key match.
   261  	if keySet == name {
   262  		return true
   263  	}
   264  	// Check for set match.
   265  	if len(keySet) < 2 || keySet[0] != '[' || keySet[len(keySet)-1] != ']' {
   266  		return false
   267  	}
   268  	keySet = keySet[1 : len(keySet)-1]
   269  	for len(keySet) > 0 {
   270  		key, rest, _ := cut(keySet, ",")
   271  		keySet = rest
   272  		if key == name {
   273  			return true
   274  		}
   275  	}
   276  	return false
   277  }
   278  
   279  func modSetContains(modSet string, mods Modifiers) bool {
   280  	var smods Modifiers
   281  	for len(modSet) > 0 {
   282  		mod, rest, _ := cut(modSet, "-")
   283  		modSet = rest
   284  		if len(mod) >= 2 && mod[0] == '(' && mod[len(mod)-1] == ')' {
   285  			mods &^= modFor(mod[1 : len(mod)-1])
   286  		} else {
   287  			smods |= modFor(mod)
   288  		}
   289  	}
   290  	return mods == smods
   291  }
   292  
   293  // cut is a copy of the standard library strings.Cut.
   294  // TODO: remove when Go 1.18 is our minimum.
   295  func cut(s, sep string) (before, after string, found bool) {
   296  	if i := strings.Index(s, sep); i >= 0 {
   297  		return s[:i], s[i+len(sep):], true
   298  	}
   299  	return s, "", false
   300  }
   301  
   302  func modFor(name string) Modifiers {
   303  	switch name {
   304  	case NameCtrl:
   305  		return ModCtrl
   306  	case NameShift:
   307  		return ModShift
   308  	case NameAlt:
   309  		return ModAlt
   310  	case NameSuper:
   311  		return ModSuper
   312  	case NameCommand:
   313  		return ModCommand
   314  	case "Short":
   315  		return ModShortcut
   316  	case "ShortAlt":
   317  		return ModShortcutAlt
   318  	}
   319  	return 0
   320  }
   321  
   322  func (h InputOp) Add(o *op.Ops) {
   323  	if h.Tag == nil {
   324  		panic("Tag must be non-nil")
   325  	}
   326  	data := ops.Write2String(&o.Internal, ops.TypeKeyInputLen, h.Tag, string(h.Keys))
   327  	data[0] = byte(ops.TypeKeyInput)
   328  	data[1] = byte(h.Hint)
   329  }
   330  
   331  func (h SoftKeyboardOp) Add(o *op.Ops) {
   332  	data := ops.Write(&o.Internal, ops.TypeKeySoftKeyboardLen)
   333  	data[0] = byte(ops.TypeKeySoftKeyboard)
   334  	if h.Show {
   335  		data[1] = 1
   336  	}
   337  }
   338  
   339  func (h FocusOp) Add(o *op.Ops) {
   340  	data := ops.Write1(&o.Internal, ops.TypeKeyFocusLen, h.Tag)
   341  	data[0] = byte(ops.TypeKeyFocus)
   342  }
   343  
   344  func (s SnippetOp) Add(o *op.Ops) {
   345  	data := ops.Write2String(&o.Internal, ops.TypeSnippetLen, s.Tag, s.Text)
   346  	data[0] = byte(ops.TypeSnippet)
   347  	bo := binary.LittleEndian
   348  	bo.PutUint32(data[1:], uint32(s.Range.Start))
   349  	bo.PutUint32(data[5:], uint32(s.Range.End))
   350  }
   351  
   352  func (s SelectionOp) Add(o *op.Ops) {
   353  	data := ops.Write1(&o.Internal, ops.TypeSelectionLen, s.Tag)
   354  	data[0] = byte(ops.TypeSelection)
   355  	bo := binary.LittleEndian
   356  	bo.PutUint32(data[1:], uint32(s.Start))
   357  	bo.PutUint32(data[5:], uint32(s.End))
   358  	bo.PutUint32(data[9:], math.Float32bits(s.Pos.X))
   359  	bo.PutUint32(data[13:], math.Float32bits(s.Pos.Y))
   360  	bo.PutUint32(data[17:], math.Float32bits(s.Ascent))
   361  	bo.PutUint32(data[21:], math.Float32bits(s.Descent))
   362  }
   363  
   364  func (EditEvent) ImplementsEvent()      {}
   365  func (Event) ImplementsEvent()          {}
   366  func (FocusEvent) ImplementsEvent()     {}
   367  func (SnippetEvent) ImplementsEvent()   {}
   368  func (SelectionEvent) ImplementsEvent() {}
   369  
   370  func (e Event) String() string {
   371  	return fmt.Sprintf("%v %v %v}", e.Name, e.Modifiers, e.State)
   372  }
   373  
   374  func (m Modifiers) String() string {
   375  	var strs []string
   376  	if m.Contain(ModCtrl) {
   377  		strs = append(strs, NameCtrl)
   378  	}
   379  	if m.Contain(ModCommand) {
   380  		strs = append(strs, NameCommand)
   381  	}
   382  	if m.Contain(ModShift) {
   383  		strs = append(strs, NameShift)
   384  	}
   385  	if m.Contain(ModAlt) {
   386  		strs = append(strs, NameAlt)
   387  	}
   388  	if m.Contain(ModSuper) {
   389  		strs = append(strs, NameSuper)
   390  	}
   391  	return strings.Join(strs, "-")
   392  }
   393  
   394  func (s State) String() string {
   395  	switch s {
   396  	case Press:
   397  		return "Press"
   398  	case Release:
   399  		return "Release"
   400  	default:
   401  		panic("invalid State")
   402  	}
   403  }