@@ -274,6 +274,10 @@ private static class LayoutParser {
274274 private final Deque <Context > contextStack = new ArrayDeque <>();
275275 private final Path skinDirectory ;
276276 private final Path layoutParent ;
277+ private Integer baseDisplayX ;
278+ private Integer baseDisplayY ;
279+ private Integer baseDisplayWidth ;
280+ private Integer baseDisplayHeight ;
277281
278282 LayoutParser (Path layoutFile , Path skinDirectory ) {
279283 this .skinDirectory = skinDirectory ;
@@ -296,10 +300,10 @@ LayoutInfo parse(String text) {
296300 }
297301 }
298302 OrientationInfo portrait = builders .containsKey (OrientationType .PORTRAIT )
299- ? builders .get (OrientationType .PORTRAIT ).build (OrientationType .PORTRAIT )
303+ ? builders .get (OrientationType .PORTRAIT ).build (OrientationType .PORTRAIT , this )
300304 : null ;
301305 OrientationInfo landscape = builders .containsKey (OrientationType .LANDSCAPE )
302- ? builders .get (OrientationType .LANDSCAPE ).build (OrientationType .LANDSCAPE )
306+ ? builders .get (OrientationType .LANDSCAPE ).build (OrientationType .LANDSCAPE , this )
303307 : null ;
304308 return new LayoutInfo (portrait , landscape );
305309 }
@@ -310,25 +314,49 @@ private void handleKeyValue(String line) {
310314 return ;
311315 }
312316 OrientationType orientation = findCurrentOrientation ();
313- if (orientation == null ) {
314- return ;
315- }
316- OrientationInfoBuilder builder = builders .computeIfAbsent (orientation , o -> new OrientationInfoBuilder ());
317+
317318 String [] parts = splitKeyValue (line );
318319 if (parts == null ) {
319320 return ;
320321 }
321322 String key = parts [0 ];
322323 String value = unquote (parts [1 ]);
324+
325+ if (orientation == null && isInDeviceDisplayContext ()) {
326+ switch (key .toLowerCase (Locale .ROOT )) {
327+ case "x" -> baseDisplayX = parseInt (value );
328+ case "y" -> baseDisplayY = parseInt (value );
329+ case "width" -> baseDisplayWidth = parseInt (value );
330+ case "height" -> baseDisplayHeight = parseInt (value );
331+ }
332+ return ;
333+ }
334+
335+ if (orientation == null ) {
336+ if (ctx .isPartBlock && key .equalsIgnoreCase ("name" )) {
337+ ctx .devicePart = value .equalsIgnoreCase ("device" );
338+ }
339+ return ;
340+ }
341+ OrientationInfoBuilder builder = builders .computeIfAbsent (orientation , o -> new OrientationInfoBuilder ());
323342 String ctxName = ctx .name .toLowerCase (Locale .ROOT );
324- if (ctxName .contains ("image" ) && isImageKey (key )) {
343+ if (ctx .isPartBlock && key .equalsIgnoreCase ("name" )) {
344+ ctx .devicePart = value .equalsIgnoreCase ("device" );
345+ }
346+ if (isImageKey (key ) && shouldTreatAsImage (ctx , key )) {
325347 builder .considerImage (value , contextStack , this ::resolveImagePath );
326348 } else if (ctxName .contains ("display" )) {
327349 switch (key .toLowerCase (Locale .ROOT )) {
328- case "x" -> builder .displayX = parseInt (value );
329- case "y" -> builder .displayY = parseInt (value );
330- case "width" -> builder .displayWidth = parseInt (value );
331- case "height" -> builder .displayHeight = parseInt (value );
350+ case "x" -> builder .displayXOverride = parseInt (value );
351+ case "y" -> builder .displayYOverride = parseInt (value );
352+ case "width" -> builder .displayWidthOverride = parseInt (value );
353+ case "height" -> builder .displayHeightOverride = parseInt (value );
354+ }
355+ } else if (isInDevicePartContext ()) {
356+ switch (key .toLowerCase (Locale .ROOT )) {
357+ case "x" -> builder .offsetX = parseInt (value );
358+ case "y" -> builder .offsetY = parseInt (value );
359+ case "rotation" -> builder .rotation = parseInt (value );
332360 }
333361 }
334362 }
@@ -338,6 +366,23 @@ private boolean isImageKey(String key) {
338366 return lower .equals ("name" ) || lower .equals ("image" ) || lower .equals ("filename" );
339367 }
340368
369+ private boolean shouldTreatAsImage (Context ctx , String key ) {
370+ if (!key .equalsIgnoreCase ("name" )) {
371+ return true ;
372+ }
373+ String ctxName = ctx .name .toLowerCase (Locale .ROOT );
374+ return ctxName .contains ("image" )
375+ || ctxName .contains ("background" )
376+ || ctxName .contains ("foreground" )
377+ || ctxName .contains ("frame" )
378+ || ctxName .contains ("skin" )
379+ || ctxName .contains ("device" )
380+ || ctxName .contains ("phone" )
381+ || ctxName .contains ("tablet" )
382+ || ctxName .contains ("onion" )
383+ || ctxName .contains ("overlay" );
384+ }
385+
341386 private void pushContext (String name ) {
342387 name = name .trim ();
343388 if (name .isEmpty ()) {
@@ -432,14 +477,53 @@ private OrientationType detectOrientation(String name) {
432477 return null ;
433478 }
434479
435- private record Context (String name , OrientationType orientation ) {}
480+ private boolean isInDeviceDisplayContext () {
481+ Iterator <Context > it = contextStack .iterator ();
482+ if (!it .hasNext ()) {
483+ return false ;
484+ }
485+ Context top = it .next ();
486+ if (!top .name .equalsIgnoreCase ("display" )) {
487+ return false ;
488+ }
489+ if (!it .hasNext ()) {
490+ return false ;
491+ }
492+ Context parent = it .next ();
493+ return parent .name .equalsIgnoreCase ("device" );
494+ }
495+
496+ private boolean isInDevicePartContext () {
497+ for (Context ctx : contextStack ) {
498+ if (ctx .isPartBlock && ctx .devicePart ) {
499+ return true ;
500+ }
501+ }
502+ return false ;
503+ }
504+
505+ private static final class Context {
506+ final String name ;
507+ final OrientationType orientation ;
508+ final boolean isPartBlock ;
509+ boolean devicePart ;
510+
511+ Context (String name , OrientationType orientation ) {
512+ this .name = name ;
513+ this .orientation = orientation ;
514+ this .isPartBlock = name != null && name .toLowerCase (Locale .ROOT ).startsWith ("part" );
515+ }
516+ }
436517
437518 private static class OrientationInfoBuilder {
438519 ImageCandidate selectedImage ;
439- Integer displayX ;
440- Integer displayY ;
441- Integer displayWidth ;
442- Integer displayHeight ;
520+ Integer displayXOverride ;
521+ Integer displayYOverride ;
522+ Integer displayWidthOverride ;
523+ Integer displayHeightOverride ;
524+ Integer offsetX ;
525+ Integer offsetY ;
526+ Integer rotation ;
443527
444528 void considerImage (String name , Deque <Context > contexts , java .util .function .Function <String , Path > resolver ) {
445529 ImageCandidate candidate = ImageCandidate .from (name , contexts , resolver );
@@ -448,11 +532,34 @@ void considerImage(String name, Deque<Context> contexts, java.util.function.Func
448532 }
449533 }
450534
451- OrientationInfo build (OrientationType type ) {
452- if (selectedImage == null || displayX == null || displayY == null || displayWidth == null || displayHeight == null ) {
535+ OrientationInfo build (OrientationType type , LayoutParser parser ) {
536+ if (selectedImage == null ) {
453537 throw new IllegalStateException ("Layout definition for " + type + " is incomplete" );
454538 }
455- return new OrientationInfo (type , selectedImage .name (), new DisplayArea (displayX , displayY , displayWidth , displayHeight ));
539+ int baseX = parser .baseDisplayX != null ? parser .baseDisplayX : 0 ;
540+ int baseY = parser .baseDisplayY != null ? parser .baseDisplayY : 0 ;
541+ Integer widthSource = displayWidthOverride != null ? displayWidthOverride : parser .baseDisplayWidth ;
542+ Integer heightSource = displayHeightOverride != null ? displayHeightOverride : parser .baseDisplayHeight ;
543+ if (widthSource == null || heightSource == null ) {
544+ throw new IllegalStateException ("Layout definition for " + type + " is missing display dimensions" );
545+ }
546+ int finalWidth = widthSource ;
547+ int finalHeight = heightSource ;
548+ int normalizedRotation = rotation != null ? Math .floorMod (rotation , 4 ) : 0 ;
549+ if ((normalizedRotation & 1 ) == 1 ) {
550+ int tmp = finalWidth ;
551+ finalWidth = finalHeight ;
552+ finalHeight = tmp ;
553+ }
554+ int finalX = displayXOverride != null ? displayXOverride : baseX ;
555+ int finalY = displayYOverride != null ? displayYOverride : baseY ;
556+ if (offsetX != null ) {
557+ finalX += offsetX ;
558+ }
559+ if (offsetY != null ) {
560+ finalY += offsetY ;
561+ }
562+ return new OrientationInfo (type , selectedImage .name (), new DisplayArea (finalX , finalY , finalWidth , finalHeight ));
456563 }
457564 }
458565
0 commit comments