Kaido Kert | b108943 | 2024-03-18 19:46:49 -0700 | [diff] [blame^] | 1 | <!DOCTYPE html> |
| 2 | <title>TextEdit demo in CanvasKit</title> |
| 3 | <meta charset="utf-8" /> |
| 4 | <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | <script type="text/javascript" src="https://particles.skia.org/dist/canvaskit.js"></script> |
| 7 | <script type="text/javascript" src="textapi_utils.js"></script> |
| 8 | <script type="text/javascript" src="spiralshader.js"></script> |
| 9 | |
| 10 | <style> |
| 11 | canvas { |
| 12 | border: 1px dashed grey; |
| 13 | } |
| 14 | </style> |
| 15 | |
| 16 | <body> |
| 17 | <h1>TextEdit in CanvasKit</h1> |
| 18 | |
| 19 | <canvas id=para2 width=600 height=600 tabindex='-1'></canvas> |
| 20 | </body> |
| 21 | |
| 22 | <script type="text/javascript" charset="utf-8"> |
| 23 | let CanvasKit; |
| 24 | onload = async () => { |
| 25 | CanvasKit = await CanvasKitInit({ locateFile: (file) => 'https://particles.skia.org/dist/'+file }); |
| 26 | ParagraphAPI2(); |
| 27 | }; |
| 28 | |
| 29 | function ParagraphAPI2() { |
| 30 | const surface = CanvasKit.MakeCanvasSurface('para2'); |
| 31 | if (!surface) { |
| 32 | console.error('Could not make surface'); |
| 33 | return; |
| 34 | } |
| 35 | |
| 36 | const mouse = MakeMouse(); |
| 37 | const cursor = MakeCursor(CanvasKit); |
| 38 | const canvas = surface.getCanvas(); |
| 39 | const spiralEffect = MakeSpiralShaderEffect(CanvasKit); |
| 40 | |
| 41 | const text0 = "In a hole in the ground there lived a hobbit. Not a nasty, dirty, " + |
| 42 | "wet hole full of worms and oozy smells. This was a hobbit-hole and " + |
| 43 | "that means good food, a warm hearth, and all the comforts of home."; |
| 44 | const LOC_X = 20, |
| 45 | LOC_Y = 20; |
| 46 | |
| 47 | const bgPaint = new CanvasKit.Paint(); |
| 48 | bgPaint.setColor([0.965, 0.965, 0.965, 1]); |
| 49 | |
| 50 | const editor = MakeEditor(text0, {typeface:null, size:30}, cursor, 540); |
| 51 | |
| 52 | editor.applyStyleToRange({size:130}, 0, 1); |
| 53 | editor.applyStyleToRange({italic:true}, 38, 38+6); |
| 54 | editor.applyStyleToRange({color:[1,0,0,1]}, 5, 5+4); |
| 55 | |
| 56 | editor.setXY(LOC_X, LOC_Y); |
| 57 | |
| 58 | function drawFrame(canvas) { |
| 59 | const lines = editor.getLines(); |
| 60 | |
| 61 | canvas.clear(CanvasKit.WHITE); |
| 62 | |
| 63 | if (mouse.isActive()) { |
| 64 | const pos = mouse.getPos(-LOC_X, -LOC_Y); |
| 65 | const a = lines_pos_to_index(lines, pos[0], pos[1]); |
| 66 | const b = lines_pos_to_index(lines, pos[2], pos[3]); |
| 67 | if (a === b) { |
| 68 | editor.setIndex(a); |
| 69 | } else { |
| 70 | editor.setIndices(a, b); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | canvas.drawRect(editor.bounds(), bgPaint); |
| 75 | |
| 76 | { |
| 77 | // update our animated shaders |
| 78 | const rad_scale = Math.sin(Date.now() / 5000) / 2; |
| 79 | const shader0 = spiralEffect.makeShader([ |
| 80 | rad_scale, |
| 81 | editor.width()/2, editor.width()/2, |
| 82 | 1,0,0,1, // color0 |
| 83 | 0,0,1,1 // color1 |
| 84 | ]); |
| 85 | editor.draw(canvas, [shader0]); |
| 86 | shader0.delete(); |
| 87 | } |
| 88 | |
| 89 | surface.requestAnimationFrame(drawFrame); |
| 90 | } |
| 91 | surface.requestAnimationFrame(drawFrame); |
| 92 | |
| 93 | function interact(e) { |
| 94 | const type = e.type; |
| 95 | if (type === 'pointerup') { |
| 96 | mouse.setUp(e.offsetX, e.offsetY); |
| 97 | } else if (type === 'pointermove') { |
| 98 | mouse.setMove(e.offsetX, e.offsetY); |
| 99 | } else if (type === 'pointerdown') { |
| 100 | mouse.setDown(e.offsetX, e.offsetY); |
| 101 | } |
| 102 | }; |
| 103 | |
| 104 | function keyhandler(e) { |
| 105 | switch (e.key) { |
| 106 | case 'ArrowLeft': editor.moveDX(-1); return; |
| 107 | case 'ArrowRight': editor.moveDX(1); return; |
| 108 | case 'ArrowUp': |
| 109 | e.preventDefault(); |
| 110 | editor.moveDY(-1); |
| 111 | return; |
| 112 | case 'ArrowDown': |
| 113 | e.preventDefault(); |
| 114 | editor.moveDY(1); |
| 115 | return; |
| 116 | case 'Backspace': |
| 117 | editor.deleteSelection(-1); |
| 118 | return; |
| 119 | case 'Delete': |
| 120 | editor.deleteSelection(1); |
| 121 | return; |
| 122 | case 'Shift': |
| 123 | return; |
| 124 | case 'Tab': // todo: figure out how to handle... |
| 125 | e.preventDefault(); |
| 126 | return; |
| 127 | } |
| 128 | if (e.ctrlKey) { |
| 129 | e.preventDefault(); |
| 130 | e.stopImmediatePropagation(); |
| 131 | switch (e.key) { |
| 132 | case 'r': editor.applyStyleToSelection({color:[1,0,0,1]}); return; |
| 133 | case 'g': editor.applyStyleToSelection({color:[0,0.6,0,1]}); return; |
| 134 | case 'u': editor.applyStyleToSelection({color:[0,0,1,1]}); return; |
| 135 | case 'k': editor.applyStyleToSelection({color:[0,0,0,1]}); return; |
| 136 | |
| 137 | case 's': editor.applyStyleToSelection({shaderIndex:0}); return; |
| 138 | |
| 139 | case 'i': editor.applyStyleToSelection({italic:'toggle'}); return; |
| 140 | case 'b': editor.applyStyleToSelection({bold:'toggle'}); return; |
| 141 | case 'w': editor.applyStyleToSelection({wavy:'toggle'}); return; |
| 142 | |
| 143 | case ']': editor.applyStyleToSelection({size_add:1}); return; |
| 144 | case '[': editor.applyStyleToSelection({size_add:-1}); return; |
| 145 | case '}': editor.applyStyleToSelection({size_add:10}); return; |
| 146 | case '{': editor.applyStyleToSelection({size_add:-10}); return; |
| 147 | } |
| 148 | } |
| 149 | if (!e.ctrlKey && !e.metaKey) { |
| 150 | if (e.key.length == 1) { // avoid keys like "Escape" for now |
| 151 | e.preventDefault(); |
| 152 | e.stopImmediatePropagation(); |
| 153 | editor.insert(e.key); |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | document.getElementById('para2').addEventListener('pointermove', interact); |
| 159 | document.getElementById('para2').addEventListener('pointerdown', interact); |
| 160 | document.getElementById('para2').addEventListener('pointerup', interact); |
| 161 | document.getElementById('para2').addEventListener('keydown', keyhandler); |
| 162 | return surface; |
| 163 | } |
| 164 | |
| 165 | </script> |