@@ -17,6 +17,7 @@ import type {
1717 SurfaceSchemaLedsConfig ,
1818} from './Model/Surfaces.js'
1919import type { UserConfigGridSize } from './Model/UserConfigModel.js'
20+ import { estimateFace } from './SurfaceLayoutEstimate.js'
2021
2122/**
2223 * The style a control is drawn with: its named preset if it has one, otherwise the required default preset.
@@ -217,41 +218,19 @@ export interface SurfaceGridPlacement {
217218 panelGridSize : GridSize
218219}
219220
220- /** The gap left between controls, as a fraction of the smallest control. Stands in for the bezel of a real device. */
221- const CONTROL_GAP_RATIO = 0.14
222-
223221/** How round a control's corners are, as a fraction of its shorter side. A Stream Deck key, near enough. */
224222const CONTROL_CORNER_RATIO = 0.12
225223
226224/** The size of a control which declares no bitmap, when the surface has nothing else to go on */
227225const NOMINAL_CONTROL_SIZE = 72
228226
229- /**
230- * How big a control is on the face, in layout units.
231- *
232- * The one guess in here: the schema says how many pixels a control is drawn with, nothing about its physical size,
233- * so this treats a pixel as a fixed amount of face - i.e. it assumes every control has the same pixel density.
234- * Real devices don't (a Stream Deck + draws its touch strip at more pixels per mm than its keys), so a strip comes
235- * out wider and taller relative to a key than it really is. The shapes and ordering are right; the proportions are
236- * only as good as that assumption. Best available until a layout can state a physical size per control.
237- */
238- function estimateControlSize (
239- bitmap : SurfaceLayoutBitmapSize | undefined ,
240- fallbackSide : number
241- ) : { width : number ; height : number } {
242- if ( ! bitmap ) return { width : fallbackSide , height : fallbackSide }
243-
244- return { width : bitmap . w , height : bitmap . h }
245- }
246-
247227/**
248228 * Work out where a surface's controls are, and which buttons they drive.
249229 *
250230 * Returns null when the layout describes no controls at all.
251231 *
252- * The schema gives a row and column per control and a bitmap size per style, so positions are derived: each column
253- * is as wide as the widest control in it, each row as tall as the tallest. A control smaller than its track sits
254- * centred in it. Sizes are a guess with a known error - see `estimateControlSize`.
232+ * Drawn exactly from the surface's appearance when it described one; otherwise estimated from the layout, see
233+ * `estimateFace`.
255234 */
256235export function resolveSurfaceView (
257236 layout : SurfaceSchemaLayoutDefinition ,
@@ -282,7 +261,8 @@ export function resolveSurfaceView(
282261 /** Relative to the surface's own origin, before the offset onto the grid */
283262 gridX : number
284263 gridY : number
285- size : { width : number ; height : number }
264+ /** How big its display is on the face, or null when it has none */
265+ display : { width : number ; height : number } | null
286266 feedback : SurfaceControlFeedback
287267 leds : SurfaceSchemaLedsConfig | null
288268 aspectRatio : AspectRatio | null
@@ -296,15 +276,15 @@ export function resolveSurfaceView(
296276
297277 const preset = resolveControlStylePreset ( layout , control )
298278 const bitmap = preset . bitmap
299- const natural = estimateControlSize ( bitmap , fallbackSide )
279+ const display = bitmap ? { width : bitmap . w , height : bitmap . h } : null
300280
301281 placed . push ( {
302282 id,
303283 cell : { row : gridY + placement . offset . rows , column : gridX + placement . offset . columns } ,
304284 gridX,
305285 gridY,
306286 // A surface on its side presents its controls on their side too
307- size : quarterTurn ? { width : natural . height , height : natural . width } : natural ,
287+ display : display && quarterTurn ? { width : display . height , height : display . width } : display ,
308288 feedback : controlFeedback ( preset ) ,
309289 leds : preset . leds ?? null ,
310290 aspectRatio : bitmap ? reduceAspectRatio ( bitmap . w , bitmap . h ) : null ,
@@ -340,50 +320,29 @@ export function resolveSurfaceView(
340320 }
341321 }
342322
343- // Empty tracks between occupied ones keep their room: controls numbered 0, 3, 5 left space for what's between,
344- // and closing it up would draw the device narrower than it is
345- const columnWidths = trackSizes (
346- placed . map ( ( control ) => ( { track : control . gridX , size : control . size . width } ) ) ,
347- fallbackSide
348- )
349- const rowHeights = trackSizes (
350- placed . map ( ( control ) => ( { track : control . gridY , size : control . size . height } ) ) ,
323+ const face = estimateFace (
324+ placed . map ( ( control ) => ( {
325+ track : { x : control . gridX , y : control . gridY } ,
326+ display : control . display ,
327+ ring : control . leds ?. mode === 'full-ring' ,
328+ } ) ) ,
351329 fallbackSide
352330 )
353331
354- const gap = Math . round ( Math . min ( ...[ ...columnWidths . values ( ) , ...rowHeights . values ( ) ] ) * CONTROL_GAP_RATIO )
355-
356- const columnOffsets = trackOffsets ( columnWidths , gap )
357- const rowOffsets = trackOffsets ( rowHeights , gap )
358-
359- const controls : ResolvedSurfaceControl [ ] = placed . map ( ( control ) => {
360- const trackWidth = columnWidths . get ( control . gridX ) ?? control . size . width
361- const trackHeight = rowHeights . get ( control . gridY ) ?? control . size . height
362-
363- return {
364- id : control . id ,
365- cell : control . cell ,
366- bounds : {
367- // Centred in the room the device leaves for it, rather than stretched to fill it
368- x : ( columnOffsets . get ( control . gridX ) ?? 0 ) + ( trackWidth - control . size . width ) / 2 ,
369- y : ( rowOffsets . get ( control . gridY ) ?? 0 ) + ( trackHeight - control . size . height ) / 2 ,
370- width : control . size . width ,
371- height : control . size . height ,
372- } ,
373- shape : { type : 'rect' , cornerRadiusRatio : CONTROL_CORNER_RATIO } ,
374- feedback : control . feedback ,
375- leds : control . leds ,
376- aspectRatio : control . aspectRatio ,
377- renderSize : control . renderSize ,
378- }
379- } )
332+ const controls : ResolvedSurfaceControl [ ] = placed . map ( ( control , index ) => ( {
333+ id : control . id ,
334+ cell : control . cell ,
335+ bounds : face . bounds [ index ] ,
336+ shape : { type : 'rect' , cornerRadiusRatio : CONTROL_CORNER_RATIO } ,
337+ feedback : control . feedback ,
338+ leds : control . leds ,
339+ aspectRatio : control . aspectRatio ,
340+ renderSize : control . renderSize ,
341+ } ) )
380342
381343 return {
382344 controls,
383- extent : {
384- width : trackExtent ( columnWidths , gap ) ,
385- height : trackExtent ( rowHeights , gap ) ,
386- } ,
345+ extent : face . extent ,
387346 gridBounds : gridBoundsFromCells ( controls ) ,
388347 // No described face; the plain ground shows between the controls
389348 body : null ,
@@ -413,47 +372,3 @@ function gridBoundsFromCells(controls: readonly ResolvedSurfaceControl[]): UserC
413372 maxColumn : Math . max ( ...controls . map ( ( control ) => control . cell . column ) ) ,
414373 }
415374}
416-
417- /**
418- * How big each track has to be to hold the largest thing in it. Covers every track from first to last, so an empty
419- * one still takes up the room the surface left for it.
420- */
421- function trackSizes ( entries : readonly { track : number ; size : number } [ ] , emptySize : number ) : Map < number , number > {
422- const sizes = new Map < number , number > ( )
423-
424- const tracks = entries . map ( ( entry ) => entry . track )
425- for ( let track = Math . min ( ...tracks ) ; track <= Math . max ( ...tracks ) ; track ++ ) {
426- sizes . set ( track , 0 )
427- }
428-
429- for ( const { track, size } of entries ) {
430- sizes . set ( track , Math . max ( sizes . get ( track ) ?? 0 , size ) )
431- }
432-
433- for ( const [ track , size ] of sizes ) {
434- if ( size === 0 ) sizes . set ( track , emptySize )
435- }
436-
437- return sizes
438- }
439-
440- /** Where each track starts, laid out in order with a gap between them */
441- function trackOffsets ( sizes : ReadonlyMap < number , number > , gap : number ) : Map < number , number > {
442- const offsets = new Map < number , number > ( )
443-
444- let position = 0
445- for ( const track of [ ...sizes . keys ( ) ] . sort ( ( a , b ) => a - b ) ) {
446- offsets . set ( track , position )
447- position += ( sizes . get ( track ) ?? 0 ) + gap
448- }
449-
450- return offsets
451- }
452-
453- /** How far the tracks reach in total, without a trailing gap */
454- function trackExtent ( sizes : ReadonlyMap < number , number > , gap : number ) : number {
455- let extent = 0
456- for ( const size of sizes . values ( ) ) extent += size + gap
457-
458- return Math . max ( 0 , extent - gap )
459- }
0 commit comments