Skip to content

Commit 5c4af95

Browse files
committed
pxt.json: user-interface-base: 0.0.16 to 0.0.26, added generic keyboard
1 parent 7c9fc36 commit 5c4af95

4 files changed

Lines changed: 298 additions & 13 deletions

File tree

app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace microgui {
1111
}
1212

1313
// application configuration
14-
user_interface_base.getIcon = (id) => user_interface_base.icons.get(id)
14+
user_interface_base.getIcon = (id) => user_interface_base.icons.get(id as string)
1515
user_interface_base.resolveTooltip = (ariaId: string) => ariaId
1616

1717
export class App {

guiComponents.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,8 +1430,8 @@ namespace microgui {
14301430
this.components[componentID].addContext(context)
14311431
}
14321432

1433-
/* override */ startup() {
1434-
super.startup()
1433+
/* override */ startup(controlSetupFn?: () => {}) {
1434+
super.startup(controlSetupFn)
14351435
}
14361436

14371437
private focus(hideOthers: boolean) {

inputMethods.ts

Lines changed: 294 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,291 @@ namespace microgui {
178178
}
179179

180180

181+
export enum KeyboardLayouts {
182+
QWERTY,
183+
NUMERIC
184+
}
185+
186+
interface IKeyboard {
187+
appendText(txt: string): void;
188+
deletePriorCharacters(n: number): void;
189+
swapCase(): void;
190+
nextScene(): void;
191+
}
192+
193+
type KeyboardBtnFn = (btn: Button, kb: IKeyboard) => void;
194+
type SpecialBtnData = { btnRow: number, btnCol: number, behaviour: (btn: Button, kb: IKeyboard) => void };
195+
type KeyboardLayoutData = {
196+
[id: number]: {
197+
btnTexts: string[][],
198+
defaultBtnBehaviour: KeyboardBtnFn,
199+
specialBtnBehaviours: SpecialBtnData[]
200+
}
201+
};
202+
203+
const __keyboardLayout: KeyboardLayoutData = {
204+
[KeyboardLayouts.QWERTY]: {
205+
btnTexts: [
206+
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
207+
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
208+
["A", "S", "D", "F", "G", "H", "J", "K", "L", ";"],
209+
["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/"],
210+
["<-", "^", " _______ ", "ENTER"]
211+
],
212+
defaultBtnBehaviour: (btn: Button, kb: IKeyboard) => {
213+
kb.appendText(btn.state[0])
214+
},
215+
specialBtnBehaviours: [
216+
{ btnRow: 4, btnCol: 0, behaviour: (btn: Button, kb: IKeyboard) => kb.deletePriorCharacters(1) }, // Backspace
217+
{ btnRow: 4, btnCol: 1, behaviour: (btn: Button, kb: IKeyboard) => kb.swapCase() }, // Change case
218+
{ btnRow: 4, btnCol: 2, behaviour: (btn: Button, kb: IKeyboard) => kb.appendText(" ") }, // Spacebar
219+
{ btnRow: 4, btnCol: 3, behaviour: (btn: Button, kb: IKeyboard) => kb.nextScene() } // ENTER
220+
]
221+
}
222+
};
223+
224+
export class Keyboard extends CursorSceneWithPriorPage implements IKeyboard {
225+
private btns: Button[][]
226+
private text: string;
227+
private isUpperCase: boolean;
228+
private nextBtnFn: (keyboardText: string) => void
229+
private keyboardLayout: KeyboardLayouts;
230+
231+
// Special effects:
232+
private frameCounter: number;
233+
private shakeText: boolean
234+
private shakeTextCounter: number
235+
236+
constructor(
237+
app: AppInterface,
238+
keyboardLayout: KeyboardLayouts,
239+
nextBtnFn: (keyboardText: string) => void,
240+
priorScene: CursorSceneWithPriorPage,
241+
) {
242+
super(app, () => {app.popScene(); app.pushScene(priorScene)}, new GridNavigator([[]]))
243+
this.text = ""
244+
this.isUpperCase = true
245+
246+
this.nextBtnFn = nextBtnFn;
247+
this.frameCounter = 0;
248+
this.shakeText = false;
249+
this.shakeTextCounter = 0;
250+
251+
this.keyboardLayout = keyboardLayout;
252+
}
253+
254+
startup(controlSetupFn: () => void) {
255+
super.startup(controlSetupFn)
256+
257+
const data = __keyboardLayout[KeyboardLayouts.QWERTY];
258+
this.btns = data.btnTexts.map(_ => [])
259+
260+
for (let row = 0; row < data.btnTexts.length; row++) {
261+
const bitmapWidths = data.btnTexts[row].map((txt: string) => 2 + (bitmaps.font8.charWidth * (txt.length + 1)));
262+
const totalWidth: number = bitmapWidths.reduce((total: number, w: number) => total + w, 0)
263+
264+
let x: number = -Screen.HALF_WIDTH + (bitmapWidths[0] >> 1) + 3;
265+
for (let col = 0; col < data.btnTexts[row].length; col++) {
266+
const bitmapWidth = bitmapWidths[col]
267+
x+= bitmapWidth >> 1
268+
this.btns[row][col] =
269+
new Button({
270+
parent: null,
271+
style: ButtonStyles.Transparent,
272+
icon: bitmaps.create(bitmapWidth - 4, 10),
273+
ariaId: "",
274+
x,
275+
y: (13 * (row + 1)) - 18,
276+
onClick: (btn: Button) => data.defaultBtnBehaviour(btn, this),
277+
state: [data.btnTexts[row][col]]
278+
})
279+
x += bitmapWidth >> 1;
280+
}
281+
}
282+
283+
// Setup special btn behaviours:
284+
data.specialBtnBehaviours.forEach(
285+
(data: SpecialBtnData, i) => {
286+
this.btns[data.btnRow][data.btnCol].onClick =
287+
(btn: Button) => data.behaviour(btn, this);
288+
}
289+
)
290+
this.navigator.setBtns(this.btns)
291+
}
292+
293+
get textLength() { return this.text.length }
294+
295+
//-------------------
296+
// Interface Methods:
297+
//-------------------
298+
299+
public appendText(str: string) {
300+
if (this.textLength < KEYBOARD_MAX_TEXT_LENGTH)
301+
this.frameCounter = KEYBOARD_FRAME_COUNTER_CURSOR_ON
302+
else
303+
this.shakeText = true
304+
this.text += str;
305+
}
306+
307+
public deletePriorCharacters(n: number) {
308+
this.text =
309+
(this.text.length > 0)
310+
? this.text.substr(0, this.text.length - n)
311+
: this.text
312+
this.frameCounter = KEYBOARD_FRAME_COUNTER_CURSOR_ON
313+
}
314+
315+
public swapCase(): void {
316+
this.isUpperCase = !this.isUpperCase;
317+
318+
const swapCaseFn = (this.isUpperCase)
319+
? (t: string) => {return t.toUpperCase()}
320+
: (t: string) => {return t.toLowerCase()}
321+
322+
323+
const specialBtnData: SpecialBtnData[] = __keyboardLayout[this.keyboardLayout].specialBtnBehaviours;
324+
const specialBtnRows: number[] = specialBtnData.map((sbd: SpecialBtnData) => sbd.btnRow);
325+
const specialBtnCols: number[] = specialBtnData.map((sbd: SpecialBtnData) => sbd.btnCol);
326+
327+
const isSpecialBtn = (row: number, col: number): boolean => {
328+
return (specialBtnRows.indexOf(row) != -1) && (specialBtnRows.indexOf(col) != -1);
329+
}
330+
331+
// Skip special char row:
332+
for (let i = 0; i < this.btns.length - 1; i++) {
333+
for (let j = 0; j < this.btns[i].length; j++) {
334+
if (!isSpecialBtn)
335+
continue
336+
337+
const btnText: string = (this.btns[i][j].state[0] as string)
338+
this.btns[i][j].state[0] = swapCaseFn(btnText);
339+
}
340+
}
341+
}
342+
343+
public nextScene(): void {
344+
this.nextBtnFn(this.text)
345+
}
346+
347+
draw() {
348+
this.frameCounter += 1
349+
350+
// Blue base colour:
351+
Screen.fillRect(
352+
Screen.LEFT_EDGE,
353+
Screen.TOP_EDGE,
354+
Screen.WIDTH,
355+
Screen.HEIGHT,
356+
6 // Blue
357+
)
358+
359+
// Black border around the text window for depth
360+
Screen.fillRect(
361+
Screen.LEFT_EDGE + 3,
362+
Screen.TOP_EDGE + 4,
363+
Screen.WIDTH - 7,
364+
34,
365+
15 // Black
366+
)
367+
368+
// White text window, slightly smaller than the black
369+
Screen.fillRect(
370+
Screen.LEFT_EDGE + 6,
371+
Screen.TOP_EDGE + 6,
372+
Screen.WIDTH - 12,
373+
32,
374+
1 // White
375+
)
376+
377+
378+
// Legal text length, draw a flickering cursor using this.frameCounter:
379+
if (this.text.length < KEYBOARD_MAX_TEXT_LENGTH) {
380+
screen().printCenter(this.text, 17, 15)
381+
if (this.frameCounter >= KEYBOARD_FRAME_COUNTER_CURSOR_ON) {
382+
screen().print(
383+
"_",
384+
(screen().width / 2) + ((this.text.length * bitmaps.font8.charWidth) / 2),
385+
17,
386+
15
387+
)
388+
389+
if (this.frameCounter >= KEYBOARD_FRAME_COUNTER_CURSOR_OFF)
390+
this.frameCounter = 0
391+
}
392+
}
393+
394+
// Don't draw the cursor if beyond the max length, shake the text a bit:
395+
else if (this.shakeText) {
396+
if (this.shakeTextCounter % 5 == 0) {
397+
screen().print(
398+
this.text,
399+
(screen().width / 2) - ((this.text.length * bitmaps.font8.charWidth) / 2) - 2,
400+
17,
401+
15
402+
)
403+
}
404+
405+
else {
406+
screen().print(
407+
this.text,
408+
(screen().width / 2) - ((this.text.length * bitmaps.font8.charWidth) / 2),
409+
17,
410+
15
411+
)
412+
}
413+
414+
if (this.shakeTextCounter >= 5) {
415+
this.shakeText = false;
416+
this.shakeTextCounter = 0;
417+
}
418+
419+
else
420+
this.shakeTextCounter += 1
421+
}
422+
423+
// At max-length, done shaking the text:
424+
else {
425+
screen().printCenter(this.text, 17, 15)
426+
}
427+
428+
// Orange Keyboard with a black shadow on the bot & right edge (depth effect):
429+
430+
// Black border around right & bot edge:
431+
Screen.fillRect(
432+
Screen.LEFT_EDGE + 4,
433+
Screen.TOP_EDGE + 47,
434+
Screen.WIDTH - 6,
435+
71,
436+
15 // Black
437+
)
438+
439+
// Orange keyboard that the white text will be ontop of:
440+
Screen.fillRect(
441+
Screen.LEFT_EDGE + 4,
442+
Screen.TOP_EDGE + 44,
443+
Screen.WIDTH - 8,
444+
72,
445+
4 // Orange
446+
)
447+
448+
for (let i = 0; i < this.btns.length; i++) {
449+
for (let j = 0; j < this.btns[i].length; j++) {
450+
const btn = this.btns[i][j];
451+
const btnText = btn.state[0];
452+
453+
const x = (screen().width / 2) + btn.xfrm.localPos.x - (btn.icon.width / 2) + 2
454+
const y = (screen().height / 2) + btn.xfrm.localPos.y + font.charHeight - 12
455+
456+
btn.draw()
457+
screen().print(btnText, x, y, 1) // White text
458+
}
459+
}
460+
461+
super.draw()
462+
}
463+
}
464+
465+
181466
const KEYBOARD_FRAME_COUNTER_CURSOR_ON = 20;
182467
const KEYBOARD_FRAME_COUNTER_CURSOR_OFF = 40;
183468
const KEYBOARD_MAX_TEXT_LENGTH = 20
@@ -214,12 +499,12 @@ namespace microgui {
214499
this.shakeTextCounter = 0;
215500
}
216501

217-
/* override */ startup() {
218-
super.startup()
502+
/* override */ startup(controlSetupFn: () => void) {
503+
super.startup(controlSetupFn)
219504

220505
const defaultBehaviour = (btn: Button) => {
221506
if (this.text.length < KEYBOARD_MAX_TEXT_LENGTH) {
222-
this.text += this.btnsText[btn.state[0]][btn.state[1]]
507+
this.text += btn.state[0]
223508
this.frameCounter = KEYBOARD_FRAME_COUNTER_CURSOR_ON
224509
}
225510
else {
@@ -417,7 +702,7 @@ namespace microgui {
417702
}
418703
}
419704

420-
super.draw()
705+
// super.draw()
421706
}
422707
}
423708

@@ -446,11 +731,11 @@ namespace microgui {
446731
this.next = next
447732
}
448733

449-
/* override */ startup() {
450-
super.startup()
734+
/* override */ startup(controlSetupFn: () => void) {
735+
super.startup(controlSetupFn)
451736

452737
const defaultBehaviour = (btn: Button) => {
453-
this.text += this.btnText[btn.state[0]] + " "
738+
this.text += btn.state[0] + " "
454739
}
455740

456741
const behaviours = []
@@ -684,8 +969,8 @@ namespace microgui {
684969
this.callbacks = callbacks
685970
}
686971

687-
/* override */ startup() {
688-
super.startup()
972+
/* override */ startup(controlSetupFn: () => void) {
973+
super.startup(controlSetupFn)
689974

690975
for (let i = 0; i < this.callbacks.length; i++) {
691976
for (let j = 0; j < this.callbacks[0].length; j++) {

pxt.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"radio": "*",
88
"microphone": "*",
99
"datalogger": "*",
10-
"user-interface-base": "github:microbit-apps/user-interface-base#v0.0.16",
10+
"user-interface-base": "github:microbit-apps/user-interface-base#v0.0.26",
1111
"display-shield": "github:microbit-apps/display-shield#v1.0.4"
1212
},
1313
"files": [

0 commit comments

Comments
 (0)