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 }