Documentation ¶
Index ¶
- Variables
- type Action
- type DeviceKind
- type EventInfo
- type Handler
- func (h *Handler) ActionIsJustPressed(action Action) bool
- func (h *Handler) ActionIsJustReleased(action Action) bool
- func (h *Handler) ActionIsPressed(action Action) bool
- func (h *Handler) ActionKeyNames(action Action, mask DeviceKind) []string
- func (h *Handler) CursorPos() Vec
- func (h *Handler) DefaultInputMask() DeviceKind
- func (h *Handler) EmitEvent(e SimulatedAction)
- func (h *Handler) EmitKeyEvent(e SimulatedKeyEvent)
- func (h *Handler) GamepadConnected() bool
- func (h *Handler) JustPressedActionInfo(action Action) (EventInfo, bool)
- func (h *Handler) JustReleasedActionInfo(action Action) (EventInfo, bool)
- func (h *Handler) PressedActionInfo(action Action) (EventInfo, bool)
- func (h *Handler) Remap(keymap Keymap)
- func (h *Handler) TouchEventsEnabled() bool
- type Key
- type KeyModifier
- type KeyScanStatus
- type KeyScanner
- type Keymap
- type SimulatedAction
- type SimulatedKeyEvent
- type System
- type SystemConfig
- type Vec
Constants ¶
This section is empty.
Variables ¶
var ( KeyWheelUp = Key{/* contains filtered or unexported fields */} KeyWheelDown = Key{/* contains filtered or unexported fields */} KeyWheelVertical = Key{/* contains filtered or unexported fields */} )
Wheel keys.
var ( KeyMouseLeft = Key{/* contains filtered or unexported fields */} KeyMouseRight = Key{/* contains filtered or unexported fields */} KeyMouseMiddle = Key{/* contains filtered or unexported fields */} KeyMouseBack = Key{/* contains filtered or unexported fields */} KeyMouseForward = Key{/* contains filtered or unexported fields */} )
Mouse keys.
var ( KeyTouchTap = Key{/* contains filtered or unexported fields */} // Like a tap, but user was holding that gesture for at least 0.5s. KeyTouchLongTap = Key{/* contains filtered or unexported fields */} KeyTouchDrag = Key{/* contains filtered or unexported fields */} )
Touch keys. Experimental: touch keys API is not stable yet!
var ( KeyLeft = Key{/* contains filtered or unexported fields */} KeyRight = Key{/* contains filtered or unexported fields */} KeyUp = Key{/* contains filtered or unexported fields */} KeyDown = Key{/* contains filtered or unexported fields */} Key0 = Key{/* contains filtered or unexported fields */} Key1 = Key{/* contains filtered or unexported fields */} Key2 = Key{/* contains filtered or unexported fields */} Key3 = Key{/* contains filtered or unexported fields */} Key4 = Key{/* contains filtered or unexported fields */} Key5 = Key{/* contains filtered or unexported fields */} Key6 = Key{/* contains filtered or unexported fields */} Key7 = Key{/* contains filtered or unexported fields */} Key8 = Key{/* contains filtered or unexported fields */} Key9 = Key{/* contains filtered or unexported fields */} KeyMinus = Key{/* contains filtered or unexported fields */} KeyEqual = Key{/* contains filtered or unexported fields */} KeyQuote = Key{/* contains filtered or unexported fields */} KeyBackquote = Key{/* contains filtered or unexported fields */} KeyQ = Key{/* contains filtered or unexported fields */} KeyW = Key{/* contains filtered or unexported fields */} KeyE = Key{/* contains filtered or unexported fields */} KeyR = Key{/* contains filtered or unexported fields */} KeyT = Key{/* contains filtered or unexported fields */} KeyY = Key{/* contains filtered or unexported fields */} KeyU = Key{/* contains filtered or unexported fields */} KeyI = Key{/* contains filtered or unexported fields */} KeyO = Key{/* contains filtered or unexported fields */} KeyP = Key{/* contains filtered or unexported fields */} KeyA = Key{/* contains filtered or unexported fields */} KeyS = Key{/* contains filtered or unexported fields */} KeyD = Key{/* contains filtered or unexported fields */} KeyF = Key{/* contains filtered or unexported fields */} KeyG = Key{/* contains filtered or unexported fields */} KeyH = Key{/* contains filtered or unexported fields */} KeyJ = Key{/* contains filtered or unexported fields */} KeyK = Key{/* contains filtered or unexported fields */} KeyL = Key{/* contains filtered or unexported fields */} KeyZ = Key{/* contains filtered or unexported fields */} KeyX = Key{/* contains filtered or unexported fields */} KeyC = Key{/* contains filtered or unexported fields */} KeyV = Key{/* contains filtered or unexported fields */} KeyB = Key{/* contains filtered or unexported fields */} KeyN = Key{/* contains filtered or unexported fields */} KeyM = Key{/* contains filtered or unexported fields */} KeyBackslash = Key{/* contains filtered or unexported fields */} KeyBackspace = Key{/* contains filtered or unexported fields */} KeyBracketLeft = Key{/* contains filtered or unexported fields */} KeyBracketRight = Key{/* contains filtered or unexported fields */} KeyCapsLock = Key{/* contains filtered or unexported fields */} KeyComma = Key{/* contains filtered or unexported fields */} KeyContextMenu = Key{/* contains filtered or unexported fields */} KeyAlt = Key{/* contains filtered or unexported fields */} KeyAltLeft = Key{/* contains filtered or unexported fields */} KeyAltRight = Key{/* contains filtered or unexported fields */} KeyControl = Key{/* contains filtered or unexported fields */} KeyControlLeft = Key{/* contains filtered or unexported fields */} KeyControlRight = Key{/* contains filtered or unexported fields */} KeyDelete = Key{/* contains filtered or unexported fields */} KeyEnd = Key{/* contains filtered or unexported fields */} KeyEnter = Key{/* contains filtered or unexported fields */} KeyEscape = Key{/* contains filtered or unexported fields */} KeyHome = Key{/* contains filtered or unexported fields */} KeyInsert = Key{/* contains filtered or unexported fields */} KeyNumLock = Key{/* contains filtered or unexported fields */} KeyPageDown = Key{/* contains filtered or unexported fields */} KeyPageUp = Key{/* contains filtered or unexported fields */} KeyPause = Key{/* contains filtered or unexported fields */} KeyPeriod = Key{/* contains filtered or unexported fields */} KeyPrintScreen = Key{/* contains filtered or unexported fields */} KeyScrollLock = Key{/* contains filtered or unexported fields */} KeySemicolon = Key{/* contains filtered or unexported fields */} KeyShift = Key{/* contains filtered or unexported fields */} KeyShiftLeft = Key{/* contains filtered or unexported fields */} KeyShiftRight = Key{/* contains filtered or unexported fields */} KeySlash = Key{/* contains filtered or unexported fields */} KeySpace = Key{/* contains filtered or unexported fields */} KeyTab = Key{/* contains filtered or unexported fields */} KeyF1 = Key{/* contains filtered or unexported fields */} KeyF2 = Key{/* contains filtered or unexported fields */} KeyF3 = Key{/* contains filtered or unexported fields */} KeyF4 = Key{/* contains filtered or unexported fields */} KeyF5 = Key{/* contains filtered or unexported fields */} KeyF6 = Key{/* contains filtered or unexported fields */} KeyF7 = Key{/* contains filtered or unexported fields */} KeyF8 = Key{/* contains filtered or unexported fields */} KeyF9 = Key{/* contains filtered or unexported fields */} KeyF10 = Key{/* contains filtered or unexported fields */} KeyF11 = Key{/* contains filtered or unexported fields */} KeyF12 = Key{/* contains filtered or unexported fields */} KeyNum0 = Key{/* contains filtered or unexported fields */} KeyNum1 = Key{/* contains filtered or unexported fields */} KeyNum2 = Key{/* contains filtered or unexported fields */} KeyNum3 = Key{/* contains filtered or unexported fields */} KeyNum4 = Key{/* contains filtered or unexported fields */} KeyNum5 = Key{/* contains filtered or unexported fields */} KeyNum6 = Key{/* contains filtered or unexported fields */} KeyNum7 = Key{/* contains filtered or unexported fields */} KeyNum8 = Key{/* contains filtered or unexported fields */} KeyNum9 = Key{/* contains filtered or unexported fields */} KeyNumAdd = Key{/* contains filtered or unexported fields */} KeyNumDivide = Key{/* contains filtered or unexported fields */} KeyNumEnter = Key{/* contains filtered or unexported fields */} KeyNumMultiply = Key{/* contains filtered or unexported fields */} KeyNumPeriod = Key{/* contains filtered or unexported fields */} KeyNumSubtract = Key{/* contains filtered or unexported fields */} )
Keyboard keys.
var ( KeyGamepadStart = Key{/* contains filtered or unexported fields */} KeyGamepadBack = Key{/* contains filtered or unexported fields */} KeyGamepadHome = Key{/* contains filtered or unexported fields */} KeyGamepadUp = Key{/* contains filtered or unexported fields */} KeyGamepadRight = Key{/* contains filtered or unexported fields */} KeyGamepadDown = Key{/* contains filtered or unexported fields */} KeyGamepadLeft = Key{/* contains filtered or unexported fields */} // Stick keys that simulate the D-pad like events. KeyGamepadLStickUp = Key{/* contains filtered or unexported fields */} KeyGamepadLStickRight = Key{/* contains filtered or unexported fields */} KeyGamepadLStickDown = Key{/* contains filtered or unexported fields */} KeyGamepadLStickLeft = Key{/* contains filtered or unexported fields */} KeyGamepadRStickUp = Key{/* contains filtered or unexported fields */} KeyGamepadRStickRight = Key{/* contains filtered or unexported fields */} KeyGamepadRStickDown = Key{/* contains filtered or unexported fields */} KeyGamepadRStickLeft = Key{/* contains filtered or unexported fields */} // Stick button press. KeyGamepadLStick = Key{/* contains filtered or unexported fields */} KeyGamepadRStick = Key{/* contains filtered or unexported fields */} // Stick keys that can be used for the smooth movement. KeyGamepadLStickMotion = Key{/* contains filtered or unexported fields */} KeyGamepadRStickMotion = Key{/* contains filtered or unexported fields */} KeyGamepadA = Key{/* contains filtered or unexported fields */} KeyGamepadB = Key{/* contains filtered or unexported fields */} KeyGamepadX = Key{/* contains filtered or unexported fields */} KeyGamepadY = Key{/* contains filtered or unexported fields */} KeyGamepadL1 = Key{/* contains filtered or unexported fields */} KeyGamepadL2 = Key{/* contains filtered or unexported fields */} KeyGamepadR1 = Key{/* contains filtered or unexported fields */} KeyGamepadR2 = Key{/* contains filtered or unexported fields */} )
Gamepad keys (we're trying to follow the Xbox naming conventions here).
Functions ¶
This section is empty.
Types ¶
type Action ¶
type Action uint32
Action is an ID that represents an abstract action that can be activeted by the input.
type DeviceKind ¶
type DeviceKind uint8
DeviceKind is used as a bit mask to select the enabled input devices. See constants like KeyboardInput and GamepadInput. Combine them like KeyboardInput|GamepadInput to get a bit mask that includes multiple entries. Use AnyDevice if you want to have a mask covering all devices.
const ( KeyboardDevice DeviceKind = 1 << iota GamepadDevice MouseDevice TouchDevice )
const AnyDevice DeviceKind = KeyboardDevice | GamepadDevice | MouseDevice | TouchDevice
AnyDevice includes all input devices.
func (DeviceKind) String ¶
func (d DeviceKind) String() string
String returns a pretty-printed representation of the input device mask.
type EventInfo ¶
type EventInfo struct { Duration int Pos Vec StartPos Vec // contains filtered or unexported fields }
EventInfo holds extra information about the input device event.
Pos carries the event location, if available. Pos is a click location for mouse events. Pos is a tap location for screen touch events. Use HasPos() predicate to know whether there is a pos associated with the event to distinguish between (0, 0) pos and lack of pos info.
StartPos is only set for a few events where it makes sense. A drag event, for instance, will store the "dragging from" location there.
Duration carries the key press duration if available. Duration specifies how long the key has been pressed in ticks same as inpututil.KeyPressDuration. Duration for key press with modifiers it will return the lowest duration of all key presses. Use HasDuration() predicate to know whether there is a duration associated with the event to distinguish between 0 duration and lack of duration info.
func (EventInfo) HasDuration ¶ added in v0.9.1
HasDuration reports whether this event has a press duration associated with it. Use Duration field to get the press duration value.
func (EventInfo) HasPos ¶
HasPos reports whether this event has a position associated with it. Use Pos field to get the pos value.
func (EventInfo) IsGamepadEvent ¶
IsGamepadEvent reports whether this event was triggered by a gamepad device.
func (EventInfo) IsKeyboardEvent ¶
IsKeyboardEvent reports whether this event was triggered by a keyboard device.
func (EventInfo) IsMouseEvent ¶
IsMouseEvent reports whether this event was triggered by a mouse device.
func (EventInfo) IsTouchEvent ¶
IsTouchEvent reports whether this event was triggered by a screen touch device.
type Handler ¶
type Handler struct { // GamepadDeadzone is the magnitude of a controller stick movements // the handler can receive before registering it as an input. // // The default value is 0.055, meaning the slight movements are ignored. // A value of 0.5 means about half the axis is ignored. // // The default value is good for a new/responsive controller. // For more worn out controllers or flaky sticks, a higher value may be required. // // This parameter can be adjusted on the fly, so you're encouraged to // give a player a way to configure a deadzone that will fit their controller. // // Note that this is a per-handler option. // Different gamepads/devices can have different deadzone values. GamepadDeadzone float64 // contains filtered or unexported fields }
Handler is used to associate a keymap with an abstract input consumer.
The ID bound to the handler is used to distinguish which gamepad is related to this handler.
You usually need to create the input handlers only once and carry them through the game using your preferred method.
If any game object needs to handle the input, they need an input handler object.
func (*Handler) ActionIsJustPressed ¶
ActionIsJustPressed is like inpututil.IsKeyJustPressed, but operates on the action level and works with any kinds of "keys". It returns true if any of the keys bound to the action was pressed during this frame.
func (*Handler) ActionIsJustReleased ¶ added in v0.9.1
ActionIsJustReleased is like inpututil.IsKeyJustReleased, but operates on the action level and works with any kinds of "keys". It returns true if any of the keys bound to the action was released during this frame.
Implementation limitation: for now it doesn't work for some of the key types. It's easier to list the supported list:
- Keyboard events
- Mouse events
- Gamepad normal buttons events (doesn't include joystick D-pad emulation events like KeyGamepadLStickUp)
For the keys with modifiers it doesn't require the modifier keys to be released simultaneously with a main key. These modifier keys can be in either "pressed" or "just released" state. This makes the "ctrl+left click just released" event easier to perform on the user's side (try releasing ctrl on the same frame as left click, it's hard!)
TODO: implement other "action released" events if feasible. The touch tap events, for example, doesn't sound useful here: a tap is only registered when the gesture was already finished. Therefore, the tap release event would be identical to a tap activation event. We could re-word this event by saying that "released" happens when previous frame ActionIsPressed reported true and the current frame reported false. But that's a more complicated task. Let's wait until users report their use cases.
Note: this action event is never simulated (see #35).
func (*Handler) ActionIsPressed ¶
ActionIsPressed is like ebiten.IsKeyPressed, but operates on the action level and works with any kinds of "keys". It returns true if any of the keys bound to the action is being pressed.
func (*Handler) ActionKeyNames ¶
func (h *Handler) ActionKeyNames(action Action, mask DeviceKind) []string
ActionKeyNames returns a list of key names associated by this action.
It filters the results by a given input device mask. If you want to include all input device keys, use AnyDevice value.
This function is useful when you want to display a list of keys the player should press in order to activate some action.
The filtering is useful to avoid listing the unrelated options. For example, if player uses the gamepad, it could be weird to show keyboard options listed. For the simple cases, you can use DefaultInputMask() method to get the mask that will try to avoid that situation. See its comment to learn more.
Keys with modifiers will have them listed too. Modifiers are separated by "+". A "k" keyboard key with ctrl modifier will have a "ctrl+k" name.
Note: this function doesn't check whether some input device is available or not. For example, if mask contains a TouchDevice, but touch actions are not available on a machine, touch-related keys will still be returned. It's up to the caller to specify a correct device mask. Using a AnyDevice mask would return all mapped keys for the action.
func (*Handler) DefaultInputMask ¶
func (h *Handler) DefaultInputMask() DeviceKind
DefaultInputMask returns the input mask suitable for functions like ActionKeyNames.
If gamepad is connected, it returns GamepadInput mask. Otherwise it returns KeyboardInput+MouseInput mask. This is good enough for the simplest games, but you may to implement this logic inside your game if you need something more complicated.
func (*Handler) EmitEvent ¶
func (h *Handler) EmitEvent(e SimulatedAction)
EmitEvent activates the given action for the player. Only the handlers with the same player ID will discover this action.
Note: simulated events are only visible after the next System.Update() call.
Note: action release events can't be simulated yet (see #35).
See SimulatedAction documentation for more info.
Experimental: this is a part of virtual input API, which is not stable yet.
func (*Handler) EmitKeyEvent ¶ added in v0.9.0
func (h *Handler) EmitKeyEvent(e SimulatedKeyEvent)
EmitKeyEvent sends given key event into the input system.
The event is emitted from the perspective of this handler, so the gamepad events will be handled properly in the multi-device context.
Note: simulated events are only visible after the next System.Update() call.
Note: key release events can't be simulated yet (see #35).
See SimulatedKeyEvent documentation for more info.
Experimental: this is a part of virtual input API, which is not stable yet.
func (*Handler) GamepadConnected ¶
GamepadConnected reports whether the gamepad associated with this handler is connected. The gamepad ID is the handler ID used during the handler creation.
There should be at least one call to the System.Update() before this function can return the correct results.
func (*Handler) JustPressedActionInfo ¶
JustPressedActionInfo is like ActionIsJustPressed, but with more information.
The first return value will hold the extra event info. The second return value is false if given action is not activated.
See EventInfo comment to learn more.
func (*Handler) JustReleasedActionInfo ¶ added in v0.9.1
JustReleasedActionInfo is like ActionIsJustReleased, but with more information.
This method has the same limitations as ActionIsJustReleased (see its comments).
The first return value will hold the extra event info. The second return value is false if given action is not just released.
See EventInfo comment to learn more.
Note: this action event is never simulated (see #35).
func (*Handler) PressedActionInfo ¶ added in v0.9.0
PressedActionInfo is like ActionIsPressed, but with more information.
The first return value will hold the extra event info. The second return value is false if given action is not activated.
See EventInfo comment to learn more.
func (*Handler) Remap ¶ added in v0.9.0
Remap changes the handler keymap while keeping all other settings the same.
func (*Handler) TouchEventsEnabled ¶
TouchEventsEnabled reports whether this handler can receive screen touch events.
type Key ¶
type Key struct {
// contains filtered or unexported fields
}
Key represents an input method that can be used to activate Action. Key could be a keyboard key, a gamepad key, a mouse button, etc.
Use the predefined global vars like KeyMouseLeft and KeyTab to create a Keymap.
func KeyWithModifier ¶
func KeyWithModifier(k Key, mod KeyModifier) Key
KeyWithModifier turns k into a combined modifier+k key. For instance, KeyUp+ModControl will trigger an action only if both of these keys are being pressed.
func ParseKey ¶
ParseKeys tries to construct an appropriate Key object given its name.
It can also be used as a string->key constructor:
ParseKey("left") // returns KeyLeft ParseKey("gamepad_left") // returns KeyGamepadLeft
The format is one of the following:
- keyname
- mod+keyname
- mod+mod+keyname
Some valid input examples:
- "gamepad_left"
- "left"
- "ctrl+left"
- "ctrl+shift+left"
- "shift+ctrl+left"
See Handler.ActionKeyNames() for more information about the key names.
type KeyModifier ¶
type KeyModifier uint8
const ( ModUnknown KeyModifier = iota ModControl ModShift ModControlShift )
type KeyScanStatus ¶ added in v0.9.0
type KeyScanStatus int
KeyScanStatus represents the KeyScanner.Scan operation result.
const ( KeyScanUnchanged KeyScanStatus = iota KeyScanChanged KeyScanCompleted )
type KeyScanner ¶ added in v0.9.0
type KeyScanner struct {
// contains filtered or unexported fields
}
KeyScanner checks the currently pressed keys and buttons and tries to map them to a local Key type that can be used in a Keymap.
Use NewKeyScanner to create a usable object of this type.
Experimental: this is a part of a key remapping API, which is not stable yet.
func NewKeyScanner ¶ added in v0.9.0
func NewKeyScanner(h *Handler) *KeyScanner
NewKeyScanner creates a key scanner for the specifier input Handler.
You don't have to create a new scanner for every remap; they can be reused.
It's important to have the correct Handler though: their ID is used to check the appropriate device keys.
Experimental: this is a part of a key remapping API, which is not stable yet.
func (*KeyScanner) Scan ¶ added in v0.9.0
func (s *KeyScanner) Scan() (Key, KeyScanStatus)
Scan reads the buttons state and tries to map them to a Key.
It's intended to work with keyboard keys as well as gamepad buttons, but right now it only works for the keyboard.
This function should be called on every frame where you're reading the new keybind combination. See the remap example for more info.
The function can return these result statuses: * Unchanged - nothing updated since the last Scan() operation * Changed - some keys changed, you may want to update the prompt to the user * Completed - the user finished specifying the keys combination, you can use the Key as a new binding
type Keymap ¶
Keymap associates a list of keys with an action. Any of the keys from the list can activate the action.
func MergeKeymaps ¶ added in v0.9.1
MergeKeymaps merges a list of keymaps into one.
Given maps are not modified. The resulting keymap contains no references to input keymaps.
Any key duplicates for a single action are ignored, therefore:
{Action1: [KeyA, KeyB]} + {Action1: [KeyC, KeyB]} = {Action1: [KeyA, KeyB, KeyC]}
The keys order depends on the input keymaps arguments order. Keymaps with keys of higher priorities should go first. So, if you have a keyboardKeymap and gamepadKeymap keymaps, passing gamepadKeymap before keyboardKeymap would make the gamepad key binds take priority: MergeKeymaps(gamepadKeymap, keyboardKeymap).
type SimulatedAction ¶ added in v0.9.0
SimulatedAction represents an artificially triggered action.
It shares many properties with SimulatedKeyEvent, but the event consumers will have no way of knowing which input device was used to emit this event, because SimulatedAction has no device associated with it.
As a consequence, all event info methods like IsGamepadeEvent() will report false. It's possible to trigger an action that has no keys associated with it. All actions triggered using this method will be only visible to the handler of the same player ID (like gamepad button events).
Experimental: this is a part of virtual input API, which is not stable yet.
type SimulatedKeyEvent ¶ added in v0.9.0
SimulatedKeyEvent represents a virtual input that can be send down the stream.
The data carried by this event will be used to construct an EventInfo object.
Experimental: this is a part of virtual input API, which is not stable yet.
type System ¶
type System struct {
// contains filtered or unexported fields
}
System is the main component of the input library.
You usually need only one input system object.
Store System object (by value) inside your game context/state object like this:
struct GameState { InputSystem input.System }
When ebitengine game is executed, call gameState.InputSystem.Init() once.
On every ebitengine Update() call, use gameState.InputSystem.Update().
The system is usually not used directly after the input handlers are created. Use input handlers to handle the user input.
func (*System) Init ¶
func (sys *System) Init(config SystemConfig)
func (*System) NewHandler ¶
NewHandler creates a handler associated with player/device ID. IDs should start with 0 with a step of 1. So, NewHandler(0, ...) then NewHandler(1, ...).
If you want to configure the handler further, use Handler fields/methods to do that. For example, see Handler.GamepadDeadzone.
func (*System) Update ¶
func (sys *System) Update()
Update reads the input state and updates the information available to all input handlers. Generally, you call this method from your ebiten.Game.Update() method.
Since ebitengine uses a fixed timestep architecture, a time delta of 1.0/60.0 is implied. If you need a control over that, use UpdateWithDelta() instead.
The time delta mostly needed for things like press gesture detection: we need to calculate when a tap becomes a [long] press.
func (*System) UpdateWithDelta ¶ added in v0.9.0
UpdateWithDelta is like Update(), but it allows you to specify the time delta.
type SystemConfig ¶
type SystemConfig struct { // DevicesEnabled selects the input devices that should be handled. // For the most cases, AnyDevice value is a good option. DevicesEnabled DeviceKind }
SystemConfig configures the input system. This configuration can't be changed once created.
type Vec ¶
Vec is a simple wrapper around a pair of float64 coordinates.
Since most games use float values for most values, input library converts int pair to the float pair once per Update() call so all usages inside the frame can use already converted values.
We're not using some vector2d library to avoid extra dependencies. It should be easy to convert this Point object into any other structure.