diff --git a/src/bootstrap/galaxy.ts b/src/bootstrap/galaxy.ts
index 59cde13c..9303150e 100644
--- a/src/bootstrap/galaxy.ts
+++ b/src/bootstrap/galaxy.ts
@@ -88,4 +88,7 @@ dispatch.sub('window.change', () => {
})
// TODO(smolck): Put this somewhere else?
-api.onAction('update-nameplates', () => (windows.refresh(), console.log('refresh')))
+api.onAction(
+ 'update-nameplates',
+ () => (windows.refresh(), console.log('refresh'))
+)
diff --git a/src/bootstrap/index.html b/src/bootstrap/index.html
index f07f6a57..6f3e8c43 100644
--- a/src/bootstrap/index.html
+++ b/src/bootstrap/index.html
@@ -166,8 +166,6 @@
diff --git a/src/components/extensions/grep.tsx b/src/components/extensions/grep.tsx
index 98cb546a..fcef0e57 100644
--- a/src/components/extensions/grep.tsx
+++ b/src/components/extensions/grep.tsx
@@ -1,7 +1,7 @@
import { RowNormal, RowHeader } from '../row-container'
import { PluginRight } from '../plugin-container'
import { vimBlur, vimFocus } from '../../ui/uikit'
-import { showCursorline } from '../../core/cursor'
+// TODO(smolck): import { showCursorline } from '../../core/cursor'
import Input from '../text-input'
import { badgeStyle } from '../../ui/styles'
import { render } from 'inferno'
@@ -67,7 +67,7 @@ const selectResult = (results: Result[], ix: number, subix: number) => {
const [path, items] = results[ix]
const { line, column } = items[subix]
api.nvim.jumpTo({ path, line, column })
- showCursorline()
+ // TODO(smolck): showCursorline()
}
const highlightPattern = (
diff --git a/src/components/extensions/lsp-references.tsx b/src/components/extensions/lsp-references.tsx
index 20f75c17..1f557f9c 100644
--- a/src/components/extensions/lsp-references.tsx
+++ b/src/components/extensions/lsp-references.tsx
@@ -2,7 +2,7 @@ import { RowNormal, RowHeader } from '../row-container'
import { PluginRight } from '../plugin-container'
import { vimBlur, vimFocus } from '../../ui/uikit'
import { simplifyPath } from '../../support/utils'
-import { showCursorline } from '../../core/cursor'
+// TODO(smolck): import { showCursorline } from '../../core/cursor'
import { badgeStyle } from '../../ui/styles'
import { render } from 'inferno'
import Input from '../text-input'
@@ -82,7 +82,7 @@ const selectResult = (references: Refs[], ix: number, subix: number) => {
line: lineNum - 1,
column: column - 1,
})
- showCursorline()
+ // TODO(smolck): showCursorline()
}
const highlightPattern = (
diff --git a/src/components/text-input.tsx b/src/components/text-input.tsx
index 0f574b1f..df2803e4 100644
--- a/src/components/text-input.tsx
+++ b/src/components/text-input.tsx
@@ -1,6 +1,5 @@
import Loading from './loading'
import { paddingVH, cvar } from '../ui/css'
-import { xfrmUp } from '../core/input'
import { FormEvent } from 'inferno'
import Icon from './icon'
@@ -59,25 +58,6 @@ const nopMaybe = (obj: object) =>
get: (_, key) => Reflect.get(obj, key) || (() => {}),
}) as Props
-const keToStr = (e: KeyboardEvent) =>
- [
- e.key,
- (e.ctrlKey as any) | 0,
- (e.metaKey as any) | 0,
- (e.altKey as any) | 0,
- (e.shiftKey as any) | 0,
- ].join('')
-
-// TODO: could be better? it's global so will be shared between different
-// inputs. however only one input will have focus at a time, so perhaps
-// it's not a big deal
-//
-// the reason this has to live outside the function is because the view
-// function will be triggered on re-render. pressing keys will potentially
-// trigger re-renders, thus reseting the value of lastDown when inside
-// the function.
-let lastDown = ''
-
// TODO(smolck): But why though? Has to be another way to get access to
// `onComponentDidMount` with normal stuff like
const WhyInput = (props: any) =>
@@ -167,24 +147,10 @@ const textInput = (
setFocus(e.currentTarget, focus)
setPosition(e.currentTarget, position)
}}
- onKeyUp={(e: KeyboardEvent) => {
- const prevKeyAndThisOne = lastDown + keToStr(e)
-
- if (xfrmUp.has(prevKeyAndThisOne)) {
- const { key } = xfrmUp.get(prevKeyAndThisOne)!(e)
- if (key.toLowerCase() === '') {
- lastDown = ''
- ;(e.target as HTMLInputElement).blur()
- return $.hide()
- }
- }
- }}
onKeyDown={(e: KeyboardEvent) => {
const { ctrlKey: ctrl, metaKey: meta, key } = e
const cm = ctrl || meta
- lastDown = keToStr(e)
-
if (key === 'Tab') {
e.preventDefault()
return $.tab()
diff --git a/src/core/cursor.ts b/src/core/cursor.ts
index a68e2a14..2660825a 100644
--- a/src/core/cursor.ts
+++ b/src/core/cursor.ts
@@ -1,93 +1,39 @@
import * as windows from '../windows/window-manager'
-import { partialFill, translate } from '../ui/css'
-import { paddingX } from '../windows/window'
-import { cell } from '../core/workspace'
+import { hexToRGB } from '../ui/css'
export enum CursorShape {
- block,
- line,
- underline,
+ block = 0,
+ line = 1,
+ underline = 2,
}
export const cursor = {
+ visible: false,
row: 0,
col: 0,
- color: '#fff',
+ color: [0, 0, 0],
shape: CursorShape.block,
+ size: 20,
}
-const cursorEl = document.getElementById('cursor') as HTMLElement
-const cursorChar = document.createElement('span')
-const cursorline = document.getElementById('cursorline') as HTMLElement
-export const debugline = document.getElementById('debugline') as HTMLElement
+let cursorEnabled = false
let cursorRequestedToBeHidden = false
-let cursorEnabled = true
-let cursorCharVisible = true
-
-Object.assign(cursorline.style, {
- background: 'rgba(var(--background-alpha), 0.2)',
- position: 'absolute',
- 'mix-blend-mode': 'screen',
- height: `${cell.height}px`,
- 'z-index': 60,
-})
-
-Object.assign(debugline.style, {
- display: 'none',
- position: 'absolute',
- 'mix-blend-mode': 'screen',
- height: `${cell.height}px`,
- 'z-index': 60,
-})
-
-Object.assign(cursorEl.style, {
- 'z-index': 70,
- position: 'absolute',
- display: 'none',
- 'justify-content': 'center',
- 'align-items': 'center',
-})
-
-Object.assign(cursorChar.style, {
- filter: 'invert(1) grayscale(1)',
- 'font-family': 'var(--font)',
- 'font-size': 'calc(var(--font-size) * 1px)',
-})
-
-cursorEl.appendChild(cursorChar)
-
-export const getCursorBoundingClientRect = () =>
- cursorline.getBoundingClientRect()
export const setCursorShape = (shape: CursorShape, size = 20) => {
cursor.shape = shape
+ cursor.size = size
- if (shape === CursorShape.block)
- Object.assign(cursorEl.style, {
- background: cursor.color,
- height: `${cell.height}px`,
- width: `${cell.width}px`,
- })
-
- if (shape === CursorShape.line)
- Object.assign(cursorEl.style, {
- background: cursor.color,
- height: `${cell.height}px`,
- width: `${(cell.width * (size / 100)).toFixed(2)}px`,
- })
-
- if (shape === CursorShape.underline)
- Object.assign(cursorEl.style, {
- background: partialFill('horizontal', cursor.color, size),
- height: `${cell.height}px`,
- width: `${cell.width}px`,
- })
+ windows.webgl.updateCursorShape(shape)
}
export const setCursorColor = (color: string) => {
- cursorChar.style.color = color
- cursor.color = color
- cursorEl.style.background = color
+ let [r, g, b] = hexToRGB(color)
+ r /= 255
+ g /= 255
+ b /= 255
+ cursor.color = [r, g, b]
+
+ windows.webgl.updateCursorColor(r, g, b)
}
export const enableCursor = () => (cursorEnabled = true)
@@ -95,69 +41,28 @@ export const disableCursor = () => (cursorEnabled = false)
export const hideCursor = () => {
if (!cursorEnabled) return
-
cursorRequestedToBeHidden = true
- cursorEl.style.display = 'none'
- cursorline.style.display = 'none'
+
+ windows.webgl.showCursor(false)
+ Object.assign(cursor, { visible: false })
}
export const showCursor = () => {
if (!cursorEnabled) return
-
cursorRequestedToBeHidden = false
- cursorEl.style.display = 'flex'
- cursorline.style.display = 'none'
-}
-
-export const showCursorline = () => (cursorline.style.display = '')
-export const updateCursorChar = () => {
- cursorChar.innerText =
- cursor.shape === CursorShape.block
- ? windows.getActive().editor.getChar(cursor.row, cursor.col)
- : ''
-
- if (cursor.shape === CursorShape.block && !cursorCharVisible)
- cursorChar.style.display = ''
+ windows.webgl.showCursor(true)
+ Object.assign(cursor, { visible: true })
}
-const updateCursorCharInternal = (gridId: number, row: number, col: number) => {
- if (cursor.shape !== CursorShape.block) {
- cursorChar.style.display = 'none'
- cursorCharVisible = false
- cursorChar.innerText = ''
- return
- }
-
- const char = windows.get(gridId).editor.getChar(row, col)
- cursorChar.innerText = char
- cursorChar.style.display = ''
- cursorCharVisible = true
-}
+// TODO(smolck): export const showCursorline = () => (cursorline.style.display = '')
-export const moveCursor = (gridId: number, row: number, col: number) => {
+export const moveCursor = (row: number, col: number) => {
Object.assign(cursor, { row, col })
- // even if cursor(line) is hidden, we still need to update the positions.
- // once the cursor elements are re-activated, the position updated while
- // hidden must be accurate. (e.g. using jumpTo() in grep/references/etc)
- const win = windows.get(gridId)
- const cursorPos = win.positionToWorkspacePixels(row, col)
- const linePos = win.positionToWorkspacePixels(row, 0)
- const { width } = win.getWindowSize()
-
- cursorEl.style.transform = translate(cursorPos.x, cursorPos.y)
-
- Object.assign(cursorline.style, {
- transform: translate(linePos.x - paddingX, linePos.y),
- width: `${width}px`,
- height: `${cell.height}px`,
- })
-
- updateCursorCharInternal(gridId, row, col)
-
if (cursorRequestedToBeHidden) return
showCursor()
+ windows.webgl.updateCursorPosition(row, col)
}
setCursorShape(CursorShape.block)
diff --git a/src/core/input.ts b/src/core/input.ts
index 5b480e48..8cebe9d5 100644
--- a/src/core/input.ts
+++ b/src/core/input.ts
@@ -1,40 +1,18 @@
-import { normalizeVimMode } from '../support/neovim-utils'
import { input } from '../core/master-control'
import { VimMode } from '../neovim/types'
-import { $, is } from '../support/utils'
+import { $ } from '../support/utils'
import api from '../core/instance-api'
import { remote } from 'electron'
-import { Script } from 'vm'
export enum InputType {
Down = 'down',
Up = 'up',
}
-interface RemapModifer {
- from: string
- to: string
-}
-
-interface KeyShape extends KeyboardEvent {
- mode?: VimMode
-}
-
-interface KeyTransform {
- mode: string
- event: 'hold' | 'up' | 'down'
- match: KeyboardEvent
- transform: string
-}
-
type OnKeyFn = (inputKeys: string, inputType: InputType) => void
const modifiers = ['Alt', 'Shift', 'Meta', 'Control']
-const remaps = new Map()
let isCapturing = true
-let holding = ''
-let xformed = false
-let lastDown = ''
let windowHasFocus = true
let lastEscapeTimestamp = 0
let shouldClearEscapeOnNextAppFocus = false
@@ -85,102 +63,19 @@ const wrapKey = (key: string): string =>
key.length > 1 && isUpper(key[0]) ? `<${key}>` : key
const combineModsWithKey = (mods: string, key: string) =>
mods.length ? `${mods}-${key}` : key
-const userModRemaps = (mods: string[]) => mods.map((m) => remaps.get(m) || m)
const joinModsWithDash = (mods: string[]) => mods.join('-')
-const mapMods = $(handleMods, userModRemaps, joinModsWithDash)
+const mapMods = $(handleMods, joinModsWithDash)
const mapKey = $(bypassEmptyMod, toVimKey)
const formatInput = $(combineModsWithKey, wrapKey)
const shortcuts = new Map()
const globalShortcuts = new Map void>()
-const resetInputState = () => {
- xformed = false
- lastDown = ''
- holding = ''
-}
-
export const focus = () => {
isCapturing = true
- resetInputState()
}
export const blur = () => {
isCapturing = false
- resetInputState()
-}
-
-export const setupRemapModifiers = (mappings: RemapModifer[]) => {
- if (!mappings) return
- remaps.clear()
- mappings.forEach((mapping) => remapModifier(mapping.from, mapping.to))
-}
-
-const vimscriptObjectToECMA = (obj: any) =>
- Object.entries(obj).reduce((res, [key, val]) => {
- if (val === 'true') Reflect.set(res, key, true)
- else if (val === 'false') Reflect.set(res, key, false)
- else Reflect.set(res, key, val)
- return res
- }, {})
-
-const setupTransforms = (transforms: KeyTransform[]) => {
- if (!transforms) return
- xfrmHold.clear()
- xfrmDown.clear()
- xfrmUp.clear()
-
- transforms.forEach(({ event, mode, match, transform }) => {
- const nvimMode = normalizeVimMode(mode)
- const fn = Reflect.get(addTransform, event)
- if (!fn) return console.error('can not add key-transform for event:', event)
-
- const transformFn = new Script(transform).runInThisContext()
- const matchObj =
- nvimMode !== VimMode.SomeModeThatIProbablyDontCareAbout
- ? Object.assign(vimscriptObjectToECMA(match), { mode: nvimMode })
- : vimscriptObjectToECMA(match)
-
- if (is.function(fn) && is.function(transformFn)) fn(matchObj, transformFn)
- })
-}
-
-const remapModifier = (from: string, to: string) => remaps.set(from, to)
-
-type Transformer = (input: KeyboardEvent) => KeyboardEvent
-export const xfrmHold = new Map()
-export const xfrmDown = new Map()
-export const xfrmUp = new Map()
-
-const keToStr = (e: KeyShape) =>
- [
- e.key,
- (e.ctrlKey) | 0,
- (e.metaKey) | 0,
- (e.altKey) | 0,
- (e.shiftKey) | 0,
- ].join('')
-
-const defkey = {
- ...new KeyboardEvent('keydown'),
- key: '',
- ctrlKey: false,
- metaKey: false,
- altKey: false,
- shiftKey: false,
-}
-
-const addTransform = {
- hold: (e: any, fn: Transformer) =>
- xfrmHold.set(keToStr({ ...defkey, ...e }), (e) => ({ ...e, ...fn(e) })),
-
- down: (e: any, fn: Transformer) =>
- xfrmDown.set(keToStr({ ...defkey, ...e }), (e) => ({ ...e, ...fn(e) })),
-
- up: (e: any, fn: Transformer) => {
- const before = keToStr({ ...defkey, ...e })
- const now = keToStr({ ...defkey, key: e.key })
- xfrmUp.set(before + now, (e) => ({ ...e, ...fn(e) }))
- },
}
export const stealInput = (onKeyFn: OnKeyFn) => {
@@ -244,54 +139,9 @@ const sendKeys = async (e: KeyboardEvent, inputType: InputType) => {
const keydownHandler = (e: KeyboardEvent) => {
if (!windowHasFocus || !isCapturing) return
- const es = keToStr(e)
- lastDown = es
-
- if (xfrmDown.has(es)) {
- const remapped = xfrmDown.get(holding)!(e)
- sendKeys(remapped, InputType.Down)
- return
- }
-
- if (xfrmHold.has(es)) {
- holding = es
- return
- }
-
- if (xfrmHold.has(holding)) {
- const remapped = xfrmHold.get(holding)!(e)
- sendKeys(remapped, InputType.Down)
- xformed = true
- return
- }
-
sendKeys(e, InputType.Down)
}
-const keyupHandler = (e: KeyboardEvent) => {
- if (!windowHasFocus || !isCapturing) return
-
- // one of the observed ways in which we can have a 'keyup' event without a
- // 'keydown' event is when the window receives focus while a key is already
- // pressed. this will happen with key combos like cmd+tab or alt+tab to
- // switch applications in mac/windows. there is probably no good reason to
- // send the keyup event key to neovim. in fact, this causes issues if we have
- // a xform mapping of cmd -> escape, as it sends an 'esc' key to neovim
- // terminal, thus swallowing the first key after app focus
- if (!lastDown) return
- const es = keToStr(e)
-
- const prevKeyAndThisOne = lastDown + es
- if (xfrmUp.has(prevKeyAndThisOne))
- return sendKeys(xfrmUp.get(prevKeyAndThisOne)!(e), InputType.Up)
-
- if (holding === es) {
- if (!xformed) sendKeys(e, InputType.Up)
- xformed = false
- holding = ''
- }
-}
-
// Need to handle key events from window for GUI elements like the external
// cmdline, so if the key composition textarea isn't focused (which it won't
// be when those elements are in use), handle the event from the window.
@@ -301,19 +151,10 @@ window.addEventListener('keydown', (e) => {
keydownHandler(e)
})
-// Same as above, just for `keyup` event.
-window.addEventListener('keyup', (e) => {
- if (textarea) if (textarea === document.activeElement) return
-
- keyupHandler(e)
-})
-
textarea?.addEventListener('keydown', keydownHandler)
-textarea?.addEventListener('keyup', keyupHandler)
remote.getCurrentWindow().on('focus', () => {
windowHasFocus = true
- resetInputState()
if (shouldClearEscapeOnNextAppFocus) {
// so if i remap 'cmd' down+up -> 'esc' and then hit cmd+tab to switch apps
// while in a terminal buffer, the application captures the 'cmd' (xform to
@@ -335,13 +176,9 @@ remote.getCurrentWindow().on('focus', () => {
remote.getCurrentWindow().on('blur', async () => {
windowHasFocus = false
- resetInputState()
const lastEscapeFromNow = Date.now() - lastEscapeTimestamp
const isTerminalMode = api.nvim.state.mode === VimMode.Terminal
const fixTermEscape = isTerminalMode && lastEscapeFromNow < 25
if (fixTermEscape) shouldClearEscapeOnNextAppFocus = true
})
-
-api.onConfig.inputRemapModifiersDidChange(setupRemapModifiers)
-api.onConfig.inputKeyTransformsDidChange(setupTransforms)
diff --git a/src/core/instance-api.ts b/src/core/instance-api.ts
index 3bf1e045..d362ed8e 100644
--- a/src/core/instance-api.ts
+++ b/src/core/instance-api.ts
@@ -91,11 +91,6 @@ onSwitchVim(async () => {
ee.emit('git.status', gitInfo.status)
ee.emit('git.branch', gitInfo.branch)
ee.emit('nvim.load', true)
-
- const mappings = await instance.request.nvimGetVar('veonim_remap_modifiers')
- ee.emit('input.remap.modifiers', mappings)
- const transforms = await instance.request.nvimGetVar('veonim_key_transforms')
- ee.emit('input.key.transforms', transforms)
})
const getBufferInfo = (): Promise =>
diff --git a/src/neovim/startup.ts b/src/neovim/startup.ts
index a79c5de5..48fe9532 100644
--- a/src/neovim/startup.ts
+++ b/src/neovim/startup.ts
@@ -83,7 +83,7 @@ startup.defineFunc.UivonimCreateHighlights`
hi! link uvnFunction Function
hi! link uvnBuiltin Constant
hi! link uvnKeyword Keyword
- hi! link uvnCursor CursorNormal
+ hi! link uvnCursor Cursor
`
// autocmds in a separate function because chaining autocmds with "|" is bad
diff --git a/src/render/redraw.ts b/src/render/redraw.ts
index 55a40ba5..13dbbcf0 100644
--- a/src/render/redraw.ts
+++ b/src/render/redraw.ts
@@ -7,12 +7,7 @@ import {
getCharIndex,
getUpdatedFontAtlasMaybe,
} from '../render/font-texture-atlas'
-import {
- moveCursor,
- hideCursor,
- showCursor,
- updateCursorChar,
-} from '../core/cursor'
+import { hideCursor, showCursor, moveCursor } from '../core/cursor'
import * as windows from '../windows/window-manager'
import * as dispatch from '../messaging/dispatch'
import { onRedraw, resizeGrid } from '../core/master-control'
@@ -92,7 +87,7 @@ const grid_cursor_goto = ([, [gridId, row, col]]: any) => {
state_cursorVisible = gridId !== 1
if (gridId === 1) return
windows.setActiveGrid(gridId)
- moveCursor(gridId, row, col)
+ moveCursor(row, col)
}
const grid_scroll = ([
@@ -307,21 +302,6 @@ const win_float_pos = (e: any) => {
}
}
-let layoutTimeout: NodeJS.Timeout | undefined
-
-const refreshOrStartLayoutTimer = (winUpdates: boolean) => {
- layoutTimeout = setTimeout(() => {
- renderEvents.messageClearPromptsMaybeHack(state_cursorVisible)
- state_cursorVisible ? showCursor() : hideCursor()
- if (state_cursorVisible) updateCursorChar()
- dispatch.pub('redraw')
- if (!winUpdates) return
-
- windows.disposeInvalidWindows()
- windows.layout()
- }, 10)
-}
-
onRedraw((redrawEvents) => {
// because of circular logic/infinite loop. cmdline_show updates UI, UI makes
// a change in the cmdline, nvim sends redraw again. we cut that stuff out
@@ -386,6 +366,11 @@ onRedraw((redrawEvents) => {
else if (e === 'msg_ruler') renderEvents.msg_ruler(ev)
}
- if (layoutTimeout) clearTimeout(layoutTimeout)
- refreshOrStartLayoutTimer(winUpdates)
+ renderEvents.messageClearPromptsMaybeHack(state_cursorVisible)
+ state_cursorVisible ? showCursor() : hideCursor()
+ dispatch.pub('redraw')
+ if (!winUpdates) return
+
+ windows.disposeInvalidWindows()
+ windows.layout()
})
diff --git a/src/render/webgl-text-bg.ts b/src/render/webgl-text-bg.ts
index 6e8b0acc..693b4ed7 100644
--- a/src/render/webgl-text-bg.ts
+++ b/src/render/webgl-text-bg.ts
@@ -2,12 +2,16 @@ import { getColorAtlas, colors } from '../render/highlight-attributes'
import { WebGL, VarKind } from '../render/webgl-utils'
import { cell } from '../core/workspace'
import { hexToRGB } from '../ui/css'
+import { CursorShape } from '../core/cursor'
export default (webgl: WebGL) => {
const viewport = { x: 0, y: 0, width: 0, height: 0 }
+ let shouldShowCursor = true
+ let cursorShape = 0 /* CursorShape.block */
const program = webgl.setupProgram({
quadVertex: VarKind.Attribute,
+ isCursorTri: VarKind.Attribute,
cellPosition: VarKind.Attribute,
hlid: VarKind.Attribute,
hlidType: VarKind.Uniform,
@@ -15,23 +19,35 @@ export default (webgl: WebGL) => {
colorAtlasResolution: VarKind.Uniform,
colorAtlasTextureId: VarKind.Uniform,
cellSize: VarKind.Uniform,
+ cursorPosition: VarKind.Uniform,
+ cursorColor: VarKind.Uniform,
+ cursorShape: VarKind.Uniform,
+ shouldShowCursor: VarKind.Uniform,
})
program.setVertexShader(
(v) => `#version 300 es
in vec2 ${v.quadVertex};
in vec2 ${v.cellPosition};
+ in float ${v.isCursorTri};
in float ${v.hlid};
+ uniform vec2 ${v.cursorPosition};
uniform vec2 ${v.canvasResolution};
uniform vec2 ${v.colorAtlasResolution};
uniform vec2 ${v.cellSize};
+ uniform vec4 ${v.cursorColor};
+ uniform bool ${v.shouldShowCursor};
+ uniform int ${v.cursorShape};
uniform float ${v.hlidType};
uniform sampler2D ${v.colorAtlasTextureId};
-
out vec4 o_color;
out vec2 o_colorPosition;
void main() {
+ bool isCursorCell = ${v.cursorPosition} == ${v.cellPosition} && ${
+ v.shouldShowCursor
+ };
+
vec2 absolutePixelPosition = ${v.cellPosition} * ${v.cellSize};
vec2 vertexPosition = absolutePixelPosition + ${v.quadVertex};
vec2 posFloat = vertexPosition / ${v.canvasResolution};
@@ -44,7 +60,26 @@ export default (webgl: WebGL) => {
float color_y = ${v.hlidType} * texelSize + 1.0;
vec2 colorPosition = vec2(color_x, color_y) / ${v.colorAtlasResolution};
- o_color = texture(${v.colorAtlasTextureId}, colorPosition);
+ bool condition;
+ ${
+ /*
+ TODO(smolck): I'm almost certain there's a way to do this
+ condition all in one without extra if statements, but my brain is
+ not finding it right now.
+ */ ''
+ }
+ if (${v.cursorShape} == 1) {
+ condition = isCursorCell && isCursorTri == 1.0;
+ } else {
+ condition = isCursorCell;
+ }
+
+ if (condition) {
+ o_color = cursorColor;
+ } else {
+ vec4 textureColor = texture(${v.colorAtlasTextureId}, colorPosition);
+ o_color = textureColor;
+ }
}
`
)
@@ -75,6 +110,10 @@ export default (webgl: WebGL) => {
colorAtlas.width,
colorAtlas.height
)
+ webgl.gl.uniform2f(program.vars.cursorPosition, 0, 0)
+ webgl.gl.uniform4fv(program.vars.cursorColor, [1, 1, 1, 1])
+ // @ts-ignore
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, shouldShowCursor)
// total size of all pointers. chunk size that goes to shader
const wrenderStride = 4 * Float32Array.BYTES_PER_ELEMENT
@@ -98,41 +137,104 @@ export default (webgl: WebGL) => {
},
])
- const quadBuffer = program.setupData({
- pointer: program.vars.quadVertex,
- type: webgl.gl.FLOAT,
- size: 2,
- })
+ const quadBuffer = program.setupData([
+ {
+ pointer: program.vars.quadVertex,
+ type: webgl.gl.FLOAT,
+ size: 2,
+ offset: 0,
+ },
+ {
+ pointer: program.vars.isCursorTri,
+ type: webgl.gl.FLOAT,
+ size: 1,
+ offset: Float32Array.BYTES_PER_ELEMENT * 2 * 12,
+ },
+ ])
+
+ const updateCellSize = (initial = false, cursorSize = 20) => {
+ const w = cell.width
+ const h = cell.height
+ const smallerW = w * (cursorSize / 100.0)
+ const percentH = h * (cursorSize / 100.0)
- const updateCellSize = (initial = false) => {
const next = {
boxes: new Float32Array([
0,
0,
- cell.width,
- cell.height,
+ smallerW,
+ h,
0,
- cell.height,
- cell.width,
+ h,
+
+ smallerW,
+ 0,
+ smallerW,
+ h,
0,
- cell.width,
- cell.height,
0,
+
+ smallerW,
+ 0,
+ w,
+ h,
+ smallerW,
+ h,
+
+ w,
0,
+ w,
+ h,
+ smallerW,
+ 0,
+
+ // TODO(smolck): Better way of doing this? Also, note that the 1's
+ // specify which triangles of the above to color in for the cursor, and the zeroes
+ // which triangles not to color in, *if* the cursor is a line shape. If
+ // it isn't a line shape (atm a block shape), these are ignored.
+ ...Array(6).fill(1),
+ ...Array(6).fill(0),
]),
+ // TODO(smolck): Don't draw double the tris for underliens too, maybe use
+ // a separate buffer for quadVertex somehow or something?
lines: new Float32Array([
+ /* Previous values (for future ref):
+ * 0, cell.height - 1,
+ * cell.width, cell.height,
+ * 0, cell.height,
+ *
+ * cell.width, cell.height - 1,
+ * cell.width, cell.height,
+ * 0, cell.height - 1, */
0,
- cell.height - 1,
- cell.width,
- cell.height,
+ h - 1,
+ smallerW,
+ percentH,
0,
- cell.height,
- cell.width,
- cell.height - 1,
- cell.width,
- cell.height,
+ percentH,
+
+ smallerW,
+ h - 1,
+ smallerW,
+ percentH,
0,
- cell.height - 1,
+ h - 1,
+
+ smallerW,
+ h - 1,
+ w,
+ percentH,
+ smallerW,
+ percentH,
+
+ w,
+ h - 1,
+ w,
+ percentH,
+ smallerW,
+ h - 1,
+
+ ...Array(12).fill(0),
]),
}
@@ -183,15 +285,47 @@ export default (webgl: WebGL) => {
readjustViewportMaybe(x, y, width, height)
wrenderBuffer.setData(buffer)
+ if (shouldShowCursor && cursorShape == 2)
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, 0 /* false */)
// background
quadBuffer.setData(quads.boxes)
webgl.gl.uniform1f(program.vars.hlidType, 0)
- webgl.gl.drawArraysInstanced(webgl.gl.TRIANGLES, 0, 6, buffer.length / 4)
+ webgl.gl.drawArraysInstanced(webgl.gl.TRIANGLES, 0, 12, buffer.length / 4)
+
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, shouldShowCursor ? 1 : 0)
// underlines
quadBuffer.setData(quads.lines)
+
+ // Just want to ignore the cursor logic in the vertex shader for underlines,
+ // so set shouldShowCursor to false, then back to it's previous value after
+ // the draw call.
+ if (shouldShowCursor && cursorShape != 2 /* CursorShape.underline */)
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, 0 /* false */)
+
webgl.gl.uniform1f(program.vars.hlidType, 2)
- webgl.gl.drawArraysInstanced(webgl.gl.TRIANGLES, 0, 6, buffer.length / 4)
+ webgl.gl.drawArraysInstanced(webgl.gl.TRIANGLES, 0, 12, buffer.length / 4)
+
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, shouldShowCursor ? 1 : 0)
+ }
+
+ const showCursor = (enable: boolean) => (
+ (shouldShowCursor = enable),
+ // @ts-ignore
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, enable)
+ )
+
+ const updateCursorColor = (color: [number, number, number]) => {
+ webgl.gl.uniform4fv(program.vars.cursorColor, [...color, 1])
+ }
+
+ const updateCursorShape = (shape: CursorShape) => {
+ cursorShape = shape
+ webgl.gl.uniform1i(program.vars.cursorShape, shape)
+ }
+
+ const updateCursorPosition = (row: number, col: number) => {
+ webgl.gl.uniform2f(program.vars.cursorPosition, col, row)
}
const updateColorAtlas = (colorAtlas: HTMLCanvasElement) => {
@@ -222,5 +356,16 @@ export default (webgl: WebGL) => {
webgl.gl.clear(webgl.gl.COLOR_BUFFER_BIT)
}
- return { clear, clearAll, render, resize, updateColorAtlas, updateCellSize }
+ return {
+ clear,
+ clearAll,
+ render,
+ resize,
+ updateColorAtlas,
+ updateCellSize,
+ showCursor,
+ updateCursorPosition,
+ updateCursorShape,
+ updateCursorColor,
+ }
}
diff --git a/src/render/webgl-text-fg.ts b/src/render/webgl-text-fg.ts
index fe79b017..643c4f9b 100644
--- a/src/render/webgl-text-fg.ts
+++ b/src/render/webgl-text-fg.ts
@@ -2,6 +2,7 @@ import { getColorAtlas } from '../render/highlight-attributes'
import generateFontAtlas from '../render/font-texture-atlas'
import { WebGL, VarKind } from '../render/webgl-utils'
import { cell } from '../core/workspace'
+import { CursorShape } from '../core/cursor'
export default (webgl: WebGL) => {
const viewport = { x: 0, y: 0, width: 0, height: 0 }
@@ -18,6 +19,11 @@ export default (webgl: WebGL) => {
colorAtlasTextureId: VarKind.Uniform,
cellSize: VarKind.Uniform,
cellPadding: VarKind.Uniform,
+
+ shouldShowCursor: VarKind.Uniform,
+ cursorPosition: VarKind.Uniform,
+ cursorShape: VarKind.Uniform,
+ cursorColor: VarKind.Uniform,
})
program.setVertexShader(
@@ -33,10 +39,17 @@ export default (webgl: WebGL) => {
uniform vec2 ${v.cellPadding};
uniform sampler2D ${v.colorAtlasTextureId};
+ uniform vec4 ${v.cursorColor};
+ uniform vec2 ${v.cursorPosition};
+ uniform bool ${v.shouldShowCursor};
+ uniform int ${v.cursorShape};
+
out vec2 o_glyphPosition;
out vec4 o_color;
void main() {
+ bool isCursorCell = ${v.cursorPosition} == ${v.cellPosition} && ${v.shouldShowCursor};
+
vec2 absolutePixelPosition = ${v.cellPosition} * ${v.cellSize};
vec2 vertexPosition = absolutePixelPosition + ${v.quadVertex} + ${v.cellPadding};
vec2 posFloat = vertexPosition / ${v.canvasResolution};
@@ -53,7 +66,13 @@ export default (webgl: WebGL) => {
float color_y = 1.0 * texelSize + 1.0;
vec2 colorPosition = vec2(color_x, color_y) / ${v.colorAtlasResolution};
- o_color = texture(${v.colorAtlasTextureId}, colorPosition);
+ vec4 textureColor = texture(${v.colorAtlasTextureId}, colorPosition);
+
+ if (isCursorCell && cursorShape == 0) {
+ o_color = ${v.cursorColor};
+ } else {
+ o_color = textureColor;
+ }
}
`
)
@@ -104,6 +123,12 @@ export default (webgl: WebGL) => {
colorAtlas.height
)
+ webgl.gl.uniform4fv(program.vars.cursorColor, [0, 0, 0, 1])
+ webgl.gl.uniform2f(program.vars.cursorPosition, 0, 0)
+ webgl.gl.uniform1i(program.vars.cursorShape, 0) // CursorShape.block = 0
+ // @ts-ignore
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, true)
+
// total size of all pointers. chunk size that goes to shader
const wrenderStride = 4 * Float32Array.BYTES_PER_ELEMENT
@@ -231,6 +256,22 @@ export default (webgl: WebGL) => {
webgl.gl.uniform2f(program.vars.cellPadding, 0, cell.padding)
}
+ const showCursor = (enable: boolean) =>
+ // @ts-ignore
+ webgl.gl.uniform1i(program.vars.shouldShowCursor, enable)
+
+ const updateCursorColor = (color: [number, number, number]) => {
+ webgl.gl.uniform4fv(program.vars.cursorColor, [...color, 1])
+ }
+
+ const updateCursorShape = (shape: CursorShape) => {
+ webgl.gl.uniform1i(program.vars.cursorShape, shape)
+ }
+
+ const updateCursorPosition = (row: number, col: number) => {
+ webgl.gl.uniform2f(program.vars.cursorPosition, col, row)
+ }
+
const updateColorAtlas = (colorAtlas: HTMLCanvasElement) => {
webgl.loadCanvasTexture(colorAtlas, webgl.gl.TEXTURE1)
webgl.gl.uniform2f(
@@ -263,5 +304,9 @@ export default (webgl: WebGL) => {
updateFontAtlas,
updateColorAtlas,
updateCellSize,
+ updateCursorPosition,
+ updateCursorShape,
+ updateCursorColor,
+ showCursor,
}
}
diff --git a/src/render/webgl.ts b/src/render/webgl.ts
index 41eb3a71..d689c655 100644
--- a/src/render/webgl.ts
+++ b/src/render/webgl.ts
@@ -3,6 +3,8 @@ import CreateWebGL from '../render/webgl-utils'
import { cell } from '../core/workspace'
import TextFG from '../render/webgl-text-fg'
import TextBG from '../render/webgl-text-bg'
+import { cursor as cursorState, CursorShape } from '../core/cursor'
+import { getActiveGridId } from '../windows/window-manager'
export interface WebGLView {
resize: (rows: number, cols: number) => void
@@ -17,6 +19,7 @@ export interface WebGLView {
getGridLine: (row: number) => Float32Array
getGridBuffer: () => Float32Array
getBuffer: () => Float32Array
+ updateGridId: (gridId: number) => void
}
const nutella = () => {
@@ -31,6 +34,29 @@ const nutella = () => {
textFGRenderer.resize(width, height)
}
+ const showCursor = (enable: boolean) => {
+ textBGRenderer.showCursor(enable)
+ textFGRenderer.showCursor(enable)
+ }
+
+ const updateCursorShape = (shape: CursorShape) => {
+ textBGRenderer.updateCursorShape(shape)
+ // TODO(smolck): If cursor size changes need to update cells . . .
+ textBGRenderer.updateCellSize(false, cursorState.size)
+
+ textFGRenderer.updateCursorShape(shape)
+ }
+
+ const updateCursorColor = (r: number, g: number, b: number) => {
+ textBGRenderer.updateCursorColor([r, g, b])
+ textFGRenderer.updateCursorColor([1.0 - r, 1.0 - g, 1.0 - b])
+ }
+
+ const updateCursorPosition = (row: number, col: number) => {
+ textBGRenderer.updateCursorPosition(row, col)
+ textFGRenderer.updateCursorPosition(row, col)
+ }
+
const updateFontAtlas = (fontAtlas: HTMLCanvasElement) => {
textFGRenderer.updateFontAtlas(fontAtlas)
}
@@ -50,12 +76,15 @@ const nutella = () => {
textFGRenderer.clearAll()
}
- const createView = (): WebGLView => {
+ const createView = (initialGridId: number): WebGLView => {
+ let gridId = initialGridId
const viewport = { x: 0, y: 0, width: 0, height: 0 }
const gridSize = { rows: 0, cols: 0 }
const gridBuffer = CreateWebGLBuffer()
let dataBuffer = new Float32Array()
+ const updateGridId = (newGridId: number) => (gridId = newGridId)
+
const resize = (rows: number, cols: number) => {
const width = cols * cell.width
const height = rows * cell.height
@@ -85,15 +114,23 @@ const nutella = () => {
const render = (elements: number) => {
const buffer = dataBuffer.subarray(0, elements)
const { x, y, width, height } = viewport
+
+ const doHacks = gridId !== getActiveGridId() && cursorState.visible
+ if (doHacks) showCursor(false)
textBGRenderer.render(buffer, x, y, width, height)
textFGRenderer.render(buffer, x, y, width, height)
+ if (doHacks) showCursor(true)
}
const renderGridBuffer = () => {
const { x, y, width, height } = viewport
const buffer = gridBuffer.getBuffer()
+
+ const doHacks = gridId !== getActiveGridId() && cursorState.visible
+ if (doHacks) showCursor(false)
textBGRenderer.render(buffer, x, y, width, height)
textFGRenderer.render(buffer, x, y, width, height)
+ if (doHacks) showCursor(true)
}
const clear = () => {
@@ -129,6 +166,7 @@ const nutella = () => {
moveRegionDown,
clearGridBuffer,
renderGridBuffer,
+ updateGridId,
getGridCell: gridBuffer.getCell,
getGridLine: gridBuffer.getLine,
getGridBuffer: gridBuffer.getBuffer,
@@ -143,6 +181,10 @@ const nutella = () => {
updateCellSize,
updateFontAtlas,
updateColorAtlas,
+ updateCursorShape,
+ updateCursorPosition,
+ updateCursorColor,
+ showCursor,
foregroundElement: foregroundGL.canvasElement,
backgroundElement: backgroundGL.canvasElement,
}
diff --git a/src/windows/window-manager.ts b/src/windows/window-manager.ts
index be87fe99..036ff991 100644
--- a/src/windows/window-manager.ts
+++ b/src/windows/window-manager.ts
@@ -74,7 +74,9 @@ export const calculateGlobalOffset = (anchorWin: Window, float: Window) => {
}
}
-export const createWebGLView = () => webgl.createView()
+export const createWebGLView = (gridId: number) => webgl.createView(gridId)
+
+export const getActiveGridId = () => state.activeInstanceGrid
export const setActiveGrid = (id: number) =>
Object.assign(state, {
@@ -114,6 +116,7 @@ export const set = (
visible: true,
id: wid,
gridId: gid,
+ gridIdNumber: gridId,
})
if (!windows.has(gid)) windows.set(gid, win)
@@ -203,7 +206,7 @@ export const layout = () => {
state.activeGrid &&
requestAnimationFrame(() => {
if (!windows.has(state.activeGrid)) return
- moveCursor(state.activeInstanceGrid, cursor.row, cursor.col)
+ moveCursor(cursor.row, cursor.col)
})
}
diff --git a/src/windows/window.ts b/src/windows/window.ts
index 0649a1d2..d738d70b 100644
--- a/src/windows/window.ts
+++ b/src/windows/window.ts
@@ -14,6 +14,7 @@ import { makel } from '../ui/vanilla'
export interface WindowInfo {
id: string
gridId: string
+ gridIdNumber: number
row: number
col: number
width: number
@@ -116,6 +117,7 @@ export default () => {
const wininfo: WindowInfo = {
id: '0',
gridId: '0',
+ gridIdNumber: 0,
row: 0,
col: 0,
width: 0,
@@ -125,7 +127,7 @@ export default () => {
anchor: '',
}
const layout = { x: 0, y: 0, width: 0, height: 0 }
- const webgl = createWebGLView()
+ const webgl = createWebGLView(0)
const container = makel({
flexFlow: 'column',
@@ -229,6 +231,7 @@ export default () => {
container.id = `${info.id}`
container.setAttribute('gridid', info.gridId)
+ webgl.updateGridId(info.gridIdNumber)
Object.assign(wininfo, info)
}