...

Source file src/code.rocket9labs.com/tslocum/etk/kibodo/keyboard.go

Documentation: code.rocket9labs.com/tslocum/etk/kibodo

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

View as plain text