| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 08e407d commit 688ca15
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,8 @@ const CASES: Case[] = [ | |||
| 26 | 26 | { edge: 'dont-apply', description: 'hands the insets to JS and pads nothing - expect no green ring' }, | |
| 27 | 27 | { edge: 'none,none', description: 'a stacked value that resolves to none - must behave exactly like the first case' }, | |
| 28 | 28 | { edge: 'ignore,bottom', description: 'ignore wins over anything after it - must behave exactly like ignore' }, | |
| 29 | + { edge: 'none,cutout', description: 'like none, but the display cutout counts too - rotate to landscape, where the camera sits on an edge with no system bar' }, | ||
| 30 | + { edge: 'top,cutout', description: 'overflows the top while still keeping clear of the cutout on the other edges' }, | ||
| 29 | 31 | ]; | |
| 30 | 32 | ||
| 31 | 33 | class EdgeMatrix extends Observable { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,7 +10,14 @@ export namespace CoreTypes { | |||
| 10 | 10 | type AndroidOverflowSingle = 'ignore' | 'none' | 'dont-apply'; | |
| 11 | 11 | type AndroidOverflowMultiple = 'left' | 'right' | 'top' | 'bottom' | 'left-dont-consume' | 'top-dont-consume' | 'right-dont-consume' | 'bottom-dont-consume' | 'all-but-left' | 'all-but-top' | 'all-but-right' | 'all-but-bottom'; | |
| 12 | 12 | type AndroidOverflowStacked = AndroidOverflowSingle | `${AndroidOverflowSingle},${AndroidOverflowSingle}` | `${AndroidOverflowSingle},${AndroidOverflowMultiple}` | `${AndroidOverflowMultiple},${AndroidOverflowSingle}` | `${AndroidOverflowMultiple},${AndroidOverflowMultiple}`; | |
| 13 | - export type AndroidOverflow = AndroidOverflowSingle | AndroidOverflowMultiple | AndroidOverflowStacked; | ||
| 13 | + type AndroidOverflowBase = AndroidOverflowSingle | AndroidOverflowMultiple | AndroidOverflowStacked; | ||
| 14 | + /** | ||
| 15 | + * `cutout` is a modifier rather than an edge: append it to any value to fold the | ||
| 16 | + * display cutout into the insets being distributed. Without it only the system bars | ||
| 17 | + * are used, so in landscape a camera on an edge with no bar is not accounted for. | ||
| 18 | + * It has no effect alongside `ignore` or `dont-apply`. | ||
| 19 | + */ | ||
| 20 | + export type AndroidOverflow = AndroidOverflowBase | 'cutout' | `${AndroidOverflowBase},cutout`; | ||
| 14 | 21 | export type CSSWideKeywords = 'initial' | 'inherit' | 'unset' | 'revert'; | |
| 15 | 22 | ||
| 16 | 23 | /** | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | 1 | import { describe, it, expect } from 'vitest'; | |
| 2 | 2 | ||
| 3 | - import { OverflowEdgeAllButTop, OverflowEdgeBottom, OverflowEdgeBottomDontConsume, OverflowEdgeDontApply, OverflowEdgeIgnore, OverflowEdgeLeft, OverflowEdgeNone, OverflowEdgeRight, OverflowEdgeTop, parseEdges } from './overflow-edges.android'; | ||
| 3 | + import { OverflowEdgeAllButTop, OverflowEdgeBottom, OverflowEdgeBottomDontConsume, OverflowEdgeCutout, OverflowEdgeDontApply, OverflowEdgeIgnore, OverflowEdgeLeft, OverflowEdgeNone, OverflowEdgeRight, OverflowEdgeTop, parseEdges } from './overflow-edges.android'; | ||
| 4 | 4 | ||
| 5 | 5 | describe('parseEdges', () => { | |
| 6 | 6 | it('maps a single edge to its flag', () => { | |
@@ -45,4 +45,19 @@ describe('parseEdges', () => { | |||
| 45 | 45 | it('ignores unknown entries mixed in with known ones', () => { | |
| 46 | 46 | expect(parseEdges('nope,top')).toBe(OverflowEdgeTop); | |
| 47 | 47 | }); | |
| 48 | + | ||
| 49 | + it('carries cutout alongside any edge', () => { | ||
| 50 | + // cutout is a modifier, not an edge: it selects the inset source and leaves the | ||
| 51 | + // edge bits to say which sides get padded. | ||
| 52 | + expect(parseEdges('cutout')).toBe(OverflowEdgeCutout); | ||
| 53 | + expect(parseEdges('none,cutout')).toBe(OverflowEdgeCutout); | ||
| 54 | + expect(parseEdges('top,cutout')).toBe(OverflowEdgeTop | OverflowEdgeCutout); | ||
| 55 | + expect(parseEdges('cutout,top,bottom')).toBe(OverflowEdgeCutout | OverflowEdgeTop | OverflowEdgeBottom); | ||
| 56 | + }); | ||
| 57 | + | ||
| 58 | + it('drops cutout next to a sentinel, which distributes nothing of its own', () => { | ||
| 59 | + expect(parseEdges('dont-apply,cutout')).toBe(OverflowEdgeDontApply); | ||
| 60 | + expect(parseEdges('cutout,dont-apply')).toBe(OverflowEdgeDontApply); | ||
| 61 | + expect(parseEdges('ignore,cutout')).toBe(OverflowEdgeIgnore); | ||
| 62 | + }); | ||
| 48 | 63 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,6 +20,12 @@ export const OverflowEdgeAllButLeft = 1 << 10; | |||
| 20 | 20 | export const OverflowEdgeAllButTop = 1 << 11; | |
| 21 | 21 | export const OverflowEdgeAllButRight = 1 << 12; | |
| 22 | 22 | export const OverflowEdgeAllButBottom = 1 << 13; | |
| 23 | + /** | ||
| 24 | + * Not an edge: a modifier that folds the display cutout into the insets being | ||
| 25 | + * distributed. Type.systemBars() leaves the cutout out, which only shows up once the | ||
| 26 | + * device is rotated and the camera moves to an edge that has no bar. | ||
| 27 | + */ | ||
| 28 | + export const OverflowEdgeCutout = 1 << 14; | ||
| 23 | 29 | ||
| 24 | 30 | const edgeMap: Record<string, number> = { | |
| 25 | 31 | none: OverflowEdgeNone, | |
@@ -37,6 +43,7 @@ const edgeMap: Record<string, number> = { | |||
| 37 | 43 | 'all-but-top': OverflowEdgeAllButTop, | |
| 38 | 44 | 'all-but-right': OverflowEdgeAllButRight, | |
| 39 | 45 | 'all-but-bottom': OverflowEdgeAllButBottom, | |
| 46 | + cutout: OverflowEdgeCutout, | ||
| 40 | 47 | }; | |
| 41 | 48 | ||
| 42 | 49 | /** | |
@@ -55,7 +62,9 @@ export function parseEdges(edges: string): number | null { | |||
| 55 | 62 | ||
| 56 | 63 | matched = true; | |
| 57 | 64 | // `dont-apply` and `ignore` are sentinels rather than bit flags, so OR-ing them | |
| 58 | - // with an edge would corrupt both. The first one wins outright. | ||
| 65 | + // with an edge would corrupt both. The first one wins outright - which also means | ||
| 66 | + // `cutout` alongside either of them is dropped. `dont-apply` hands the raw cutout | ||
| 67 | + // values to JS regardless, and `ignore` distributes nothing at all. | ||
| 59 | 68 | if (value === OverflowEdgeDontApply || value === OverflowEdgeIgnore) { | |
| 60 | 69 | return value; | |
| 61 | 70 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -266,6 +266,7 @@ | |||
| 266 | 266 | public static OverflowEdgeAllButTop: number; | |
| 267 | 267 | public static OverflowEdgeAllButRight: number; | |
| 268 | 268 | public static OverflowEdgeAllButBottom: number; | |
| 269 | + public static OverflowEdgeCutout: number; | ||
| 269 | 270 | constructor(context: android.content.Context); | |
| 270 | 271 | public getOverflowEdge(): number; | |
| 271 | 272 | public setOverflowEdge(value: number): void; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -39,6 +39,12 @@ public abstract class LayoutBase extends ViewGroup { | |||
| 39 | 39 | public static final int OverflowEdgeAllButTop = 1 << 11; | |
| 40 | 40 | public static final int OverflowEdgeAllButRight = 1 << 12; | |
| 41 | 41 | public static final int OverflowEdgeAllButBottom = 1 << 13; | |
| 42 | + /** | ||
| 43 | + * Not an edge: a modifier that folds the display cutout into the insets being | ||
| 44 | + * distributed. Type.systemBars() leaves the cutout out, which only shows up once | ||
| 45 | + * the device is rotated and the camera moves to an edge that has no bar. | ||
| 46 | + */ | ||
| 47 | + public static final int OverflowEdgeCutout = 1 << 14; | ||
| 42 | 48 | ||
| 43 | 49 | // Layout (bytes): | |
| 44 | 50 | // 0 - left inset (int) | |
@@ -308,7 +314,17 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 308 | 314 | return insets; | |
| 309 | 315 | } | |
| 310 | 316 | ||
| 311 | - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); | ||
| 317 | + boolean includeCutout = (overflowEdge & OverflowEdgeCutout) != 0; | ||
| 318 | + // OverflowEdgeCutout is a modifier rather than an edge, so it is masked off | ||
| 319 | + // before anything below asks which edges were requested. | ||
| 320 | + int edges = overflowEdge & ~OverflowEdgeCutout; | ||
| 321 | + | ||
| 322 | + int barTypes = WindowInsetsCompat.Type.systemBars(); | ||
| 323 | + if (includeCutout) { | ||
| 324 | + barTypes |= WindowInsetsCompat.Type.displayCutout(); | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + Insets systemBars = insets.getInsets(barTypes); | ||
| 312 | 328 | Insets ime = insets.getInsets(WindowInsetsCompat.Type.ime()); | |
| 313 | 329 | Insets cutout = insets.getInsets(WindowInsetsCompat.Type.displayCutout()); | |
| 314 | 330 | ||
@@ -318,7 +334,7 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 318 | 334 | int insetNavBottom = systemBars.bottom; | |
| 319 | 335 | int insetImeBottom = ime.bottom; | |
| 320 | 336 | ||
| 321 | - if (overflowEdge == OverflowEdgeNone) { | ||
| 337 | + if (edges == OverflowEdgeNone) { | ||
| 322 | 338 | int bottom = mPaddingBottom + Math.max(insetNavBottom, insetImeBottom); | |
| 323 | 339 | ||
| 324 | 340 | base.applyingEdges = true; | |
@@ -333,37 +349,42 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 333 | 349 | edgeInsets = Insets.of(insetLeft, insetTop, insetRight, insetNavBottom); | |
| 334 | 350 | imeInsets = Insets.of(0, 0, 0, insetImeBottom); | |
| 335 | 351 | ||
| 336 | - return new WindowInsetsCompat.Builder(insets) | ||
| 352 | + WindowInsetsCompat.Builder noneBuilder = new WindowInsetsCompat.Builder(insets) | ||
| 337 | 353 | .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE) | |
| 338 | - .setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE) | ||
| 339 | - .build(); | ||
| 354 | + .setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE); | ||
| 355 | + | ||
| 356 | + if (includeCutout) { | ||
| 357 | + noneBuilder.setInsets(WindowInsetsCompat.Type.displayCutout(), Insets.NONE); | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + return noneBuilder.build(); | ||
| 340 | 361 | } | |
| 341 | 362 | ||
| 342 | 363 | boolean[] apply = new boolean[4]; // L T R B | |
| 343 | 364 | boolean[] consume = new boolean[4]; | |
| 344 | 365 | boolean[] defaultConsume = new boolean[4]; | |
| 345 | 366 | defaultConsume[0] = defaultConsume[1] = defaultConsume[2] = defaultConsume[3] = true; | |
| 346 | 367 | ||
| 347 | - consume[0] = (overflowEdge & OverflowEdgeLeft) != 0; | ||
| 348 | - consume[1] = (overflowEdge & OverflowEdgeTop) != 0; | ||
| 349 | - consume[2] = (overflowEdge & OverflowEdgeRight) != 0; | ||
| 350 | - consume[3] = (overflowEdge & OverflowEdgeBottom) != 0; | ||
| 368 | + consume[0] = (edges & OverflowEdgeLeft) != 0; | ||
| 369 | + consume[1] = (edges & OverflowEdgeTop) != 0; | ||
| 370 | + consume[2] = (edges & OverflowEdgeRight) != 0; | ||
| 371 | + consume[3] = (edges & OverflowEdgeBottom) != 0; | ||
| 351 | 372 | ||
| 352 | - if ((overflowEdge & OverflowEdgeLeftDontConsume) != 0) | ||
| 373 | + if ((edges & OverflowEdgeLeftDontConsume) != 0) | ||
| 353 | 374 | defaultConsume[0] = consume[0] = false; | |
| 354 | - if ((overflowEdge & OverflowEdgeTopDontConsume) != 0) | ||
| 375 | + if ((edges & OverflowEdgeTopDontConsume) != 0) | ||
| 355 | 376 | defaultConsume[1] = consume[1] = false; | |
| 356 | - if ((overflowEdge & OverflowEdgeRightDontConsume) != 0) | ||
| 377 | + if ((edges & OverflowEdgeRightDontConsume) != 0) | ||
| 357 | 378 | defaultConsume[2] = consume[2] = false; | |
| 358 | - if ((overflowEdge & OverflowEdgeBottomDontConsume) != 0) | ||
| 379 | + if ((edges & OverflowEdgeBottomDontConsume) != 0) | ||
| 359 | 380 | defaultConsume[3] = consume[3] = false; | |
| 360 | 381 | ||
| 361 | 382 | apply[0] = !consume[0]; | |
| 362 | 383 | apply[1] = !consume[1]; | |
| 363 | 384 | apply[2] = !consume[2]; | |
| 364 | 385 | apply[3] = !consume[3]; | |
| 365 | 386 | ||
| 366 | - if ((overflowEdge & OverflowEdgeAllButLeft) != 0) { | ||
| 387 | + if ((edges & OverflowEdgeAllButLeft) != 0) { | ||
| 367 | 388 | for (int i = 0; i < 4; i++) { | |
| 368 | 389 | consume[i] = true; | |
| 369 | 390 | apply[i] = false; | |
@@ -372,7 +393,7 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 372 | 393 | apply[0] = true; | |
| 373 | 394 | } | |
| 374 | 395 | ||
| 375 | - if ((overflowEdge & OverflowEdgeAllButTop) != 0) { | ||
| 396 | + if ((edges & OverflowEdgeAllButTop) != 0) { | ||
| 376 | 397 | for (int i = 0; i < 4; i++) { | |
| 377 | 398 | consume[i] = true; | |
| 378 | 399 | apply[i] = false; | |
@@ -381,7 +402,7 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 381 | 402 | apply[1] = true; | |
| 382 | 403 | } | |
| 383 | 404 | ||
| 384 | - if ((overflowEdge & OverflowEdgeAllButRight) != 0) { | ||
| 405 | + if ((edges & OverflowEdgeAllButRight) != 0) { | ||
| 385 | 406 | for (int i = 0; i < 4; i++) { | |
| 386 | 407 | consume[i] = true; | |
| 387 | 408 | apply[i] = false; | |
@@ -390,7 +411,7 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 390 | 411 | apply[2] = true; | |
| 391 | 412 | } | |
| 392 | 413 | ||
| 393 | - if ((overflowEdge & OverflowEdgeAllButBottom) != 0) { | ||
| 414 | + if ((edges & OverflowEdgeAllButBottom) != 0) { | ||
| 394 | 415 | for (int i = 0; i < 4; i++) { | |
| 395 | 416 | consume[i] = true; | |
| 396 | 417 | apply[i] = false; | |
@@ -401,7 +422,7 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 401 | 422 | ||
| 402 | 423 | boolean consumeIme = consume[3]; | |
| 403 | 424 | ||
| 404 | - if (overflowEdge == OverflowEdgeDontApply) { | ||
| 425 | + if (edges == OverflowEdgeDontApply) { | ||
| 405 | 426 | resetInset(); | |
| 406 | 427 | ||
| 407 | 428 | putInset(BufferOffset.INSET_LEFT, insetLeft); | |
@@ -487,10 +508,23 @@ public WindowInsetsCompat onApplyWindowInsets( | |||
| 487 | 508 | consumeIme ? Insets.NONE | |
| 488 | 509 | : Insets.of(0, 0, 0, insetImeBottom); | |
| 489 | 510 | ||
| 490 | - return new WindowInsetsCompat.Builder(insets) | ||
| 511 | + WindowInsetsCompat.Builder builder = new WindowInsetsCompat.Builder(insets) | ||
| 491 | 512 | .setInsets(WindowInsetsCompat.Type.systemBars(), remainingSystemBars) | |
| 492 | - .setInsets(WindowInsetsCompat.Type.ime(), remainingIme) | ||
| 493 | - .build(); | ||
| 513 | + .setInsets(WindowInsetsCompat.Type.ime(), remainingIme); | ||
| 514 | + | ||
| 515 | + if (includeCutout) { | ||
| 516 | + // The cutout was folded into the values above, so whatever was consumed | ||
| 517 | + // of it is gone for children too - otherwise a nested view that also | ||
| 518 | + // asked for the cutout would apply it a second time. | ||
| 519 | + builder.setInsets(WindowInsetsCompat.Type.displayCutout(), Insets.of( | ||
| 520 | + defaultConsume[0] ? 0 : cutout.left, | ||
| 521 | + defaultConsume[1] ? 0 : cutout.top, | ||
| 522 | + defaultConsume[2] ? 0 : cutout.right, | ||
| 523 | + defaultConsume[3] ? 0 : cutout.bottom | ||
| 524 | + )); | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + return builder.build(); | ||
| 494 | 528 | } | |
| 495 | 529 | }; | |
| 496 | 530 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,7 +5,7 @@ | |||
| 5 | 5 | "artifactHash": "sha256:71ac91f0e4290e8a38d253649a73cdeef87e5bfa10e013ead5f61eecd57aff70" | |
| 6 | 6 | }, | |
| 7 | 7 | "android": { | |
| 8 | - "sourceHash": "sha256:f88afcb7563c5519239f0d10d655b3ce399f862efaf4ef82d52b30be9389f527", | ||
| 9 | - "artifactHash": "sha256:0c8c180948f0cb8fbf25f24274466267e328aa62f2273c0df2c25be437d1249d" | ||
| 8 | + "sourceHash": "sha256:205ba541f47e249772e24cf9b38bd1d4552c9fabfcaa089335d03bafb1900e59", | ||
| 9 | + "artifactHash": "sha256:54ab3d40c9cf143717664c627ea810f0e8f3383b96eac2ef578fc3f78301f2d7" | ||
| 10 | 10 | } | |
| 11 | 11 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments