Repository navigation
Add iPadOS support (#203) - #254
Open
naco-siren wants to merge 146 commits into
Open
naco-siren wants to merge 146 commits into
naco-siren wants to merge 146 commits into
Conversation
Most of Document, IO and Rendering imported AppKit without using it. They now import the frameworks they use, and the few calls that do reach the platform's UI framework go through Compositor/Platform/Platform.swift: the alert beep, drawing text into a bitmap, the general pasteboard, and the font and color types. The Mac app behaves as before, and these files now also build for iPadOS. Three things stay on the Mac for now: SVG import, which NSImage draws; the prompt when deleting a layer that supplies live masks; and PaletteColor's NSColor bridge, which only the Mac's panels use. PSDConversionRequest, which the session holds, moves from the sheet that shows it to the PSD types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ProjectStore's package reader and the package it writes are now static functions the actor calls, so a caller that coordinates the file itself, as a UIDocument does on iPad, reads and writes projects through the same code and the same validation. The actor's save and load are unchanged. CanvasPreset moves out of the New Canvas sheet into Document, so both platforms offer the same sizes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Layers panel's canvas-framed thumbnails (CanvasThumbnail) move from UI to Rendering and draw a PlatformImage, an NSImage on the Mac. A layer row's size label moves next to ImageLayer. Which layer a Move-tool press drags, with Auto Select and Command flipping it, moves from the Mac canvas into EditorSession, so the iPad canvas picks the same layer the Mac's does. The Mac app behaves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New Canvas lists iPad 11", iPad Air 11" and 13", and iPad Pro 11" and 13" beside the iPhones and Macs, at the resolutions Apple gives for the iPad (A16), iPad Air (M4) and iPad Pro (M5), landscape as an iPad usually sits in a keyboard or stand. The Mac's New Canvas menu shares the list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The × between New Canvas's Width and Height is a button now, with the arrow.left.arrow.right symbol, that swaps the two: a portrait canvas from a landscape preset, and back. It lines up with the fields rather than their labels, through an alignment guide on the fields, where a fixed nudge had placed the ×. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CompositorPad builds the document model, file formats and renderers from Compositor/ for iPadOS, under a UIKit interface of its own in CompositorPad/. Its build settings are in CompositorPad/CompositorPad.xcconfig, including the list of files that are the Mac's own interface and stay out. The project file only gains the new target; the Mac app's is untouched. The window is laid out as the Mac's: a toolbar, a navigation bar grouped as the Mac's SwiftUI toolbar is, with New Canvas, the projects as tabs, and Undo and Redo, Fit, 100% and zooming at its end; the tools down the left in the Mac's order, with the two colors; and the canvas, drawn on the GPU as the Mac's is. A new tab shows the New Canvas form, with the Mac's presets. Each tab is a UIDocument that reads and writes the package through ProjectStore's shared code, coordinates with Files and iCloud, saves as the project changes and takes up changes other apps make. A new canvas is written to the app's Documents folder at once, and its undo history starts there. Projects open in place from Files, as tabs in the window in front, and windows come back with their tabs after the app is quit or reinstalled. The menu bar and a hardware keyboard have the Mac's File and View commands and single-key tools. Apple Pencil or a finger paints through the editor's own brush; once Pencil has painted, a finger moves the canvas instead, though it still picks colors, with the Mac's sample ring, and zooms. Two fingers move and zoom, and the system's three-finger gestures undo and redo. Photoshop conversion reports and RAW development are UIKit sheets. The icon is the Mac's artwork, redrawn full-bleed for iPadOS by scripts/ipad-icon.swift. The iPad canvas composites with an adapted copy of CanvasView's GPU path, until that moves out of the AppKit view where both canvases can share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CompositorPadTests runs on an iPad simulator: the editor's tests that build for iPadOS as they are (the brush, dithering, selections, PSD and PSB import and round trips, among others), and the iPad's own. The iPad canvas matches the editor's own composite of a document with masks, blend modes, a rotated layer, a clipping stack, a masked folder and adjustment layers; a brush stroke shows while it's painted, stays once it's committed and goes with undo; text drawn through UIKit comes out upright; a finger still picks colors and zooms once Apple Pencil paints; a new canvas's undo history starts at its file; and the app's own projects are found again after it's reinstalled. A new ipad job in Verify builds the app and runs these on whichever iPad simulator the runner has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the toolbar, a bar holds the tool in hand's settings, as the Mac's tool header does: Transform's Auto Select, position, size, aspect lock, scale, angle, sampling, flips, and Cancel and Apply while an edit waits for them; the brush's mode, size, hardness, opacity or strength, radius, smoothing and color; the Eyedropper's sample ring; the Zoom tool's percentage. A tool that doesn't work by touch yet says so. Captions can be dragged to scrub their values, as on the Mac. The Layers panel sits right of the canvas: the count, the active layer's blend mode and opacity, the layers top first with visibility, canvas-framed thumbnails, masks and their link, folders, clipping and effects, and the footer's new layer, new folder, add mask and delete. A long press opens the Mac's menu for a layer, a double tap renames it, and dragging a row moves the layer above another or into a folder. Effects and adjustment layers are shown dimmed, as their editors aren't on iPad yet. The status line along the foot shows the zoom, the canvas's size and color space, and what the tool in hand does, or what the editor is busy with. A window can't be made smaller than the Mac's minimum, so the canvas keeps room between the tools and the Layers panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The part of a drop that turns item providers into files the importer can read, the file itself or a copy of the image an item holds, moves from the Mac's ImageFileDrop into ItemProviderFiles in IO, where the iPad's drops and picks from Photos use it too. A copy can be named after the name an item suggests for itself, as a photo in Photos does; the Mac's drops name copies as before. The Mac app behaves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photos' own picker brings in the photos picked, as they are, HEIC and RAW included, and needs no access to the library. New Canvas's Import image offers it beside Files, and so does the File menu. Images dropped on the window from Photos, Files or another app come in as the Mac's do: centered where they land on the canvas, into the project in front, or into an empty tab as its canvas and a new project, named as the photos are. A project dropped from Files opens in a tab, in place where it can be. The canvas is outlined while a drop can land. The Edit menu's Cut, Copy and Paste work on the canvas as on the Mac, and Copy Merged joins them: a layer copied whole pastes as a copy above it, pixels go back where they came from, and an image copied in another app comes in centered. A photo copied in Photos pastes upright now: UIKit hands it over with its EXIF turn beside the pixels, which its CGImage left out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What's drawn over the canvas moves from the Mac's TransformOverlay view into CanvasOverlay in Rendering, which draws into the context a view gives it: the layout grid and guides, the crop frame, a gradient's line, the transform box and its handles, the marching ants with their level of detail, an outline being drawn, and the lines a move snaps to. Colors come through PlatformColor, and the accent through Platform.accentColor. TransformOverlay keeps its interface and draws through it, so the iPad's canvas can draw the same. The Mac draws exactly as before: each overlay state was drawn before and after, and the pixels are the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What a Move-tool press picks, a handle or else a layer it selects, and what each step of the drag does, moving, resizing, turning or distorting with Shift, Option and Control, snapping, and landing on whole pixels and degrees, move from the Mac's canvas into EditorSession, as beginTransformDrag and dragTransform. The Mac's canvas keeps its cursors and calls them, so the iPad's canvas drags the same way. TransformDragTests covers them without AppKit, so the iPad's tests can run it too. The Mac app behaves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPad canvas draws the Mac's overlay over the document through CanvasOverlay: the transform box and its handles, guides and the lines a move snaps to. With the Move tool, a finger or Apple Pencil drags a corner or edge handle to resize, the circle to rotate, or the layer to move, snapping as on the Mac. A fingertip finds a handle up to 22 points off, the nearer of two, though less far around a small box, which a press inside still moves. On a keyboard, Shift, Option, Command and Control do what they do on the Mac, the arrows nudge the layer, Return applies and Escape cancels an edit waiting for them, and the Layer menu's Transform Layer (⌘T) starts one, which an export keeps first, as on the Mac. The Transform bar gains Show Controls. Once Apple Pencil has painted, a finger still moves layers; it moves the canvas instead only with the tools that paint or draw. A second finger coming down to zoom takes back a drag in progress, a distortion's corners included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Marquee, Lasso and Magic tools work by touch through the editor's own selection code, as the Mac's do with the mouse: a drag draws a rectangle or an ellipse, Apple Pencil or a finger draws around an area, and a tap selects similar colors or, with Object, what it lands on. A polygonal lasso puts a corner down where each tap lifts and closes on a tap back at its first corner, or on a double tap. In New mode a drag inside the selection moves its outline, and a tap there deselects, or with the Magic tool selects afresh. On a keyboard, M, L and W choose the tools, Shift and Option add and subtract, Command moves the selected pixels, Control drags without snapping, the arrows nudge, Return closes an outline, Escape cancels it, and Delete takes back a corner or clears the selected pixels. The marching ants march as on the Mac, drawn through CanvasOverlay. The bar has the Mac's settings for each tool: its shape or way of selecting; New, Add or Subtract; the Wand's tolerance, sample size, layers and contiguity; the Object tool's layers and edge; anti-aliasing; Expand, Contract and Feather; and Deselect. The menu bar gains the Mac's Select menu, the Edit menu's fills and Clear Selection Pixels, and Layer via Copy (⌘J). Add Mask in the Layers panel masks to the selection, as on the Mac. What touches do with the Move tool and the selection tools moves from the canvas view into PadCanvasInput, which tests drive with points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Mac's Levels, Curves and Hue/Saturation panels open on iPad as popovers beside the Layers panel, through the editor's own adjustment code, for an adjustment layer or for a layer's own pixels. Levels has its channels, the histogram, the input and output triangles with their fields, and Auto; Curves its channels and a curve whose points a finger adds, drags and drags off; Hue/Saturation its ranges, sliders, Colorize and the range's inverse. Each has Preview, Reset, Cancel and OK, and while one is open the canvas still moves and zooms. The Layers panel's adjustment button lists the Mac's adjustment layers, and the menu bar gains the Mac's Image menu (Curves, Levels, Hue/Saturation and Invert, with their shortcuts) and, in the Layer menu, New Adjustment Layer and Edit Adjustment. A layer's row menu or a double tap on its thumbnail opens its editor again, as a double click does on the Mac. Adjustments the iPad has no editor for yet are listed, dimmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File › Export JPEG… (⌥⇧⌘S) and a tab's menu open the Mac's Export JPEG dialog: the image as it will be encoded, at a quality the next export starts from, over a color for its transparent areas chosen with the system's color well. The preview fits the dialog until it's pinched, double-tapped to 100% where the tap lands, or stepped through the Mac's zooms with its buttons or the View menu; from 100% up, the JPEG's pixels show as they are. Export… offers the file to share, save to Photos or keep in Files, as Export PNG does. While the dialog is open the project waits for it, as on the Mac. The canvas's keys wait while any editor or dialog is open over the window, and an export doesn't start under one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Crop tool works by touch through the editor's own crop code, as the Mac's does with the mouse: choosing it frames the whole canvas, or the selection's bounds; a drag draws a new frame, a drag inside a smaller one moves it, and a finger finds the frame's corners and edges up to 22 points off, the nearer where two reach, though less far around a small frame. Edges snap to layer and canvas edges as on the Mac. The bar has the Mac's Ratio, the frame's size, Cancel and Apply Crop. On a keyboard, C chooses the tool, Option keeps the frame's center, Control drags without snapping, Return applies the crop and Escape cancels it. A second finger coming down to zoom takes back a drag in progress, and an export sets a crop in progress aside, as on the Mac. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Image Size's rules move out of the Mac's sheet into ImageSizeDraft in Document/, beside CanvasSizeDraft: its units, the locked ratio, a resolution that changes alone without resampling and the pixels a print size scales with resampling, and how far its fields scrub. CanvasSizeDraft gains Canvas Size's scrubbing limits and its memory readout. The sheets keep only their views, so the iPad's dialogs can share the arithmetic. Nothing changes on the Mac. New tests, which build for iPadOS too, pin the rules down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Image menu gains the Mac's Canvas Size… (⌥⌘C) and Image Size… (⌥⌘I), as dialogs with the fields of the Mac's sheets and the arithmetic they share. Canvas Size takes its units, a size typed or scrubbed along its caption, relative or with the ratio locked, the anchor, and a color for the room added; Image Size its units and size, the resolution, resampling and its sampling. As on the Mac, the project waits while a dialog is open, a crop in progress is set aside and a transform kept first, and the change is one step to undo. Return applies and Escape cancels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shape being dragged out, which the Mac's canvas draws just above the active layer in the foreground color, moves out of EditorCanvas into Rendering/ as the session's drawShapeDraft and shapeDraftImage. The 45° steps Shift turns a gradient's line to move into GradientEdit. The iPad's canvas can then share both. Nothing changes on the Mac: before the move and after, every shape kind, with and without a corner radius, Shift and Option, drew the same pixels. New tests, which build for iPadOS too, pin the drawing and the steps down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Gradient and Shape tools work by touch through the editor's own code, as the Mac's do with the mouse. A drag draws a gradient's line, previewed on the layer as it goes; the line waits for Apply, or Return, with ends to drag, which a finger finds up to 22 points off. A drag with the Shape tool draws a rectangle, ellipse or line, shown just above the active layer, where its layer goes once the drag ends. Both have the Mac's bars: the gradient's shape, colors, Reverse and Opacity, with Cancel and Apply while it waits; the shape's kind, a line's width or a rectangle's corner radius, and its fill. On a keyboard, G and U choose the tools and Shift-U the next shape, Shift turns a line to 45° steps or squares a shape, Option draws a shape from its center, Control drags without snapping, and Escape cancels. A second finger coming down to zoom takes back a drag in progress, and once Apple Pencil has drawn, fingers move the canvas with both tools, as with the brushes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the Mac's InlineTextEditor and CanvasView work out apart from AppKit moves into shared code. Document/TextEditing.swift has where the editor shows the text being typed, point text growing at its layer's scale; how a handle turns point text into a box and resizes it; and what a change to the text does to the runs of its colors and faces. TextDraftRendering has the text as the canvas draws it, rendered once for each style, with its layer's effects around it, and handed over to the layer once committed. The iPad's text editor can then share them. Nothing changes on the Mac: the Type tool's tests, which drive the editor, pass as before. New tests, which build for iPadOS too, pin the shared parts down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Type tool works through the editor's own text code, as the Mac's does: a tap starts a line of text with its baseline where the tap lands, a drag draws a box for a paragraph, and a touch on text opens it, as does a double tap on text with the Move tool, with the caret where the touch came down. UIKit's text system edits it in a box laid over the canvas, scaled and turned with the layer, so selection, marked text for other languages, dictation, Scribble, the clipboard and undo work as anywhere on iPad. The glyphs are clear, and the canvas draws the text beneath as the layer's pixels, effects and all, as the Mac's does. A finger on an edge of the box turns point text into a paragraph and resizes it. The bar has the Mac's Type settings: the font, from the system's font picker with each family's faces, and the color, applied to the letters selected or to all of them; the size, alignment, tracking and leading; then Cancel and Done, or Edit Text. Escape cancels, ⌘Return keeps the text, T chooses the tool, and while text is open Undo and Redo take back what was typed, as on the Mac. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Mac's brush cursor moves into shared code, so iPad's can be the same: BrushCursorDrawing draws the brush's circle, Clone Stamp's preview inside it, the hardness ring and Clone Stamp's source crosshair, and ClonePreview makes the preview, and the one click's coverage that shapes it, out of the Mac's canvas. The Mac draws exactly as before: the overlay's pixels were compared in nine states, and the shared preview matches the canvas's old code byte for byte. The Spot Healing and Clone Stamp tests only imported AppKit; without it they run on iPad too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spot Healing and Clone Stamp join the iPad's tools, on J and S as on the Mac, with the Mac's bars: Spot Healing's three ways of healing, and Clone Stamp's Aligned and This Layer/All Layers. The brush cursor is the Mac's, for every brush: the brush's circle around a crosshair where Apple Pencil or a finger touches, or where the pointer or Apple Pencil hovers. Clone Stamp, once it has a source, shows no crosshair in its circle: the crosshair stays on the source, keeping its offset from the brush as it paints, and while hovering the circle shows what a stroke there would stamp. With a brush in hand the pointer hides over the canvas, the cursor's crosshair standing in for it. The Mac sets Clone Stamp's source with an Option-click and won't paint until there is one. On iPad an Option-tap sets it too, and before there's a source the first touch sets it where it lands, as the bar and status line say. A finger or Apple Pencil then moves it by its crosshair, as in Apple's Pixelmator Pro for iPad. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where SF Symbols has nothing to match a tool, the Mac's rail draws its own icon: Clone Stamp's rubber stamp, the Gradient's dithered ramp, and the Polygonal Lasso's and Object mode's. Their shapes move out of the Mac's views into ToolIcons, so scripts/rail-icons.swift can draw the iPad rail's icons from the same shapes. The Mac draws exactly as before: each icon's pixels were compared at four sizes and scales. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPad rail showed SF Symbols stand-ins where the Mac draws its own icons: a seal for Clone Stamp, a half-filled square for the Gradient, the freehand lasso in Polygonal mode and the wand in Object mode. It now shows the Mac's, as vector assets that scripts/rail-icons.swift draws from the Mac's shapes, each at the size that looks as large as the symbols beside it. SF Symbols differ in how large they look at one point size, too: the wand and the drop are drawn larger, and the marquee, the shape and the type smaller, so the rail reads evenly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If undo took a project back to what its file had while an autosave was still writing the edit it undid, the finished save marked that edit saved but left the document thinking it had nothing more to save: the file kept the edit, and the undo was lost when the app went to the background. Once a save is written, whatever the project has done since, edits or an undo back past it, is now still to save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rail's foot had a hand that turned finger painting on and off. It looked like the Hand tool, said nothing when it changed, and its choice outlived the Pencil: launched with no Pencil at hand, fingers could only move the canvas. iPadOS doesn't tell an app whether Apple Pencil is paired or connected, only what it does. So each launch starts with fingers drawing and no switch. When the Pencil first touches, hovers, or is tapped or squeezed, the switch comes in, in the toolbar just before Undo and Redo since it's a mode for the whole app rather than a tool, and unless it was left with fingers drawing too, only the Pencil draws from then on, with fingers moving the canvas. The switch shows who draws, a finger (hand.tap) or Apple Pencil (applepencil.and.scribble), and morphs as it's flipped; the status line says "Draw with Apple Pencil only" or "Draw with fingers and Apple Pencil" in the accent color, and to VoiceOver, for three seconds before going back to the tool's hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before the slow work is made faster, see where its time goes. `Timing` marks a stretch of work as an interval in Instruments' Points of Interest and, once it's done, writes a line to the console beginning with ⏱: how long it took and what it was of, a canvas's size, its images and masks and their megapixels, a file's bytes, never a project's or a layer's name. Work that throws or is cancelled isn't logged. Reading a package and encoding one, rendering the flattened image, the Quick Look preview, and encoding PNG and JPEG exports are timed here, on the Mac as on iPad. The GPU canvas can say when a frame it presents has been drawn, for timing a first frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Levels, Curves, Hue/Saturation, a filter or an adjustment layer's editor open, anything that brought another tab forward left the editor over the new tab, still editing the old one: New Canvas, Open Recent, Duplicate's copy, a project opened from Files or from another window, a closing tab's failed save. The canvas behind it couldn't be reached again, OK applied the edit to the hidden tab and could leave a spinner, and Cancel left buttons that did nothing. Open Project, the imports and Rename did nothing at all under an editor. While such an editor is open, from the moment an adjustment layer is chosen for editing, what would switch or close tabs is dimmed, as on the Mac: the other tabs, every close button and the tabs' menus, the toolbar's New Canvas, and New Canvas, Open Project, Open Recent, Duplicate and Close Tab in the menu bar, with the imports and Rename. A project opened from elsewhere meanwhile opens at once in a tab behind, and the last one asked for comes forward once OK has applied the edit or Cancel has let it go. A closing tab's failed save or changed-on-disk question waits the same way, then comes forward after any project opened meanwhile, so it isn't left behind one. An effect's panel still gives way to another tab, OK'd, and a tab that's closing never holds the window. Those menu bar commands are dimmed too while anything but an effect's panel is over the window, an alert, a sheet or a picker, as Export already was. So are Levels, Curves, Hue/Saturation, the filters, Edit Adjustment and the new adjustment layers but Invert, whose editor couldn't come over it; an edit begun under one anyway, as an adjustment layer's when a question comes up while it gets the pixels beneath it, shows its editor once that has gone, rather than holding the window with nothing to show. The shared project-operation check, and so the Mac, is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An adjustment's editor follows its edit only while it's open, and the window followed only the edit of the tab in front, so an editor whose edit ended while another tab was in front stayed up: with a spinner after its OK, or with buttons that did nothing after its Cancel. The window now watches whether the editor it shows is still open, whichever tab it edits, and puts it away once it isn't. Nothing leaves an editor over another tab since the editor keeps its tab in front, so the test sets that state up directly: Levels open on a tab behind, its editor shown over the tab in front. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… are With Image Size, Canvas Size, Export JPEG, Expand, Contract or Feather, a Photoshop or RAW file's sheet, an alert such as Rename or an error, or a picker the window shows (Open Project, the imports, the rail's colors) up, anything that brought another tab forward left it over the new tab: a project opened from Files or from another window, a Duplicate's copy, a closing tab's failed save, whose question came up over the dialog and went with it. The colors and images picked went into the tab that came forward, and Export JPEG's share sheet came up over it, as Export PNG's did over a tab that came forward while it made its image. Whatever the window shows over a tab but an effect's panel now holds the window on that tab, as an adjustment's editor does, from the moment it's asked for, though an effect's panel may still be going to make way for it, until it has gone; a menu too, a tab's own among them, which stays up as the tabs are dimmed where they are. The other tabs, the close buttons and New Canvas are dimmed, as the menu bar's commands and the tabs' menus already were, and a project opened from elsewhere meanwhile opens at once in a tab behind, the last one asked for coming forward once OK or Cancel is done. An empty tab in front no longer takes such a project, so what a picker over it picks still goes there. Closing tabs' questions wait the same way and come one at a time, each over its own tab, holding the window while it asks. A tab saving as it closes stays in front while something asked for over it meanwhile is up, and closes once that's done, saving what it did. Export JPEG and Export PNG hold the window while they make their image, and their dialog or share sheet after. A project picked in Open Project opens once the picker has gone, if it's still going when it says what was picked. Until what's asked for is up, as UIKit readies a share sheet or a picker, and while an export makes its image, the menu bar's commands that show something are dimmed, another export among them, a drop on the window is refused, and what the editor asks, as an error, waits, as what's to come couldn't come up over them. Nothing tells the window when an alert or a picker that puts itself away has gone, but UIKit dims the window's tint behind whatever it shows and brings it back as the last of it begins to go: the window looks again once that transition is over. A menu leaves the tint as it was, so while the window is held with its tint as it was, it looks twice a second. Tests that put the changed-on-disk question away and answered it a moment later now answer it as its buttons do, as it goes, since the window, quick now to notice it gone, asks again meanwhile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With an effect's panel over one tab, another whose save failed as it closed came forward, the panel making way for it, and asked over the panel as it went: UIKit didn't show the question, and the tab stayed with nothing to say why. An error that came as the panel went, as for a project from Files that couldn't be opened, was lost the same way. The question now comes from the window itself, which the panel gives way to, as it may be doing already; an error comes over what the window shows unless that's going, from the window itself then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A save on iPad waits for the project's Quick Look preview on UIDocument's writing thread, at user-initiated priority, while the preview was made in a task of its own at the default priority. A semaphore has no owner, so the wait raised nothing, and Xcode's Thread Performance Checker reported the priority inversion once a run, at the first save. The preview is now made at the priority of the thread that waits for it, which Task.currentPriority reads outside a task, so it follows whatever priority UIDocument writes at rather than a fixed one. The checker no longer reports it. Duplicate's copy, also a task of its own, is awaited, which raises it, so it stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whoever exports an image can now be told how far the export has come, on the exporter's thread: - Rendering says how many layers it composes before it starts, those it draws, leaving out hidden layers and folders, and each one as it's composed, in order. - Encoding as PNG or JPEG says that it encodes, once the image is composed. The Mac tells nothing, and renders and encodes as before; no ⏱ line changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ort JPEG's dialog The rail's color picker and a mask's Black or White choice, the Brush and Shape bars' colors, the Type bar's font and text color, and the Layers panel's Rename… came up whenever they were asked for, where the menu bar's commands that show something wait for a clear window. Asked for while an export made its image, they took the window: Export PNG's share sheet then couldn't come up and was lost without a word, and Export JPEG, finding the window taken, dropped its export. A menu that came up meanwhile, as a Layers panel button's, dropped it the same way. They're now dimmed, as those commands are, and asked for anyway they show nothing, while the window shows anything but an effect's panel or an export is under way: from the moment an export begins, and back once what it shows has gone. Each asks the window as it's asked for, so Rename… chosen from a layer's menu, which comes once the menu has gone, finds the window clear, though the window hasn't looked again. While what it shows is one of these pickers, they show nothing more but aren't dimmed until it begins to go: nothing in the rail or the bar can be reached under it, and their swatches show its color as it's picked. The rail's swatches, which looked the same dimmed or not, now fade when dimmed, as the bars' do. Export JPEG's dialog, once its image is made, waits for anything else over the window to go, the window held on its tab and the project busy meanwhile, rather than being dropped; closing the window lets the project go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… does Export PNG left the project free while it made its image, where the Mac's holds it, as Export JPEG's does on iPad: a layer could be painted, added or renamed meanwhile, though the image is the project as it was when the export began. The project is now held from the moment Export PNG is chosen until its image is made and its share sheet asked for, or the export fails, and the status line says it's working once that takes a while, as for any operation that holds it. An effect's panel now goes as an export begins, its effect kept as its OK keeps it, as it goes for anything else the window shows. It stayed up while the export made its image, its sliders changing nothing in the held project, and gave way only to the dialog or the share sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s file An export showed nothing while it made its image: Export PNG's share sheet or Export JPEG's dialog came up anywhere from a moment to a few seconds after the command, the window held meanwhile with nothing to say why. Each export now shows a card over the canvas of the tab it exports, as the Loading card shows over a project opening, from the refresh the export is chosen in: "Exporting “<name>” as PNG…" or "as JPEG…", the image's size, and a line for each step the export really takes, the one under way counting as it goes: - waiting its turn, should the exporter be making something else, as a save's preview: "Waiting to compose the image…"; - composing each layer drawn, in order: "Composing 3/8 layers…"; - encoding: "Encoding PNG…" or "Encoding JPEG…"; - opening what shows the file: "Opening the share sheet…" or "Opening the dialog…". Each step done stays as a line saying what it did. The steps come from the exporter's thread in the order it says them. Export JPEG now encodes its first JPEG, at the quality last exported, before its dialog opens, so the dialog opens with its preview ready to export rather than encoding it a moment later; its card goes once the dialog is up. Export PNG's goes as its share sheet is asked for. The card is the Loading card's, which now shows either; VoiceOver hears its title as it appears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export JPEG's Export… dismissed the dialog first and only once it had gone wrote the file and asked for the share sheet: about six tenths of a second of the dialog going and a third of a second of nothing before the sheet showed, though the JPEG was encoded before the button could be pressed. The share sheet now comes up over the dialog at once, from its Export… button, the project still held. Once the JPEG is shared, as by Save Image or Save to Files, the sheet and the dialog go together and the project is free. Put away with nothing shared, as by a tap off it, the sheet leaves the dialog as it was, to change and export again, where the Mac's Save panel's Cancel ends the export. Once the dialog has gone, however it goes, the canvas has the keyboard again, which UIKit gives back to nothing once a share sheet has taken it, as a tap on Save Image or Copy does: ⌘Z and Tab did nothing until the canvas was tapped. While the sheet is up, Return, Escape, Export… and the View menu's zoom commands aren't the dialog's: Return would export again under it, and Escape cancel what it shares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export PNG asked for its share sheet as soon as its file was made, whatever was over the window then: with a menu up, as a Layers panel button's, which nothing dims while an export makes its image, UIKit refused the sheet and the export was lost without a word. The share sheet now waits for anything else over the window to go, as Export JPEG's dialog does, the window held on its tab and the project busy meanwhile, the card saying it's opening the share sheet; closing the window meanwhile lets the project go, as it does for the dialog. The card stays until the sheet is up, rather than going as it's asked for, which UIKit takes a moment over, and the project is free once it's up. Once the sheet has gone, shared or not, the canvas has the keyboard again, as it does once Export JPEG's dialog has gone: UIKit gives it back to nothing once the sheet has taken it, as a tap on Save Image or Copy does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every export wrote its file straight into the app's temporary folder, named after the project: two windows exporting projects of the same name wrote the same file, so one share sheet could offer the other's image, and a write finishing late could change a file a share sheet still offered. Each file now goes in a folder of its own, under Exports in the app's temporary folder. A folder goes once its file's share sheet is done, shared or not, as the next export writes its own; folders left from before, as by an earlier launch, go then too. A file a share sheet still offers stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The share sheet an export offers its file in never listed Save Image, though the code said an export could be saved to Photos: iPadOS offers it only to an app that says why it adds to the photo library, which it asks the first time, and the app didn't say. It now does, in its generated Info.plist: "Compositor saves the images you export to your photo library." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The activity a window's tabs are kept in, for iPadOS to bring them back after a relaunch, was spelled out twice, in the code and in the Info.plist's list of the types iPadOS may hand back, both in the original bundle identifier's name. A build of your own under another identifier kept a type in someone else's name, and changing one without the other would have lost every window's tabs, as the window turns away an activity not its own. Both now come from PRODUCT_BUNDLE_IDENTIFIER, with ".window" after it: the code reads the bundle's identifier, and the Info.plist's entry is filled in from the setting as the app is built. Nothing else names the type: the scene delegate hands what iPadOS gives back to the window, which checks it. The app now says it uses no encryption that export rules count, so App Store Connect stops asking about export compliance with every upload: it sends nothing anywhere, and the SHA-256 hash that fingerprints a project is exempt. CompositorPad.xcconfig says where a build of your own sets its bundle identifier, display name and build number, with its signing team: in the local file git ignores, which it already includes. The test checks the type against the bundle's identifier and the Info.plist's list. Built under another bundle identifier, it failed before the change, the type still the original's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPad app showed its license nowhere, though the MIT license asks for its notice in every copy, and the app menu's About, which iPadOS hands over empty, had nothing in it. About Compositor now leads that menu, as on the Mac, and brings up a sheet over the tab in front: the app's icon, its name, and its version and build, read from the bundle so a build of your own shows its own; a link to the source on GitHub; and the license, from a copy of the repository's LICENSE that the app carries, each paragraph wrapped to the sheet's width, to scroll and select. Done or Escape puts it away. The sheet comes up as the window's other sheets do, holding the window on its tab while it's up, and About is dimmed while anything but an effect's panel is over the window, as the menu bar's other commands that show something are. The Mac's About panel is unchanged. The copy of LICENSE sits in CompositorPad/Dialogs, which the project takes into the app as it is. A test checks it's the repository's byte for byte, so the two can't drift apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A double tap on a layer's eye in the Layers panel hid the layer and asked for a new name: the row's double tap took the second tap from the eye, as UIKit keeps only a button's single tap from a gesture on the view around it. The mask's link and a folder's disclosure did the same, and an effect's eye opened the effect's panel. On the Mac each of those buttons tracks the mouse itself, so the list never sees a double click on one: it is two clicks, and an eye hides its layer and shows it again, two steps to undo. The row's taps now leave its buttons alone, so a double tap presses one twice, as on the Mac. A button is found by where the finger is, not by the view UIKit hands the touch to, as UIKit passes over a disabled one: while text is typed, or a transform, a crop or a gradient waits for Apply, a tap on a disabled eye selected its layer, which ended the typing or applied the transform, and a double tap went on to ask for a name. A disabled button now takes its taps and does nothing with them, as the Mac's swallows a click. A touch UIKit hands to a thumbnail stays the row's, though the link's turned bounds reach under a corner of the mask's. A layer's effect rows are kept while the effects it lists stay the same, and updated in place, rather than made again with every change: now that the row leaves an effect's eye its taps, a second tap that came down before the row showed the first would lose the eye taken away under it. A double tap on the layer's thumbnail opens what the layer holds, as the Mac's does: an adjustment's editor, and a text layer's text, which renamed the layer before. The mask's thumbnail, the red mark over a disabled mask included, now counts as a thumbnail too, as on the Mac; it renamed before. On the name, and on a pixel layer's thumbnails, a double tap still renames, the mask's corner under the link included. The tests reproduce how UIKit hands a double tap over, and double tap each part of a row, checking what changed, what's left to undo, and whether a name was asked for. Those for the eyes, the disabled eye included, the link, the disclosure, the mask's thumbnail and text failed before. Another checks that an effect's eye moves in with its layer's indent, as the rows now kept must be moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPad's checkboxes drew the system's square box on the capsule UIKit puts behind a selected button of its own accord, whose ends, rounded by 16, didn't follow the box's corners, rounded by about 3; and the checked box came out white only because the tint it asked for was taken from the label color. The box, as chosen from the mockups, is now 20 square and sits 6 in from the top and left of a highlight 32 tall whose corners are rounded by 8.84, the box's 2.84 and those 6, so the two share a center. The title starts 9 after the box and ends 12 before the highlight does, so each checkbox is 8 points wider than it was and what follows it moves along. Checked, the highlight is the tint at a quarter behind the box and the title, and the box is filled in white with the check cut out, so the tint shows through; unchecked, there's no highlight, and the box is an outline in the secondary label color. A checkbox that can't be changed draws its box and title in the tertiary label color with no highlight, checked or not, the filled box saying it's on. The box is the system's symbol, drawn so its outline is exactly 20 across, which the symbol's own image, with room around the outline, isn't, and filled with its color as far as the symbol covers each pixel: drawn in the color itself, the symbol came out darker along its left and bottom edges. With Bold Text the box is drawn heavier, as the system draws its symbols then, and its outline measured at that weight, so it's still 20 across and none of it is cut off. It's the same size on or off, so checking moves nothing. The 2.84 is the symbol's corner, 0.142 of its side, as UIKit's continuous corners count it. Drawn at 8x, the highlight's edge runs 5.76 to 6.13 outside the box's round the corner, 6 on average: the symbol's corner isn't quite continuous, which parts the two curves by that little, under half a pixel either way on the screen. The title now stays on one line. Hue/Saturation's editor, coming up as a sheet in a narrow window, squeezes its row for a moment, and the wider Colorize wrapped there and stayed wrapped once the row had room. It shows wherever a checkbox does. In the option bars: Transform's Auto Select and Show Controls, Clone Stamp's Aligned, Magic Wand's Contiguous, the selection tools' Anti-alias, on all but the rectangular Marquee, Gradient's Reverse and Eyedropper's Sample Ring. In the editors and dialogs: Preview, Hue/Saturation's Colorize and Apply outside this range instead, the filters' Monochromatic, Reverse, Light on Dark, Tint and Preserve Luminosity, Canvas Size's Relative and Lock original aspect ratio, and Image Size's Lock aspect ratio and Resample. Transform's aspect-ratio lock, a symbol alone, was a checkbox with no title that the bar restyled; it's now made as what it is, and draws as it did. The tests check the highlight's color and corners, the box's place, size and colors, edges and all, its shape while it can't be changed, its weight with Bold Text, the squeezed title, that checking moves nothing, and that the lock draws as it did, pixel for pixel. The first five failed before the change; the last two held before it too, and fail when the spacing changes between on and off or the lock takes the new look. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Xcode 26's compiler inferred withLock's result in a failed save's message from the CocoaError after `??`, and stopped there; Xcode 27's took the stored error's type. Reading it into a typed constant first builds on both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the hosted runner, one at a time, the 541 iPad tests outlast the job's hour, and six that wait on the window or a picker run out of their time there; on several clones at once, a minute's limit ends the run after about 40. They all pass on iOS 26.5 on a Mac. CI still compiles the app and the tests with Xcode 26.6, which is what caught the build failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
naco-siren
marked this pull request as draft
October 10, 2026 04:15
Upstream's 1.4.6 to 1.5.1: 16-bit and HDR layers (format versions 12 and 13; EXR and Radiance HDR import, EXR export, conversion between depths), CMYK proofing and export, Export As (PNG, JPEG, PDF, EXR), Progressive Blur and Scanlines, HEIF and WebP import, Spot Healing and Content-Aware Fill rebuilt on multi-scale PatchMatch, Camera Raw matched to Photoshop with section resets, New Canvas in units and DPI with a background, Rotate Canvas, Last Filter, the command palette, Canvas Only, the Navigator, middle-drag zoom, Sparkle updates, the Screenshots UI tests, a new icon, and our PR robbietilton#181 as upstream merged it. Rendering overlays and the Mac's panels (EditorCanvas, TransformOverlay, LayersPanel, NativeLayerList, NewCanvasSheet): upstream's additions kept; where it edited code this branch had moved into shared files, the edit went there instead: Progressive Blur's line to CanvasOverlay, the depth tag to ImageLayer.depthLabel beside sizeLabel. The brush-axis fields stay replaced by BrushAxisLock; the effects button's stale help, fixed on both sides, takes upstream's "Add layer effect". IO and the GPU canvas (ImageExporter, ProjectStore, GPUCanvas): upstream's 16-bit and half-float contexts, EXR, TIFF and PDF encoders and CMYK conversion merged with the Progress parameter, render(_:scale:), the Timing marks, the four-at-a-time package reads and EncodedImages' reuse of unchanged bytes, now keyed by an image and its 16-bit copy so a converted layer isn't saved from the old PNG; a mask's file stays its 8-bit image, as upstream writes it. Textures prepared off the main thread take a deep image's format. The merge also times upstream's TIFF, EXR, PNG and PDF encoders, so the Mac's Export As previews log ⏱ lines upstream doesn't. Document (EditorSession+Projects, FloatingSelection, LayerMerge, SelectionClipboard, Dither, ImageResizer, ImageFileDrop): upstream's text for robbietilton#181 (the same change as ours), its 16-bit lifts and merges, with the beep and the pasteboard through Platform.swift, whose SystemPasteboard gains upstream's pasteboard per test process and the reads Paste as a new canvas needs; the HEIF and WebP drop types went to ItemProviderFiles. Filters, the session and the pixel tools take upstream's changes beside the shared seams; Content-Aware Fill keeps our engine (ContentFill.c, kept beside upstream's PatchPixels.c, which Spot Healing's Content-Aware mode uses): both sides rebuilt the fill, and on ContentFillTests upstream's PatchMatch fill takes 13 s on the 12-megapixel case and 8 s with sparse sources where ours takes under a second, misses the light across a hole by up to 7.5 levels, refuses a layer too thin for a patch and can't be cancelled; a different engine is one call in ContentFill.swift. iPad: UpdateDriver.swift joins MAC_ONLY_SOURCES; DeepLayers.swift and CMYK.swift import the frameworks they use rather than AppKit; the canvas takes the Mac's three 16-bit hunks, drawing a 16-bit or HDR layer from its own pixels (DeepLayer.canvasImage, which the textures prepared on open already held), an adjustment over an HDR layer working on its full range, and the frame dithered; the unreadable-items message and the document types name HEIF and WebP, and the types EXR and Radiance HDR too, which the shared importer reads; the Dither editor loses its Scanlines rows, Scanlines being a filter of its own now; the menu bar follows the Mac's again (exports in its order, one View › Snap, the effects button's help), and CanvasRotationTests, RawDitherTests and ScanlinesTests join the iPad's test run, where DitherTests, gone upstream, leaves. The UTType statics read off the main actor are nonisolated, as photoshopImage was. CI: ContentFillTests runs with the window suites, one at a time, and holds its fills to their bounds anywhere but the hosted runner: beside the parallel clones on its three cores the 12-megapixel fill took over 40 s and a clone timed out launching. Going over the merge: the iPad's RAW develop dialog stays up saying Importing…, its controls disabled, while the full frame develops, and the window takes it down once the layer is in, where it dismissed itself and came up a second time (upstream keeps showsRawDevelop on through the develop); EditorSession gains developsRawAtSixteenBits (on by default, the Mac unchanged), which the iPad's window turns off so a RAW develops at 8 bits as before the merge, a 48-megapixel frame at 16 bits peaking near 1.3 GB on the 3 GB iPad Air while the import check and the pixel budget still count four bytes a pixel, lifted once deep layers are counted at their cost and measured on the device; and a hand-written 256 × 192 linear-raw DNG lets the Mac tests run a real develop through the session. Deep files go through the package one at a time (ProjectStore.deepSlot), a 16-bit PNG's or EXR's decode and encode taking a single slot while 8-bit files keep their four lanes, since four whole deep layers in flight could be all a 3 GB iPad lets an app have. Content-Aware Fill and Spot Healing hold their CGContexts alive until the C function reading their pixels returns, as upstream 93f8e8d requires of optimized builds. The iPad canvas draws a 16-bit layer from its own pixels (DeepLayer.canvasImage) only where the depth shows, under an adjustment that works on it or with an HDR layer in the document, and otherwise from the dithered 8-bit copy as before the merge: painted, that image is the whole layer flattened at 8 bytes a pixel, made on the main thread at each committed stroke and kept six deep with nothing to release them under memory pressure, which a 3 GB iPad can't carry; held to a budget, a shared change, the cache would let the iPad follow the Mac. PadCanvasView.dithersFrame says whether the frame is dithered on its way to the screen, so a test can check it. Round-trip tests cover 16-bit and HDR layers (read back, they save as the bytes they were read from without encoding; Convert to 16-Bit and to 8-Bit each encode the layer again); docs/project-format.md names the .exr file an HDR layer has from version 13; DeepLayerTests and ColorOverlayTests import the frameworks they use instead of AppKit and the iPad test target compiles them too; the iPad's comments naming the Mac's Export JPEG dialog, gone in 1.5.1, now say the JPEG settings of its Export As; and the iPad's unreadable-drop message also names OpenEXR, Radiance HDR and camera RAW, which the shared importer reads since 1.5.1. Listed dimmed until the iPad does them: Export As, Rotate Canvas, Last Filter, Progressive Blur, Scanlines, Search Commands, Toggle Fullscreen, Navigator, CMYK proofing and Convert to 16-Bit and 8-Bit. New Canvas stays in pixels at 72 DPI, transparent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sparkle starts a second after launch and, finding a release newer than the build, puts up its alert: modal, on the main thread, before XCTest has begun, so the test host hangs until xcodebuild gives up on it. CI saw it the day after 1.5.2 shipped, on a branch still at 1.5.1; any branch behind the latest release would. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Four commits: Camera Raw's Shadows adapting to the picture, and a RAW import from Camera Raw… opening the Camera Raw Filter once the layer is in. The one conflict was in the RAW import: upstream's two lines after the insert sit beside the iPad's 8-bit develop (`developsRawAtSixteenBits`).
naco-siren
marked this pull request as ready for review
October 14, 2026 15:58
Nine commits: smooth rectangle corners (format version 14), middle-drag scrolling of the layer list, zoom cursors that follow the drag's direction, and Camera Raw previews at full size, with sparser scopes and a Metal color lookup for large layers. Three files conflicted, each where this branch had moved code upstream then changed. EditorCanvas takes upstream's middle-zoom state (lastY, zoomingIn); Shift's straight stroke stays BrushAxisLock and the text being edited stays TextDraftRendering's, neither changed upstream. Filters keeps previewWork(_: hdrSource:) as the preview's one worker, taking upstream's discarding of the Metal buffers when a preview comes back stale or cancelled. The format doc says version 14, 1-13 readable, and keeps the EXR sentence. Two upstream changes went to the shared code that moved, where git would have dropped them without a conflict: the Shape tool's draft passes draft.smoothing in ShapeDraftImage's drawShapeDraft, so both canvases drag out the corners the layer gets, and previewWork measures Camera Raw's brightness once on the worker and returns it for the edit to keep. A shared DraftDrawingTests case fails without the first, upstream's CameraRawTests without the second. ProjectManifest.current is 14, and ProjectStore.validate refuses smoothing in an older version, on the Mac's saves and loads and the iPad document's alike; PadDocumentTests saves and opens a smooth rectangle through CompositorDocument at 14, and of two files saying version 13 opens the one without smoothing and refuses the other as invalid. ShapeToolTests, with upstream's smoothing tests, imports the frameworks it uses rather than AppKit and joins the iPad's test run. The iPad's Shape bar follows the Mac's: the fill swatch comes straight after the shape, without its Fill caption, and a rectangle has Smooth Corners after its radius, dimmed at a radius of 0 with a VoiceOver hint saying why. MetalCameraRawLookup is shared and builds for the iPad, but nothing runs it there: the iPad has no Camera Raw editor yet, so the full-size previews and the Metal lookup wait for one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream main is 134 commits past 1.5.3, through 1.5.12 and Volumetric Light's second depth model (50d632b). The project format goes from 14 to 20: line styles and caps (15), Progressive Blur, Bloom / Glow and RGB Split layers (16), GIF layers (17), Vignette, Lens Effects and Tonal Contrast layers (18), Sharpen (19) and Lens Effects' prism fringe (20), with 15 and 16 reopened (robbietilton#313). Our side saves nothing new, so there is no bump of our own. Ten files conflicted. Each is our side having moved Mac code into shared files the iPad compiles, or having changed shared logic, where upstream then changed it in its old place: - DocumentHistory: upstream's endingEditBefore beside our end(document:selection:coalescing:). - EditorSession: the Hand's hand.raised symbol beside our samplesColorWithOption; endEdit(coalescing:) keeps 16-bit and HDR layers' depth (keepLayerDepths) and prunes GIF timelines around our coalescing end; canEditLayers is transformEdit == nil over upstream's canEditLayersOnceTransformCommitted (which Align uses), itself textDraft, crop and gradient nil over our canEditBesideCanvasEdits. - SelectionEdits, SmudgeLiquify: upstream's GIF frame editing (selectedMask, pauseGIFForEditing, pixelEditingLayer) with our finishHeldDrags. - ProjectStore: GIF persistence moved into our static package(for:) and readPackage. The checks run first, the .gif and edited frames among them; frames are encoded and reused with the layers' images (the four lanes, EncodedImages) and read lazily as PackedImage entries; the GIFs are decoded once every image is read. validateAnimation, checkGIFBudget and checkFile are nonisolated static; the 512 MiB cap on an encoded file moved into encode(). importableImages gains GIF and PDF, staying nonisolated. - ImageFileDrop: ours; upstream's .gif goes into ItemProviderFiles, where dropped items are copied now. - EditorCanvas: ours for the text draft, the shape draft and Shift's axis lock (all moved), with upstream's pen pressure, the adjustment-result and Lens Effects caches, the drafts' transient prints, and the displayed GIF frame combined with our filter previews on layers with effects. - TransformOverlay: ours, a thin view over CanvasOverlay. - ImageSizeSheet: ours; robbietilton#318's limits go into ImageSizeDraft. - NativeLayerList: ours; the GIF tag goes into the shared depthLabel. Upstream changes to code that lives elsewhere now, which merged without a conflict into a stale copy or not at all, are ported to its home: - ShapeDraftImage draws a line being dragged out in its Style and Cap (strokeShapeLine), on both canvases. - CanvasOverlay marks RGB Split's center and Volumetric Light's light, and draws Progressive Blur's line beside the frame's handles, as d157548 does. - ImageSizeDraft.scrubPixelRange takes Image Size's scrubbing limits from DocumentLimits (robbietilton#318); ImageSizeTests use it. - ImageLayer.depthLabel says GIF for an animated layer. - LayerType and the Layers panel's search and type filter move out of the Mac-only LayerSearchControls into Document/LayerSearch.swift, since the shared EditorSession now holds them. - VolumetricLight beeps through Platform.beep, without AppKit. - The new GIF frame edits and Align end held drags (finishHeldDrags), as our edits do. - ProjectRoundTripTests converts depth through convertLayers(toSixteenBit:). - ItemProviderFiles copies in a dropped PDF, as the importer reads PDFs now. - The format doc lists a GIF layer's files and version 27 among those read. The iPad: - Opens, shows and saves every new kind and format through the shared ProjectStore, and round-trips a version 20 project with all of them through CompositorDocument (PadDocumentTests). - Its canvas compositor draws a GIF layer's displayed frame, passes the document's size to the adjustments (Vignette frames the canvas, Lens Effects measures by it), gives Lens Effects its kept source, and leaves the canvas showing when an RGB Split has nothing under it. - Picks Rim Light's and Volumetric Light's colors with the system picker. - Bloom / Glow, Vignette and Tonal Contrast layers are created and edited with the iPad's filter editors. Progressive Blur, RGB Split, Lens Effects and Sharpen layers have no editor yet: they open, show and save, and are listed dimmed in the Layers panel, as the Layer menu lists them. - The new filters (Sharpen, RGB Split, Lens Effects, Volumetric Light, Rim Light) are listed dimmed in the Filter menu. - Never fetches Volumetric Light's depth model: DepthModel.prepare() is false on iOS, so the app still sends nothing anywhere, whatever comes to offer Volumetric Light, and the fog keeps to its own guess. - The Filter menu and New Adjustment Layer, in the Layer menu and the Layers panel, are grouped and ordered as the Mac's. - Takes PDFs dropped from Files, as its import picker does, and names GIF and PDF among what it reads. - The line width slider runs 1–50, as the Mac's. - The Crop bar offers the Mac's ratios, Canvas for Original among them, and each one but Free holds the frame to its shape (PadToolOptionsTests). - Canvas Size keeps the zoom, as on the Mac. - The tests follow upstream: Undo Import, the selection tools' opacity digits, the Filter menu's new kinds, the saved version. Upstream's GIF layer, Lens Effects, PDF import and Volumetric Light tests run on the iPad too (GIFLayerTests no longer imports AppKit, which it didn't use); Rim Light's need Vision's subject mask, which the simulator can't make. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two Volumetric Light commits: its depth finds strands of hair from each person's pose, and uses the person mask only when it covers most of what the general mask found. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ContentFillTests holds its fills to a time only off the hosted runner, where they share three cores with other test processes, and reads GITHUB_ACTIONS for it. But xcodebuild hands the app it tests only the variables named TEST_RUNNER_<name>, so on CI the bound held all along, and a 5.2 s fill against 5 s failed the windows group on PR robbietilton#254. The script now passes GITHUB_ACTIONS on under that name. A probe test showed the bound on with GITHUB_ACTIONS alone and off with the script's line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The iPad port from #203, as a branch you can browse and pick from. I don't expect it merged as one: from your answers on #216 and #222, the iPad app itself belongs in my fork, and that's where it's distributed (TestFlight link in #203). It's here so that any of the shared-code commits below are one cherry-pick away, if you ever want them.
TestFlight build. Screen recording on iPad Air 3 (2019, A12 Bionic):
demo.1.mp4
141 commits, each with one purpose, now on top of 1.5.1:
mainis merged in (a merge, not a rebase, so the hashes below hold). 601 tests pass on an iPad simulator on iOS 26.5, and 769 on the Mac. CI builds the iPad app and its tests with Xcode 26.6, which caught one line only Xcode 27 accepted, and runs the Mac's tests; the iPad's run on a Mac for now, since on the hosted runner they outlast the job's hour.Everything under
CompositorPad/andCompositorPadTests/is the iPad's own. These are the commits that touch code the Mac runs:Mac unchanged: code moved or opened up so the iPad can share it
Mac faster, same results
Mac bugs, each reproduced by a failing test first
Mac changes you may or may not want
patch_fillfor Spot Healing, as 1.5.1 does. Your PatchPixels and HealPixels files are untouched;ContentFill.swiftis the one that differs.The iPad target: c31a33d adds it to the project file (the Mac target untouched) and 58fdf5a gives
SystemPasteboardits iPad half.Checked against 1.4.6–1.5.2 while merging: besides 22445da above, my 6c36c2a (naming all six effects in the effects button's help) was superseded by your f25effa, and the merge keeps your text. The other fixes have no counterpart in
main.