...

Source file src/codeberg.org/tslocum/etk/kibodo/keyboard.go

Documentation: codeberg.org/tslocum/etk/kibodo

     1  package kibodo
     2  
     3  import (
     4  	"image"
     5  	"image/color"
     6  	"time"
     7  
     8  	"github.com/hajimehoshi/ebiten/v2"
     9  	"github.com/hajimehoshi/ebiten/v2/inpututil"
    10  	"github.com/hajimehoshi/ebiten/v2/text/v2"
    11  )
    12  
    13  // Keyboard is an on-screen keyboard widget.
    14  type Keyboard struct {
    15  	x, y int
    16  	w, h int
    17  
    18  	visible      bool
    19  	alpha        float64
    20  	passPhysical bool
    21  
    22  	incomingBuffer []rune
    23  
    24  	inputEvents []*Input
    25  
    26  	keys         [][]*Key
    27  	normalKeys   [][]*Key
    28  	extendedKeys [][]*Key
    29  	showExtended bool
    30  
    31  	backgroundLower *ebiten.Image
    32  	backgroundUpper *ebiten.Image
    33  	backgroundDirty bool
    34  
    35  	op *ebiten.DrawImageOptions
    36  
    37  	backgroundColor     color.RGBA
    38  	lastBackgroundColor color.RGBA
    39  
    40  	shift bool
    41  
    42  	touchIDs    []ebiten.TouchID
    43  	holdTouchID ebiten.TouchID
    44  	holdKey     *Key
    45  	wasPressed  bool
    46  
    47  	hideShortcuts []ebiten.Key
    48  
    49  	fontSource *text.GoTextFaceSource
    50  	fontSize   int
    51  	fontFace   *text.GoTextFace
    52  	lineHeight int
    53  	lineOffset int
    54  
    55  	backspaceDelay  time.Duration
    56  	backspaceRepeat time.Duration
    57  	backspaceLast   time.Time
    58  
    59  	scheduleFrameFunc func()
    60  }
    61  
    62  // NewKeyboard returns a new Keyboard widget.
    63  func NewKeyboard(fontSource *text.GoTextFaceSource, fontSize int) *Keyboard {
    64  	k := &Keyboard{
    65  		alpha: 1.0,
    66  		op: &ebiten.DrawImageOptions{
    67  			Filter: ebiten.FilterNearest,
    68  		},
    69  		keys:            KeysQWERTY,
    70  		normalKeys:      KeysQWERTY,
    71  		backgroundLower: ebiten.NewImage(1, 1),
    72  		backgroundUpper: ebiten.NewImage(1, 1),
    73  		backgroundColor: color.RGBA{0, 0, 0, 255},
    74  		holdTouchID:     -1,
    75  		hideShortcuts:   []ebiten.Key{ebiten.KeyEscape},
    76  		fontSource:      fontSource,
    77  		fontSize:        fontSize,
    78  		fontFace:        fontFace(fontSource, fontSize),
    79  		backspaceDelay:  500 * time.Millisecond,
    80  		backspaceRepeat: 75 * time.Millisecond,
    81  	}
    82  	k.fontUpdated()
    83  	return k
    84  }
    85  
    86  func fontFace(source *text.GoTextFaceSource, size int) *text.GoTextFace {
    87  	return &text.GoTextFace{
    88  		Source: source,
    89  		Size:   float64(size),
    90  	}
    91  }
    92  
    93  // SetRect sets the position and size of the widget.
    94  func (k *Keyboard) SetRect(x, y, w, h int) {
    95  	if k.x == x && k.y == y && k.w == w && k.h == h {
    96  		return
    97  	}
    98  	k.x, k.y, k.w, k.h = x, y, w, h
    99  
   100  	k.updateKeyRects()
   101  	k.backgroundDirty = true
   102  }
   103  
   104  // Rect returns the position and size of the widget.
   105  func (k *Keyboard) Rect() image.Rectangle {
   106  	return image.Rect(k.x, k.y, k.x+k.w, k.y+k.h)
   107  }
   108  
   109  // GetKeys returns the keys of the keyboard.
   110  func (k *Keyboard) GetKeys() [][]*Key {
   111  	return k.keys
   112  }
   113  
   114  // SetKeys sets the keys of the keyboard.
   115  func (k *Keyboard) SetKeys(keys [][]*Key) {
   116  	k.normalKeys = keys
   117  
   118  	if !k.showExtended && !keysEqual(keys, k.keys) {
   119  		k.keys = keys
   120  		k.updateKeyRects()
   121  		k.backgroundDirty = true
   122  	}
   123  }
   124  
   125  // SetExtendedKeys sets the keys of the keyboard when the symbol key is pressed.
   126  func (k *Keyboard) SetExtendedKeys(keys [][]*Key) {
   127  	k.extendedKeys = keys
   128  
   129  	if k.showExtended && !keysEqual(keys, k.keys) {
   130  		k.keys = keys
   131  		k.updateKeyRects()
   132  		k.backgroundDirty = true
   133  	}
   134  }
   135  
   136  // SetShowExtended sets whether the normal or extended keyboard is shown.
   137  func (k *Keyboard) SetShowExtended(show bool) {
   138  	if k.showExtended == show {
   139  		return
   140  	}
   141  	k.showExtended = show
   142  	if k.showExtended {
   143  		k.keys = k.extendedKeys
   144  	} else {
   145  		k.keys = k.normalKeys
   146  	}
   147  	k.updateKeyRects()
   148  	k.backgroundDirty = true
   149  }
   150  
   151  // SetFont sets the key label font.
   152  func (k *Keyboard) SetFont(fontSource *text.GoTextFaceSource, fontSize int) {
   153  	k.fontSource = fontSource
   154  	k.fontSize = fontSize
   155  	k.fontFace = fontFace(fontSource, fontSize)
   156  	k.fontUpdated()
   157  
   158  	k.backgroundDirty = true
   159  }
   160  
   161  func (k *Keyboard) fontUpdated() {
   162  	m := k.fontFace.Metrics()
   163  	k.lineHeight = int(m.HAscent + m.HDescent)
   164  	k.lineOffset = int(m.CapHeight)
   165  	if k.lineOffset < 0 {
   166  		k.lineOffset *= -1
   167  	}
   168  }
   169  
   170  // SetHideShortcuts sets the key shortcuts which, when pressed, will hide the
   171  // keyboard. Defaults to the Escape key.
   172  func (k *Keyboard) SetHideShortcuts(shortcuts []ebiten.Key) {
   173  	k.hideShortcuts = shortcuts
   174  }
   175  
   176  func (k *Keyboard) updateKeyRects() {
   177  	if len(k.keys) == 0 {
   178  		return
   179  	}
   180  
   181  	maxCells := 0
   182  	for _, rowKeys := range k.keys {
   183  		if len(rowKeys) > maxCells {
   184  			maxCells = len(rowKeys)
   185  		}
   186  	}
   187  
   188  	// TODO user configurable
   189  	cellPaddingW := 1
   190  	cellPaddingH := 1
   191  
   192  	cellH := (k.h - (cellPaddingH * (len(k.keys) - 1))) / len(k.keys)
   193  
   194  	row := 0
   195  	x, y := 0, 0
   196  	for _, rowKeys := range k.keys {
   197  		if len(rowKeys) == 0 {
   198  			continue
   199  		}
   200  
   201  		availableWidth := k.w
   202  		for _, key := range rowKeys {
   203  			if key.Wide {
   204  				availableWidth = availableWidth / 2
   205  				break
   206  			}
   207  		}
   208  
   209  		cellW := (availableWidth - (cellPaddingW * (len(rowKeys) - 1))) / len(rowKeys)
   210  
   211  		x = 0
   212  		for i, key := range rowKeys {
   213  			key.w, key.h = cellW, cellH
   214  			key.x, key.y = x, y
   215  
   216  			if i == len(rowKeys)-1 {
   217  				key.w = k.w - key.x
   218  			}
   219  
   220  			if key.Wide {
   221  				key.w = k.w - k.w/2 + (cellW)
   222  			}
   223  
   224  			x += key.w
   225  		}
   226  
   227  		// Count non-empty rows only
   228  		row++
   229  		y += (cellH + cellPaddingH)
   230  	}
   231  }
   232  
   233  func (k *Keyboard) at(x, y int) *Key {
   234  	if !k.visible {
   235  		return nil
   236  	}
   237  	if x >= k.x && x <= k.x+k.w && y >= k.y && y <= k.y+k.h {
   238  		x, y = x-k.x, y-k.y // Offset
   239  		for _, rowKeys := range k.keys {
   240  			for _, key := range rowKeys {
   241  				if x >= key.x && x <= key.x+key.w && y >= key.y && y <= key.y+key.h {
   242  					return key
   243  				}
   244  			}
   245  		}
   246  	}
   247  	return nil
   248  }
   249  
   250  // KeyAt returns the key located at the specified position, or nil if no key is found.
   251  func (k *Keyboard) KeyAt(x, y int) *Key {
   252  	return k.at(x, y)
   253  }
   254  
   255  func (k *Keyboard) handleToggleExtendedKey(inputKey ebiten.Key) bool {
   256  	if inputKey != KeyToggleExtended {
   257  		return false
   258  	}
   259  	k.showExtended = !k.showExtended
   260  	if k.showExtended {
   261  		k.keys = k.extendedKeys
   262  	} else {
   263  		k.keys = k.normalKeys
   264  	}
   265  	k.updateKeyRects()
   266  	k.backgroundDirty = true
   267  	return true
   268  }
   269  
   270  func (k *Keyboard) handleHideKey(inputKey ebiten.Key) bool {
   271  	for _, key := range k.hideShortcuts {
   272  		if key == inputKey {
   273  			k.Hide()
   274  			return true
   275  		}
   276  	}
   277  	return false
   278  }
   279  
   280  // Hit handles a key press.
   281  func (k *Keyboard) Hit(key *Key) {
   282  	now := time.Now()
   283  	if !key.pressedTime.IsZero() && now.Sub(key.pressedTime) < 50*time.Millisecond {
   284  		return
   285  	}
   286  	key.pressedTime = now
   287  	key.repeatTime = now.Add(k.backspaceDelay)
   288  
   289  	input := key.LowerInput
   290  	if k.shift {
   291  		input = key.UpperInput
   292  	}
   293  
   294  	if input.Key == ebiten.KeyShift {
   295  		k.shift = !k.shift
   296  		if k.scheduleFrameFunc != nil {
   297  			k.scheduleFrameFunc()
   298  		}
   299  		return
   300  	} else if k.handleToggleExtendedKey(input.Key) || k.handleHideKey(input.Key) {
   301  		return
   302  	}
   303  
   304  	k.inputEvents = append(k.inputEvents, input)
   305  }
   306  
   307  // HandleMouse passes the specified mouse event to the on-screen keyboard.
   308  func (k *Keyboard) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
   309  	if k.backgroundDirty {
   310  		k.drawBackground()
   311  		k.backgroundDirty = false
   312  	}
   313  
   314  	//pressDuration := 50 * time.Millisecond
   315  	if clicked {
   316  		var key *Key
   317  		if cursor.X != 0 || cursor.Y != 0 {
   318  			key = k.at(cursor.X, cursor.Y)
   319  		}
   320  		for _, rowKeys := range k.keys {
   321  			for _, rowKey := range rowKeys {
   322  				if key != nil && rowKey == key {
   323  					continue
   324  				}
   325  				rowKey.pressed = false
   326  			}
   327  		}
   328  		if key != nil {
   329  			key.pressed = true
   330  
   331  			k.Hit(key)
   332  		}
   333  		k.wasPressed = true
   334  	} else if pressed {
   335  		key := k.at(cursor.X, cursor.Y)
   336  		if key != nil {
   337  			// Repeat backspace and delete operations.
   338  			input := key.LowerInput
   339  			if k.shift {
   340  				input = key.UpperInput
   341  			}
   342  			if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
   343  				for time.Since(key.repeatTime) >= k.backspaceRepeat {
   344  					k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
   345  					key.repeatTime = key.repeatTime.Add(k.backspaceRepeat)
   346  				}
   347  			}
   348  
   349  			key.pressed = true
   350  			k.wasPressed = true
   351  
   352  			for _, rowKeys := range k.keys {
   353  				for _, rowKey := range rowKeys {
   354  					if rowKey == key || !rowKey.pressed {
   355  						continue
   356  					}
   357  					rowKey.pressed = false
   358  				}
   359  			}
   360  		}
   361  	} else if k.wasPressed {
   362  		for _, rowKeys := range k.keys {
   363  			for _, rowKey := range rowKeys {
   364  				if !rowKey.pressed {
   365  					continue
   366  				}
   367  				rowKey.pressed = false
   368  			}
   369  		}
   370  	}
   371  	return true, nil
   372  }
   373  
   374  // Update handles user input. This function is called by Ebitengine.
   375  func (k *Keyboard) Update() error {
   376  	if !k.visible {
   377  		return nil
   378  	}
   379  
   380  	if k.backgroundDirty {
   381  		k.drawBackground()
   382  		k.backgroundDirty = false
   383  	}
   384  
   385  	// Pass through physical keyboard input
   386  	if k.passPhysical {
   387  		// Read input characters
   388  		k.incomingBuffer = ebiten.AppendInputChars(k.incomingBuffer[:0])
   389  		if len(k.incomingBuffer) > 0 {
   390  			for _, r := range k.incomingBuffer {
   391  				k.inputEvents = append(k.inputEvents, &Input{Rune: r}) // Pass through
   392  			}
   393  		} else {
   394  			// Read keys
   395  			for _, key := range allKeys {
   396  				if inpututil.IsKeyJustPressed(key) {
   397  					if k.handleHideKey(key) {
   398  						// Hidden
   399  						return nil
   400  					}
   401  					k.inputEvents = append(k.inputEvents, &Input{Key: key}) // Pass through
   402  				}
   403  			}
   404  		}
   405  	}
   406  
   407  	// Handle mouse input
   408  	var key *Key
   409  	pressDuration := 50 * time.Millisecond
   410  	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
   411  		x, y := ebiten.CursorPosition()
   412  
   413  		key = k.at(x, y)
   414  		if key != nil {
   415  			for _, rowKeys := range k.keys {
   416  				for _, rowKey := range rowKeys {
   417  					rowKey.pressed = false
   418  				}
   419  			}
   420  			key.pressed = true
   421  
   422  			k.Hit(key)
   423  
   424  			go func() {
   425  				time.Sleep(pressDuration)
   426  
   427  				key.pressed = false
   428  				if k.scheduleFrameFunc != nil {
   429  					k.scheduleFrameFunc()
   430  				}
   431  			}()
   432  		}
   433  
   434  		k.wasPressed = true
   435  	} else if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
   436  		x, y := ebiten.CursorPosition()
   437  
   438  		key = k.at(x, y)
   439  		if key != nil {
   440  			if !key.pressed {
   441  				input := key.LowerInput
   442  				if k.shift {
   443  					input = key.UpperInput
   444  				}
   445  
   446  				// Repeat backspace and delete operations.
   447  				if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
   448  					for time.Since(key.repeatTime) >= k.backspaceRepeat {
   449  						k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
   450  						key.repeatTime = key.repeatTime.Add(k.backspaceRepeat)
   451  					}
   452  				}
   453  			}
   454  			key.pressed = true
   455  
   456  			for _, rowKeys := range k.keys {
   457  				for _, rowKey := range rowKeys {
   458  					if rowKey == key || !rowKey.pressed {
   459  						continue
   460  					}
   461  					rowKey.pressed = false
   462  				}
   463  			}
   464  		}
   465  
   466  		k.wasPressed = true
   467  	}
   468  
   469  	// Handle touch input
   470  	if k.holdTouchID != -1 {
   471  		x, y := ebiten.TouchPosition(k.holdTouchID)
   472  		if x == 0 && y == 0 {
   473  			k.holdTouchID = -1
   474  		} else {
   475  			key = k.at(x, y)
   476  			if key != k.holdKey {
   477  				k.holdTouchID = -1
   478  				return nil
   479  			}
   480  			//k.Hold(key)
   481  			k.holdKey = key
   482  			k.wasPressed = true
   483  		}
   484  	}
   485  	if k.holdTouchID == -1 {
   486  		k.touchIDs = inpututil.AppendJustPressedTouchIDs(k.touchIDs[:0])
   487  		for _, id := range k.touchIDs {
   488  			x, y := ebiten.TouchPosition(id)
   489  
   490  			key = k.at(x, y)
   491  			if key != nil {
   492  				input := key.LowerInput
   493  				if k.shift {
   494  					input = key.UpperInput
   495  				}
   496  
   497  				if !key.pressed {
   498  					key.pressed = true
   499  					key.pressedTouchID = id
   500  
   501  					for _, rowKeys := range k.keys {
   502  						for _, rowKey := range rowKeys {
   503  							if rowKey != key && rowKey.pressed {
   504  								rowKey.pressed = false
   505  							}
   506  						}
   507  					}
   508  
   509  					k.Hit(key)
   510  					k.holdTouchID = id
   511  					k.holdKey = key
   512  
   513  					// Repeat backspace and delete operations.
   514  					if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
   515  						k.backspaceLast = time.Now().Add(k.backspaceDelay)
   516  					}
   517  				} else {
   518  					// Repeat backspace and delete operations.
   519  					if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
   520  						for time.Since(k.backspaceLast) >= k.backspaceRepeat {
   521  							k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
   522  							k.backspaceLast = k.backspaceLast.Add(k.backspaceRepeat)
   523  						}
   524  					}
   525  				}
   526  				k.wasPressed = true
   527  			}
   528  		}
   529  	}
   530  	for _, rowKeys := range k.keys {
   531  		for _, rowKey := range rowKeys {
   532  			if rowKey == key || !rowKey.pressed {
   533  				continue
   534  			}
   535  			rowKey.pressed = false
   536  		}
   537  	}
   538  	return nil
   539  }
   540  
   541  func (k *Keyboard) drawBackground() {
   542  	if k.w == 0 || k.h == 0 {
   543  		return
   544  	}
   545  
   546  	if !k.backgroundLower.Bounds().Eq(image.Rect(0, 0, k.w, k.h)) || !k.backgroundUpper.Bounds().Eq(image.Rect(0, 0, k.w, k.h)) || k.backgroundColor != k.lastBackgroundColor {
   547  		k.backgroundLower = ebiten.NewImage(k.w, k.h)
   548  		k.backgroundUpper = ebiten.NewImage(k.w, k.h)
   549  		k.lastBackgroundColor = k.backgroundColor
   550  	}
   551  	k.backgroundLower.Fill(k.backgroundColor)
   552  	k.backgroundUpper.Fill(k.backgroundColor)
   553  
   554  	lightShade := color.RGBA{150, 150, 150, 255}
   555  	darkShade := color.RGBA{30, 30, 30, 255}
   556  
   557  	var keyImage *ebiten.Image
   558  	for i := 0; i < 2; i++ {
   559  		shift := i == 1
   560  		img := k.backgroundLower
   561  		if shift {
   562  			img = k.backgroundUpper
   563  		}
   564  		for _, rowKeys := range k.keys {
   565  			for _, key := range rowKeys {
   566  				r := image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)
   567  				keyImage = img.SubImage(r).(*ebiten.Image)
   568  
   569  				// Draw key background
   570  				// TODO configurable
   571  				keyImage.Fill(color.RGBA{90, 90, 90, 255})
   572  
   573  				// Draw key label
   574  				label := key.LowerLabel
   575  				if shift {
   576  					label = key.UpperLabel
   577  				}
   578  
   579  				var x, y int
   580  				size := k.fontSize
   581  				f := k.fontFace
   582  				for {
   583  					boundsW, boundsY := text.Measure(label, f, float64(k.lineHeight))
   584  					x = key.w/2 - int(boundsW)/2
   585  					if x < 0 {
   586  						x = 0
   587  					}
   588  					y = key.h/2 - int(boundsY)/2
   589  					if (int(boundsW) <= key.w && int(boundsY) <= key.h) || size == 1 {
   590  						break
   591  					}
   592  					// Scale font size until key label fits.
   593  					size--
   594  					f = fontFace(k.fontSource, size)
   595  				}
   596  				op := &text.DrawOptions{}
   597  				op.GeoM.Translate(float64(key.x+x), float64(key.y+y))
   598  				op.ColorScale.ScaleWithColor(color.White)
   599  				text.Draw(keyImage, label, f, op)
   600  
   601  				// Draw border
   602  				keyImage.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+1)).(*ebiten.Image).Fill(lightShade)
   603  				keyImage.SubImage(image.Rect(key.x, key.y, key.x+1, key.y+key.h)).(*ebiten.Image).Fill(lightShade)
   604  				keyImage.SubImage(image.Rect(key.x, key.y+key.h-1, key.x+key.w, key.y+key.h)).(*ebiten.Image).Fill(darkShade)
   605  				keyImage.SubImage(image.Rect(key.x+key.w-1, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image).Fill(darkShade)
   606  			}
   607  		}
   608  	}
   609  }
   610  
   611  // Draw draws the widget on the provided image.  This function is called by Ebitengine.
   612  func (k *Keyboard) Draw(target *ebiten.Image) {
   613  	if !k.visible {
   614  		return
   615  	}
   616  
   617  	if k.backgroundDirty {
   618  		k.drawBackground()
   619  		k.backgroundDirty = false
   620  	}
   621  
   622  	var background *ebiten.Image
   623  	if !k.shift {
   624  		background = k.backgroundLower
   625  	} else {
   626  		background = k.backgroundUpper
   627  	}
   628  
   629  	k.op.GeoM.Reset()
   630  	k.op.GeoM.Translate(float64(k.x), float64(k.y))
   631  	k.op.ColorM.Scale(1, 1, 1, k.alpha)
   632  	target.DrawImage(background, k.op)
   633  	k.op.ColorM.Reset()
   634  
   635  	// Draw pressed keys
   636  	for _, rowKeys := range k.keys {
   637  		for _, key := range rowKeys {
   638  			if !key.pressed {
   639  				continue
   640  			}
   641  
   642  			// TODO buffer to prevent issues with alpha channel
   643  			k.op.GeoM.Reset()
   644  			k.op.GeoM.Translate(float64(k.x+key.x), float64(k.y+key.y))
   645  			k.op.ColorM.Scale(0.75, 0.75, 0.75, k.alpha)
   646  
   647  			target.DrawImage(background.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image), k.op)
   648  			k.op.ColorM.Reset()
   649  
   650  			// Draw shadow.
   651  			darkShade := color.RGBA{60, 60, 60, 255}
   652  			subImg := target.SubImage(image.Rect(k.x+key.x, k.y+key.y, k.x+key.x+key.w, k.y+key.y+1)).(*ebiten.Image)
   653  			subImg.Fill(darkShade)
   654  			subImg = target.SubImage(image.Rect(k.x+key.x, k.y+key.y, k.x+key.x+1, k.y+key.y+key.h)).(*ebiten.Image)
   655  			subImg.Fill(darkShade)
   656  		}
   657  	}
   658  }
   659  
   660  // SetPassThroughPhysicalInput sets a flag that controls whether physical
   661  // keyboard input is passed through to the widget's input buffer. This is not
   662  // enabled by default.
   663  func (k *Keyboard) SetPassThroughPhysicalInput(pass bool) {
   664  	k.passPhysical = pass
   665  }
   666  
   667  // SetAlpha sets the transparency level of the widget on a scale of 0 to 1.0.
   668  func (k *Keyboard) SetAlpha(alpha float64) {
   669  	k.alpha = alpha
   670  }
   671  
   672  // Show shows the widget.
   673  func (k *Keyboard) Show() {
   674  	k.visible = true
   675  }
   676  
   677  // Visible returns whether the widget is currently shown.
   678  func (k *Keyboard) Visible() bool {
   679  	return k.visible
   680  }
   681  
   682  // Hide hides the widget.
   683  func (k *Keyboard) Hide() {
   684  	k.visible = false
   685  	if k.showExtended {
   686  		k.showExtended = false
   687  		k.keys = k.normalKeys
   688  		k.updateKeyRects()
   689  		k.backgroundDirty = true
   690  	}
   691  }
   692  
   693  // AppendInput appends user input that was received since the function was last called.
   694  func (k *Keyboard) AppendInput(events []*Input) []*Input {
   695  	events = append(events, k.inputEvents...)
   696  	k.inputEvents = nil
   697  	return events
   698  }
   699  
   700  // SetScheduleFrameFunc sets the function called whenever the screen should be redrawn.
   701  func (k *Keyboard) SetScheduleFrameFunc(f func()) {
   702  	k.scheduleFrameFunc = f
   703  }
   704  
   705  func keysEqual(a [][]*Key, b [][]*Key) bool {
   706  	if len(a) != len(b) {
   707  		return false
   708  	}
   709  	for i := range a {
   710  		if len(a[i]) != len(b[i]) {
   711  			return false
   712  		}
   713  		for j := range b[i] {
   714  			if a[i][j] != b[i][j] {
   715  				return false
   716  			}
   717  		}
   718  	}
   719  	return true
   720  }
   721  

View as plain text