...

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

View as plain text