"""Check actual Inspector geometry, including clipping by inner containers.""" import json,math,pathlib def nodes_of_class(node,kind): result=[node]if node.get('class')==kind else[] for child in node.get('children',[]):result.extend(nodes_of_class(child,kind)) return result def inspect(path,profile): frame=json.loads(pathlib.Path(path).read_text(encoding='utf-8'))['frames'][-1] pages=[] def visible_pages(node,parent_visible=True): visible=parent_visible and node.get('visible',True) if not visible:return if node.get('class')=='AppletPage':pages.append(node) for child in node.get('children',[]):visible_pages(child,visible) visible_pages(frame['widgets']) page=pages[-1] circle=profile.startswith('round');width=466 if circle else 390;height=466 if circle else 450 failures=[];buttons=[];icons=[];regions={} def visit(node,ancestors=(),px=0,py=0,visible=True): g=node['geometry'];x=px+g['x'];y=py+g['y'];w=g['w'];h=g['h'] visible=visible and node.get('visible',True) if not visible:return box=[x,y,w,h];kind=node['class'];identifier=node.get('id','') if identifier in ['ime-preview','ime-composition','ime-keypad','ime-candidate-strip']:regions[identifier]=box if kind in ['Button','ImageBox','Label']: for ancestor in ancestors: ax,ay,aw,ah=ancestor['box'] if xax+aw+1 or y+h>ay+ah+1: failures.append({'reason':'ancestor clipping','class':kind,'id':identifier,'box':box,'ancestor':ancestor}) if kind in ['Button','ImageBox']: radius=max(math.hypot(tx-233,ty-233)for tx in[x,x+w]for ty in[y,y+h])if circle else None if(circle and radius>230)or(not circle and(x<0 or y<0 or x+w>width or y+h>height)): failures.append({'reason':'screen boundary','class':kind,'id':identifier,'box':box,'radius':radius}) record={'id':identifier,'box':box,'radius':radius} (buttons if kind=='Button'else icons).append(record) for child in node.get('children',[]):visit(child,ancestors+({'class':kind,'id':identifier,'box':box},),x,y,visible) visit(page) gaps={} if 'ime-composition' in regions and 'ime-keypad' in regions: compose=regions['ime-composition'];keys=regions['ime-keypad'] gap=keys[1]-compose[1]-compose[3];gaps['composition_to_keys']=gap # Account for the actual visible strip; direct entry may leave at most # one rounding pixel, never an unused candidate-sized band. maximum=(48 if circle else 53)if 'ime-candidate-strip' in regions else 1 if gap < -1 or gap > maximum:failures.append({'reason':'extra gap before keyboard','gap':gap,'maximum':maximum}) return {'page':page.get('id'),'buttons':len(buttons),'icons':len(icons),'gaps':gaps,'max_button_radius':round(max([b['radius']or 0 for b in buttons],default=0),2),'failures':failures,'passed':not failures}