Skip to content

Commit

Permalink
feat: customisable emulator size
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Jul 9, 2023
1 parent 82d9625 commit fdc6122
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 71 deletions.
35 changes: 25 additions & 10 deletions lib/Surface/Handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,7 @@ class SurfaceHandler extends CoreBase {
this.currentPage = 1 // The current page of the device

// Fill in max offsets
const keysPerRow = this.panel.info.keysPerRow || 0
const keysTotal = this.panel.info.keysTotal || 0
if (keysPerRow && keysTotal) {
const maxRows = Math.ceil(global.MAX_BUTTONS / global.MAX_BUTTONS_PER_ROW)
this.panelInfo.xOffsetMax = Math.max(Math.floor(global.MAX_BUTTONS_PER_ROW - keysPerRow), 0)
this.panelInfo.yOffsetMax = Math.max(Math.floor(maxRows - Math.ceil(keysTotal / keysPerRow)), 0)
}
this.updateMaxOffset()

{
const rawConfig = this.db.getKey('deviceconfig', {})
Expand All @@ -149,6 +143,9 @@ class SurfaceHandler extends CoreBase {

if (!this.panelconfig.config) {
this.panelconfig.config = cloneDeep(SurfaceHandler.PanelDefaults)
if (typeof this.panel.getDefaultConfig === 'function') {
Object.assign(this.panelconfig.config, this.panel.getDefaultConfig())
}
}

if (this.panelconfig.config.xOffset === undefined || this.panelconfig.config.yOffset === undefined) {
Expand Down Expand Up @@ -187,6 +184,7 @@ class SurfaceHandler extends CoreBase {
this.panel.on('click', this.#onDeviceClick.bind(this))
this.panel.on('rotate', this.#onDeviceRotate.bind(this))
this.panel.on('remove', this.#onDeviceRemove.bind(this))
this.panel.on('resized', this.#onDeviceResized.bind(this))

// subscribe to some xkeys specific events
this.panel.on('xkeys-subscribePage', this.#onXkeysSubscribePages.bind(this))
Expand All @@ -206,6 +204,16 @@ class SurfaceHandler extends CoreBase {
})
}

updateMaxOffset() {
const keysPerRow = this.panel.info.keysPerRow || 0
const keysTotal = this.panel.info.keysTotal || 0
if (keysPerRow && keysTotal) {
const maxRows = Math.ceil(global.MAX_BUTTONS / global.MAX_BUTTONS_PER_ROW)
this.panelInfo.xOffsetMax = Math.max(Math.floor(global.MAX_BUTTONS_PER_ROW - keysPerRow), 0)
this.panelInfo.yOffsetMax = Math.max(Math.floor(maxRows - Math.ceil(keysTotal / keysPerRow)), 0)
}
}

get deviceId() {
return this.panel.info.deviceId
}
Expand Down Expand Up @@ -346,9 +354,16 @@ class SurfaceHandler extends CoreBase {
}

#onDeviceRemove() {
if (this.panel) {
this.surfaces.removeDevice(this.panel.info.devicepath)
}
if (!this.panel) return
this.surfaces.removeDevice(this.panel.info.devicepath)
}

#onDeviceResized() {
if (!this.panel) return

this.drawPage()

this.updateMaxOffset()
}

#onDeviceClick(x, y, pressed, pageOffset) {
Expand Down
77 changes: 53 additions & 24 deletions lib/Surface/IP/ElgatoEmulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { cloneDeep } from 'lodash-es'
import LogController from '../../Log/Controller.js'
import jsonPatch from 'fast-json-patch'
import debounceFn from 'debounce-fn'
import { xyToOldBankIndex } from '../../Shared/ControlId.js'

export function EmulatorRoom(id) {
return `emulator:${id}`
Expand Down Expand Up @@ -68,21 +67,15 @@ class SurfaceIPElgatoEmulator extends EventEmitter {
this.info = {
type: 'Emulator',
devicepath: `emulator:${emulatorId}`,
configFields: ['emulator_control_enable', 'emulator_prompt_fullscreen'],
keysPerRow: 8,
keysTotal: 32,
configFields: ['emulator_control_enable', 'emulator_prompt_fullscreen', 'emulator_size'],
keysPerRow: 8, // Overridden later
keysTotal: 32, // Overridden later
deviceId: `emulator:${emulatorId}`,
}

this.logger.debug('Adding Elgato Streamdeck Emulator')

this.imageCache = {}
for (let y = 0; y < global.MAX_BUTTONS_PER_COL; ++y) {
this.imageCache[y] = {}
for (let x = 0; x < global.MAX_BUTTONS_PER_ROW; ++x) {
this.imageCache[y][x] = null
}
}
}

setupClient(client) {
Expand All @@ -91,7 +84,26 @@ class SurfaceIPElgatoEmulator extends EventEmitter {
return this.#lastSentConfigJson
}

getDefaultConfig() {
return {
emulator_control_enable: false,
emulator_prompt_fullscreen: false,

emulator_columns: 8,
emulator_rows: 4,
}
}

setConfig(config) {
// Populate some defaults
if (!config.emulator_columns) config.emulator_columns = this.getDefaultConfig().emulator_columns
if (!config.emulator_rows) config.emulator_rows = this.getDefaultConfig().emulator_rows

// Ensure info is accurate
this.info.keysPerRow = config.emulator_columns
this.info.keysTotal = config.emulator_columns * config.emulator_rows

// Send config to clients
const roomName = EmulatorRoom(this.id)
if (this.io.countRoomMembers(roomName) > 0) {
const patch = jsonPatch.compare(this.#lastSentConfigJson || {}, config || {})
Expand All @@ -100,43 +112,60 @@ class SurfaceIPElgatoEmulator extends EventEmitter {
}
}

// Handle resize
if (
config.emulator_columns !== this.#lastSentConfigJson.emulator_columns ||
config.emulator_rows !== this.#lastSentConfigJson.emulator_rows
) {
// Clear the cache to ensure no bleed
this.imageCache = {}

for (let y = 0; y < this.#lastSentConfigJson.emulator_rows; y++) {
for (let x = 0; x < this.#lastSentConfigJson.emulator_columns; x++) {
this.#trackChanged(x, y)
}
}

setImmediate(() => {
// Trigger the redraw after this call has completed
this.emit('resized')
})
}

this.#lastSentConfigJson = cloneDeep(config)
}

quit() {}

draw(x, y, render) {
if (x < 0 || y < 0 || x >= this.#lastSentConfigJson.emulator_columns || y >= this.#lastSentConfigJson.emulator_rows)
return true

const dataUrl = render.asDataUrl
if (!dataUrl) {
this.logger.verbose('draw call had no data-url')
return false
}

// Temporarily use this to validate that the key is usable by the emulator
const key = xyToOldBankIndex(x, y)
if (key !== null) {
if (!this.imageCache[y]) this.imageCache[y] = {}
this.imageCache[y][x] = dataUrl || null
if (!this.imageCache[y]) this.imageCache[y] = {}
this.imageCache[y][x] = dataUrl || null

this.#pendingBufferUpdates.set(`${x}/${y}`, [x, y])
this.#emitChanged()
}
this.#trackChanged(x, y)
this.#emitChanged()

return true
}

#trackChanged(x, y) {
this.#pendingBufferUpdates.set(`${x}/${y}`, [x, y])
}

clearDeck() {
this.logger.silly('elgato.prototype.clearDeck()')

// clear all images
this.imageCache = {}
for (let y = 0; y < global.MAX_BUTTONS_PER_COL; ++y) {
if (!this.imageCache[y]) this.imageCache[y] = {}
for (let x = 0; x < global.MAX_BUTTONS_PER_ROW; ++x) {
this.imageCache[y][x] = null
}
}

this.io.emitToRoom(EmulatorRoom(this.id), 'emulator:images', {})
}
}
Expand Down
2 changes: 1 addition & 1 deletion webui/src/Components/ButtonPreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const ButtonPreview = React.memo(function (props) {
backgroundRepeat: 'no-repeat',
}}
>
<img width={72} height={72} src={props.preview ?? BlackImage} alt={props.alt} title={props.title} />
<img width={72} height={72} src={props.preview || BlackImage} alt={props.alt} title={props.title} />
</div>
</div>
)
Expand Down
40 changes: 10 additions & 30 deletions webui/src/Emulator/Emulator.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { nanoid } from 'nanoid'
import { useParams } from 'react-router-dom'
import { dsanMastercueKeymap, keyboardKeymap, logitecKeymap } from './Keymaps'
import { ButtonPreview } from '../Components/ButtonPreview'
import { MAX_COLS, MAX_ROWS } from '../Constants'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCancel, faExpand } from '@fortawesome/free-solid-svg-icons'
import { formatLocation } from '@companion/shared/ControlId'
Expand Down Expand Up @@ -68,6 +67,7 @@ export function Emulator() {

useEffect(() => {
const updateImages = (newImages) => {
console.log('new images', newImages)
setImageCache((old) => {
if (Array.isArray(newImages)) {
const res = { ...old }
Expand Down Expand Up @@ -172,7 +172,12 @@ export function Emulator() {
<>
<ConfigurePanel config={config} />

<CyclePages imageCache={imageCache} setKeyDown={setKeyDown} />
<CyclePages
imageCache={imageCache}
setKeyDown={setKeyDown}
columns={config.emulator_columns}
rows={config.emulator_rows}
/>
</>
) : (
<CRow>
Expand Down Expand Up @@ -233,29 +238,7 @@ function ConfigurePanel({ config }) {
// return Math.min(Math.max(0, val), max)
// }

function CyclePages({ imageCache, setKeyDown }) {
// const goPrevPage = useCallback(() => {
// // if (currentIndex <= 0) {
// // if (loop) {
// // setCurrentIndex(orderedPages.length - 1)
// // }
// // } else {
// // setCurrentIndex(currentIndex - 1)
// // }
// }, [])
// const goNextPage = useCallback(() => {
// // if (currentIndex >= orderedPages.length - 1) {
// // if (loop) {
// // setCurrentIndex(0)
// // }
// // } else {
// // setCurrentIndex(currentIndex + 1)
// // }
// }, [])
// const goFirstPage = useCallback(() => {
// // setCurrentIndex(0)
// }, [])

function CyclePages({ imageCache, setKeyDown, columns, rows }) {
const bankClick = useCallback(
(location, pressed) => {
if (pressed) {
Expand All @@ -267,9 +250,6 @@ function CyclePages({ imageCache, setKeyDown }) {
[setKeyDown]
)

const cols = 8
const rows = 4

return (
<CRow className="flex-grow-1">
<div className="cycle-layout">
Expand All @@ -293,12 +273,12 @@ function CyclePages({ imageCache, setKeyDown }) {
</div>
<div className="bankgrid">
{' '}
{Array(Math.min(MAX_ROWS, rows))
{Array(rows)
.fill(0)
.map((_, y) => {
return (
<CCol key={y} sm={12} className="pagebank-row">
{Array(Math.min(MAX_COLS, cols))
{Array(columns)
.fill(0)
.map((_2, x) => {
return (
Expand Down
53 changes: 47 additions & 6 deletions webui/src/Surfaces.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { nanoid } from 'nanoid'
import { TextInputField } from './Components/TextInputField'
import { useMemo } from 'react'
import { GenericConfirmModal } from './Components/GenericConfirmModal'
import { MAX_COLS, MAX_ROWS } from './Constants'

export const SurfacesPage = memo(function SurfacesPage() {
const socket = useContext(SocketContext)
Expand Down Expand Up @@ -357,6 +358,15 @@ const SurfaceEditModal = forwardRef(function SurfaceEditModal(_props, ref) {
[socket, deviceInfo?.id]
)

const maxHorizontalOffset =
deviceInfo?.configFields?.includes('emulator_size') && deviceConfig
? MAX_COLS - deviceConfig.emulator_columns
: deviceConfigInfo?.xOffsetMax
const maxVerticalOffset =
deviceInfo?.configFields?.includes('emulator_size') && deviceConfig
? MAX_ROWS - deviceConfig.emulator_rows
: deviceConfigInfo?.yOffsetMax

return (
<CModal show={show} onClose={doClose} onClosed={onClosed}>
<CModalHeader closeButton>
Expand Down Expand Up @@ -394,29 +404,60 @@ const SurfaceEditModal = forwardRef(function SurfaceEditModal(_props, ref) {
/>
<span>{deviceConfig.page}</span>
</CFormGroup>
{deviceConfigInfo.xOffsetMax > 0 && (
{deviceInfo.configFields?.includes('emulator_size') && (
<>
<CFormGroup>
<CLabel htmlFor="page">Row count</CLabel>
<CInput
name="emulator_rows"
type="range"
min={1}
max={MAX_ROWS}
step={1}
value={deviceConfig.emulator_rows}
onChange={(e) => updateConfig('emulator_rows', parseInt(e.currentTarget.value))}
/>
<span>{deviceConfig.emulator_rows}</span>
</CFormGroup>
<CFormGroup>
<CLabel htmlFor="page">Column count</CLabel>
<CInput
name="emulator_columns"
type="range"
min={1}
max={MAX_COLS}
step={1}
value={deviceConfig.emulator_columns}
onChange={(e) => updateConfig('emulator_columns', parseInt(e.currentTarget.value))}
/>
<span>{deviceConfig.emulator_columns}</span>
</CFormGroup>
</>
)}

{maxHorizontalOffset > 0 && (
<CFormGroup>
<CLabel htmlFor="page">X Offset in grid</CLabel>
<CLabel htmlFor="page">Horizontal Offset in grid</CLabel>
<CInput
name="page"
type="range"
min={0}
max={deviceConfigInfo.xOffsetMax}
max={maxHorizontalOffset}
step={1}
value={deviceConfig.xOffset}
onChange={(e) => updateConfig('xOffset', parseInt(e.currentTarget.value))}
/>
<span>{deviceConfig.xOffset}</span>
</CFormGroup>
)}
{deviceConfigInfo.yOffsetMax > 0 && (
{maxVerticalOffset > 0 && (
<CFormGroup>
<CLabel htmlFor="page">Y Offset in grid</CLabel>
<CLabel htmlFor="page">Vertical Offset in grid</CLabel>
<CInput
name="page"
type="range"
min={0}
max={deviceConfigInfo.yOffsetMax}
max={maxVerticalOffset}
step={1}
value={deviceConfig.yOffset}
onChange={(e) => updateConfig('yOffset', parseInt(e.currentTarget.value))}
Expand Down

0 comments on commit fdc6122

Please sign in to comment.