// SPDX-License-Identifier: Unlicense OR MIT // Event system types ported from gioui.org/io/{event,key,pointer,clipboard,system,transfer,semantic}. // All sub-packages merged into package gio with prefixed names to avoid collisions. package gio import ( "bytes" "io" "time" ) // --------------------------------------------------------------------------- // event - core interfaces // --------------------------------------------------------------------------- // Tag is the stable identifier for an event handler. // For a handler h, the tag is typically &h. type Tag any // Event is the marker interface for events. type Event interface { ImplementsEvent() } // Filter represents a filter for Event types. type Filter interface { ImplementsFilter() } // InputCommand represents a request such as moving the focus or reading the clipboard. type InputCommand interface { ImplementsCommand() } // EventOp declares a tag for input routing at the current transformation // and clip area hierarchy. It panics if tag is nil. func EventOp(o *OpList, tag Tag) { if tag == nil { panic("Tag must be non-nil") } data := WriteOps1(&o.Internal, TypeInputLen, tag) data[0] = byte(TypeInput) } // --------------------------------------------------------------------------- // key - key events, names, modifiers // --------------------------------------------------------------------------- // KeyFilter matches any KeyEvent that matches the parameters. type KeyFilter struct { // Focus is the tag that must be focused for the filter to match. It has no // effect if it is nil. Focus Tag // Required is the set of modifiers that must be included in events matched. Required Modifiers // Optional is the set of modifiers that may be included in events matched. Optional Modifiers // Name of the key to be matched. As a special case, the empty Name matches // every key not matched by any other filter. Name KeyName } // KeyInputHintOp describes the type of text expected by a tag. type KeyInputHintOp struct { Tag Tag Hint InputHint } // SoftKeyboardCmd shows or hides the on-screen keyboard, if available. type SoftKeyboardCmd struct { Show bool } // SelectionCmd updates the selection for an input handler. type SelectionCmd struct { Tag Tag KeyRange Caret } // SnippetCmd updates the content snippet for an input handler. type SnippetCmd struct { Tag Tag Snippet } // KeyRange represents a range of text, such as an editor's selection. // Start and End are in runes. type KeyRange struct { Start int End int } // Snippet represents a snippet of text content used for communicating between // an editor and an input method. type Snippet struct { KeyRange Text string } // Caret represents the position of a caret. type Caret struct { // Pos is the intersection point of the caret and its baseline. Pos Point // Ascent is the length of the caret above its baseline. Ascent float32 // Descent is the length of the caret below its baseline. Descent float32 } // SelectionEvent is generated when an input method changes the selection. type SelectionEvent KeyRange // SnippetEvent is generated when the snippet range is updated by an input method. type SnippetEvent KeyRange // KeyFocusEvent is generated when a handler gains or loses focus. type KeyFocusEvent struct { Focus bool } // KeyEvent is generated when a key is pressed. For text input use KeyEditEvent. type KeyEvent struct { // Name of the key. Name KeyName // Modifiers is the set of active modifiers when the key was pressed. Modifiers Modifiers // State is the state of the key when the event was fired. State KeyState } // KeyEditEvent requests an edit by an input method. type KeyEditEvent struct { // Range specifies the range to replace with Text. Range KeyRange Text string } // KeyFocusFilter matches any KeyFocusEvent, KeyEditEvent, SnippetEvent, // or SelectionEvent with the specified target. type KeyFocusFilter struct { // Target is a tag specified in a previous EventOp. Target Tag } // InputHint changes the on-screen-keyboard type. That hints the type of data // that might be entered by the user. type InputHint uint8 const ( // HintAny hints that any input is expected. HintAny InputHint = iota // HintText hints that text input is expected. It may activate auto-correction and suggestions. HintText // HintNumeric hints that numeric input is expected. It may activate shortcuts for 0-9, "." and ",". HintNumeric // HintEmail hints that email input is expected. It may activate shortcuts for common email characters, such as "@" and ".com". HintEmail // HintURL hints that URL input is expected. It may activate shortcuts for common URL fragments such as "/" and ".com". HintURL // HintTelephone hints that telephone number input is expected. It may activate shortcuts for 0-9, "#" and "*". HintTelephone // HintPassword hints that password input is expected. It may disable autocorrection and enable password autofill. HintPassword ) // KeyState is the state of a key during an event. type KeyState uint8 const ( // KeyPress is the state of a pressed key. KeyPress KeyState = iota // KeyRelease is the state of a key that has been released. KeyRelease ) // Modifiers type Modifiers uint32 const ( // ModCtrl is the ctrl modifier key. ModCtrl Modifiers = 1 << iota // ModCommand is the command modifier key found on Apple keyboards. ModCommand // ModShift is the shift modifier key. ModShift // ModAlt is the alt modifier key, or the option key on Apple keyboards. ModAlt // ModSuper is the "logo" modifier key, often represented by a Windows logo. ModSuper ) // ModShortcut is the platform's shortcut modifier (WASM: always Ctrl). const ModShortcut = ModCtrl // ModShortcutAlt is the platform's alternative shortcut modifier (WASM: always Ctrl). const ModShortcutAlt = ModCtrl // KeyName is the identifier for a keyboard key. // // For letters, the upper case form is used, via unicode.ToUpper. // The shift modifier is taken into account, all other modifiers are ignored. // For example, the "shift-1" and "ctrl-shift-1" combinations both give the // Name "!" with the US keyboard layout. type KeyName string const ( KeyNameLeftArrow KeyName = "\xe2\x86\x90" KeyNameRightArrow KeyName = "\xe2\x86\x92" KeyNameUpArrow KeyName = "\xe2\x86\x91" KeyNameDownArrow KeyName = "\xe2\x86\x93" KeyNameReturn KeyName = "\xe2\x8f\x8e" KeyNameEnter KeyName = "\xe2\x8c\xa4" KeyNameEscape KeyName = "\xe2\x8e\x8b" KeyNameHome KeyName = "\xe2\x87\xb1" KeyNameEnd KeyName = "\xe2\x87\xb2" KeyNameDeleteBackward KeyName = "\xe2\x8c\xab" KeyNameDeleteForward KeyName = "\xe2\x8c\xa6" KeyNamePageUp KeyName = "\xe2\x87\x9e" KeyNamePageDown KeyName = "\xe2\x87\x9f" KeyNameTab KeyName = "Tab" KeyNameSpace KeyName = "Space" KeyNameCtrl KeyName = "Ctrl" KeyNameShift KeyName = "Shift" KeyNameAlt KeyName = "Alt" KeyNameSuper KeyName = "Super" KeyNameCommand KeyName = "\xe2\x8c\x98" KeyNameF1 KeyName = "F1" KeyNameF2 KeyName = "F2" KeyNameF3 KeyName = "F3" KeyNameF4 KeyName = "F4" KeyNameF5 KeyName = "F5" KeyNameF6 KeyName = "F6" KeyNameF7 KeyName = "F7" KeyNameF8 KeyName = "F8" KeyNameF9 KeyName = "F9" KeyNameF10 KeyName = "F10" KeyNameF11 KeyName = "F11" KeyNameF12 KeyName = "F12" KeyNameBack KeyName = "Back" ) type FocusDirection int const ( FocusRight FocusDirection = iota FocusLeft FocusUp FocusDown FocusForward FocusBackward ) // Contain reports whether m contains all modifiers in m2. func (m Modifiers) Contain(m2 Modifiers) bool { return m&m2 == m2 } // FocusCmd requests to set or clear the keyboard focus. type FocusCmd struct { // Tag is the new focus. The focus is cleared if Tag is nil, or if Tag // has no EventOp references. Tag Tag } func (h KeyInputHintOp) Add(o *OpList) { if h.Tag == nil { panic("Tag must be non-nil") } data := WriteOps1(&o.Internal, TypeKeyInputHintLen, h.Tag) data[0] = byte(TypeKeyInputHint) data[1] = byte(h.Hint) } func (KeyEditEvent) ImplementsEvent() {} func (KeyEvent) ImplementsEvent() {} func (KeyFocusEvent) ImplementsEvent() {} func (SnippetEvent) ImplementsEvent() {} func (SelectionEvent) ImplementsEvent() {} func (FocusCmd) ImplementsCommand() {} func (SoftKeyboardCmd) ImplementsCommand() {} func (SelectionCmd) ImplementsCommand() {} func (SnippetCmd) ImplementsCommand() {} func (KeyFilter) ImplementsFilter() {} func (KeyFocusFilter) ImplementsFilter() {} func (m Modifiers) String() string { var parts []string if m.Contain(ModCtrl) { parts = append(parts, string(KeyNameCtrl)) } if m.Contain(ModCommand) { parts = append(parts, string(KeyNameCommand)) } if m.Contain(ModShift) { parts = append(parts, string(KeyNameShift)) } if m.Contain(ModAlt) { parts = append(parts, string(KeyNameAlt)) } if m.Contain(ModSuper) { parts = append(parts, string(KeyNameSuper)) } return bytes.Join(parts, "-") } func (s KeyState) String() string { switch s { case KeyPress: return "Press" case KeyRelease: return "Release" default: panic("invalid KeyState") } } // --------------------------------------------------------------------------- // pointer - pointer events, cursor types // --------------------------------------------------------------------------- // PointerEvent is a pointer event. type PointerEvent struct { Kind PointerKind Source PointerSource // PointerID is the id for the pointer and can be used to track a particular // pointer from Press to Release. PointerID PointerID // Priority is the priority of the receiving handler for this event. Priority PointerPriority // Time is when the event was received. The timestamp is relative to an // undefined base. Time time.Duration // Buttons are the set of pressed mouse buttons for this event. Buttons PointerButtons // Position is the coordinates of the event in the local coordinate system // of the receiving tag. Position Point // Scroll is the scroll amount, if any. Scroll Point // Modifiers is the set of active modifiers when the mouse button was pressed. Modifiers Modifiers } // PassOp sets the pass-through mode. InputOps added while the pass-through // mode is set don't block events to siblings. type PassOp struct{} // PassStack represents a PassOp on the pass stack. type PassStack struct { ops *Ops id StackID macroID uint32 } // PointerFilter matches every PointerEvent that targets the Tag and whose kind // is included in Kinds. type PointerFilter struct { Target Tag // Kinds is a bitwise-or of event types to match. Kinds PointerKind // ScrollX and ScrollY constrain the range of scrolling events delivered // to Target. ScrollX ScrollRange ScrollY ScrollRange } // ScrollRange describes the range of scrolling distances in an axis. type ScrollRange struct { Min, Max int } // GrabCmd requests a pointer grab on the pointer identified by ID. type GrabCmd struct { Tag Tag ID PointerID } type PointerID uint16 // PointerKind of a PointerEvent. type PointerKind uint // PointerPriority of a PointerEvent. type PointerPriority uint8 // PointerSource of a PointerEvent. type PointerSource uint8 // PointerButtons is a set of mouse buttons. type PointerButtons uint8 // Cursor denotes a pre-defined cursor shape. Its Add method adds an operation // that sets the cursor shape for the current clip area. type Cursor byte // The cursors correspond to CSS pointer naming. const ( CursorDefault Cursor = iota CursorNone CursorText CursorVerticalText CursorPointer CursorCrosshair CursorAllScroll CursorColResize CursorRowResize CursorGrab CursorGrabbing CursorNotAllowed CursorWait CursorProgress CursorNorthWestResize CursorNorthEastResize CursorSouthWestResize CursorSouthEastResize CursorNorthSouthResize CursorEastWestResize CursorWestResize CursorEastResize CursorNorthResize CursorSouthResize CursorNorthEastSouthWestResize CursorNorthWestSouthEastResize ) const ( // Cancel is generated when the current gesture is interrupted. PointerCancel PointerKind = 1 << iota // PointerPress of a pointer. PointerPress // PointerRelease of a pointer. PointerRelease // PointerMove of a pointer. PointerMove // PointerDrag of a pointer. PointerDrag // PointerEnter - pointer enters an area watching for pointer input. PointerEnter // PointerLeave - pointer leaves an area watching for pointer input. PointerLeave // PointerScroll of a pointer. PointerScroll ) const ( // Mouse generated event. Mouse PointerSource = iota // Touch generated event. Touch ) const ( // Shared priority is for handlers that are part of a matching set larger than 1. Shared PointerPriority = iota // Grabbed is used for matching sets of size 1. Grabbed ) const ( // ButtonPrimary is the primary button, usually the left button for a // right-handed user. ButtonPrimary PointerButtons = 1 << iota // ButtonSecondary is the secondary button, usually the right button for a // right-handed user. ButtonSecondary // ButtonTertiary is the tertiary button, usually the middle button. ButtonTertiary // ButtonQuaternary is the fourth button, usually used for browser // navigation (backward). ButtonQuaternary // ButtonQuinary is the fifth button, usually used for browser // navigation (forward). ButtonQuinary ) func (s ScrollRange) Union(s2 ScrollRange) ScrollRange { return ScrollRange{ Min: min(s.Min, s2.Min), Max: max(s.Max, s2.Max), } } // Push the current pass mode to the pass stack and set the pass mode. func (p PassOp) Push(o *OpList) PassStack { id, mid := PushOp(&o.Internal, PassStackKind) data := WriteOps(&o.Internal, TypePassLen) data[0] = byte(TypePass) return PassStack{ops: &o.Internal, id: id, macroID: mid} } func (p PassStack) Pop() { PopOp(p.ops, PassStackKind, p.id, p.macroID) data := WriteOps(p.ops, TypePopPassLen) data[0] = byte(TypePopPass) } func (c Cursor) Add(o *OpList) { data := WriteOps(&o.Internal, TypeCursorLen) data[0] = byte(TypeCursor) data[1] = byte(c) } func (t PointerKind) String() string { if t == PointerCancel { return "Cancel" } var buf []byte for tt := PointerKind(1); tt > 0; tt <<= 1 { if t&tt > 0 { if len(buf) > 0 { buf = append(buf, '|') } buf = append(buf, (t & tt).string()...) } } return buf } func (t PointerKind) string() string { switch t { case PointerPress: return "Press" case PointerRelease: return "Release" case PointerCancel: return "Cancel" case PointerMove: return "Move" case PointerDrag: return "Drag" case PointerEnter: return "Enter" case PointerLeave: return "Leave" case PointerScroll: return "Scroll" default: panic("unknown PointerKind") } } func (p PointerPriority) String() string { switch p { case Shared: return "Shared" case Grabbed: return "Grabbed" default: panic("unknown priority") } } func (s PointerSource) String() string { switch s { case Mouse: return "Mouse" case Touch: return "Touch" default: panic("unknown source") } } // Contain reports whether the set b contains all of the buttons. func (b PointerButtons) Contain(buttons PointerButtons) bool { return b&buttons == buttons } func (b PointerButtons) String() string { var parts []string if b.Contain(ButtonPrimary) { parts = append(parts, "ButtonPrimary") } if b.Contain(ButtonSecondary) { parts = append(parts, "ButtonSecondary") } if b.Contain(ButtonTertiary) { parts = append(parts, "ButtonTertiary") } if b.Contain(ButtonQuaternary) { parts = append(parts, "ButtonQuaternary") } if b.Contain(ButtonQuinary) { parts = append(parts, "ButtonQuinary") } return bytes.Join(parts, "|") } func (c Cursor) String() string { switch c { case CursorDefault: return "Default" case CursorNone: return "None" case CursorText: return "Text" case CursorVerticalText: return "VerticalText" case CursorPointer: return "Pointer" case CursorCrosshair: return "Crosshair" case CursorAllScroll: return "AllScroll" case CursorColResize: return "ColResize" case CursorRowResize: return "RowResize" case CursorGrab: return "Grab" case CursorGrabbing: return "Grabbing" case CursorNotAllowed: return "NotAllowed" case CursorWait: return "Wait" case CursorProgress: return "Progress" case CursorNorthWestResize: return "NorthWestResize" case CursorNorthEastResize: return "NorthEastResize" case CursorSouthWestResize: return "SouthWestResize" case CursorSouthEastResize: return "SouthEastResize" case CursorNorthSouthResize: return "NorthSouthResize" case CursorEastWestResize: return "EastWestResize" case CursorWestResize: return "WestResize" case CursorEastResize: return "EastResize" case CursorNorthResize: return "NorthResize" case CursorSouthResize: return "SouthResize" case CursorNorthEastSouthWestResize: return "NorthEastSouthWestResize" case CursorNorthWestSouthEastResize: return "NorthWestSouthEastResize" default: panic("unknown Cursor") } } func (PointerEvent) ImplementsEvent() {} func (GrabCmd) ImplementsCommand() {} func (PointerFilter) ImplementsFilter() {} // --------------------------------------------------------------------------- // clipboard - clipboard events // --------------------------------------------------------------------------- // ClipboardWriteCmd copies data to the clipboard. type ClipboardWriteCmd struct { Type string Data io.ReadCloser } // ClipboardReadCmd requests clipboard contents, delivered to the handler // through a TransferDataEvent. type ClipboardReadCmd struct { Tag Tag } func (ClipboardWriteCmd) ImplementsCommand() {} func (ClipboardReadCmd) ImplementsCommand() {} // --------------------------------------------------------------------------- // system - system actions, decoration, locale // --------------------------------------------------------------------------- // ActionInputOp makes the current clip area available for system gestures. type ActionInputOp SystemAction // SystemAction is a set of window decoration actions. type SystemAction uint const ( ActionMinimize SystemAction = 1 << iota ActionMaximize ActionUnmaximize ActionFullscreen ActionRaise ActionCenter ActionClose ActionMove ) func (a ActionInputOp) Add(o *OpList) { data := WriteOps(&o.Internal, TypeActionInputLen) data[0] = byte(TypeActionInput) data[1] = byte(a) } func (a SystemAction) String() string { var buf []byte for b := SystemAction(1); a != 0; b <<= 1 { if a&b != 0 { if len(buf) > 0 { buf = append(buf, '|') } buf = append(buf, b.string()...) a &^= b } } return buf } func (a SystemAction) string() string { switch a { case ActionMinimize: return "ActionMinimize" case ActionMaximize: return "ActionMaximize" case ActionUnmaximize: return "ActionUnmaximize" case ActionClose: return "ActionClose" case ActionMove: return "ActionMove" case ActionFullscreen: return "ActionFullscreen" case ActionRaise: return "ActionRaise" case ActionCenter: return "ActionCenter" } return "" } // Locale provides language information for the current system. type Locale struct { // Language is the BCP-47 tag for the primary language of the system. Language string // Direction indicates the primary direction of text and layout flow. Direction TextDirection } const ( axisShift = iota progressionShift ) // TextDirection defines a direction for text flow. type TextDirection byte const ( // LTR is left-to-right text. LTR TextDirection = TextDirection(Horizontal<> axisShift) } // Progression returns the way that the text flows relative to the origin. func (d TextDirection) Progression() TextProgression { return TextProgression((d & (1 << progressionShift)) >> progressionShift) } func (d TextDirection) String() string { switch d { case RTL: return "RTL" default: return "LTR" } } // TextAxis defines the layout axis of text. type TextAxis byte const ( // Horizontal indicates text that flows along the X axis. Horizontal TextAxis = iota // Vertical indicates text that flows along the Y axis. Vertical ) // TextProgression indicates how text flows along an axis relative to the // origin. The origin is the upper-left corner of coordinate space. type TextProgression byte const ( // FromOrigin indicates text that flows along its axis away from the origin. FromOrigin TextProgression = iota // TowardOrigin indicates text that flows along its axis towards the origin. TowardOrigin ) // --------------------------------------------------------------------------- // transfer - data transfer operations // --------------------------------------------------------------------------- // TransferOfferCmd is used by data sources as a response to a TransferRequestEvent. type TransferOfferCmd struct { Tag Tag Type string Data io.ReadCloser } func (TransferOfferCmd) ImplementsCommand() {} // TransferSourceFilter filters for any TransferRequestEvent that match a MIME // type as well as TransferInitiateEvent and TransferCancelEvent. type TransferSourceFilter struct { Target Tag Type string } // TransferTargetFilter filters for any TransferDataEvent whose type matches a // MIME type as well as TransferCancelEvent. type TransferTargetFilter struct { Target Tag Type string } // TransferRequestEvent requests data from a data source. The source must // respond with a TransferOfferCmd. type TransferRequestEvent struct { Type string } func (TransferRequestEvent) ImplementsEvent() {} // TransferInitiateEvent is sent to a data source when a drag-and-drop // transfer gesture is initiated. Potential data targets also receive the event. type TransferInitiateEvent struct{} func (TransferInitiateEvent) ImplementsEvent() {} // TransferCancelEvent is sent to data sources and targets to cancel the // effects of a TransferInitiateEvent. type TransferCancelEvent struct{} func (TransferCancelEvent) ImplementsEvent() {} // TransferDataEvent is sent to the target receiving the transfer. type TransferDataEvent struct { Type string // Open returns the transfer data. It is only valid to call Open in the // frame the TransferDataEvent is received. The caller must close the return // value after use. Open func() io.ReadCloser } func (TransferDataEvent) ImplementsEvent() {} func (TransferSourceFilter) ImplementsFilter() {} func (TransferTargetFilter) ImplementsFilter() {} // --------------------------------------------------------------------------- // semantic - accessibility data // --------------------------------------------------------------------------- // SemanticLabelOp provides the content of a textual component. type SemanticLabelOp string // SemanticDescriptionOp describes a component. type SemanticDescriptionOp string // SemanticClassOp provides the component class. type SemanticClassOp int const ( SemanticUnknown SemanticClassOp = iota SemanticButton SemanticCheckBox SemanticEditor SemanticRadioButton SemanticSwitch ) // SemanticSelectedOp describes the selected state for components that have // boolean state. type SemanticSelectedOp bool // SemanticEnabledOp describes the enabled state. type SemanticEnabledOp bool func (l SemanticLabelOp) Add(o *OpList) { data := WriteOps1String(&o.Internal, TypeSemanticLabelLen, string(l)) data[0] = byte(TypeSemanticLabel) } func (d SemanticDescriptionOp) Add(o *OpList) { data := WriteOps1String(&o.Internal, TypeSemanticDescLen, string(d)) data[0] = byte(TypeSemanticDesc) } func (c SemanticClassOp) Add(o *OpList) { data := WriteOps(&o.Internal, TypeSemanticClassLen) data[0] = byte(TypeSemanticClass) data[1] = byte(c) } func (s SemanticSelectedOp) Add(o *OpList) { data := WriteOps(&o.Internal, TypeSemanticSelectedLen) data[0] = byte(TypeSemanticSelected) if s { data[1] = 1 } } func (e SemanticEnabledOp) Add(o *OpList) { data := WriteOps(&o.Internal, TypeSemanticEnabledLen) data[0] = byte(TypeSemanticEnabled) if e { data[1] = 1 } } func (c SemanticClassOp) String() string { switch c { case SemanticUnknown: return "Unknown" case SemanticButton: return "Button" case SemanticCheckBox: return "CheckBox" case SemanticEditor: return "Editor" case SemanticRadioButton: return "RadioButton" case SemanticSwitch: return "Switch" default: panic("invalid SemanticClassOp") } }