owner888 / myphp
Desktop apps in PHP, with native UI or a web frontend, over ext-ffi.
Requires
- php: >=8.3
- ext-ffi: *
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.168.0
- v1.167.0
- v1.166.0
- v1.165.0
- v1.164.0
- v1.163.0
- v1.162.0
- v1.161.0
- v1.160.0
- v1.159.0
- v1.158.0
- v1.157.0
- v1.156.0
- v1.155.0
- v1.154.0
- v1.153.0
- v1.152.0
- v1.151.0
- v1.150.0
- v1.147.0
- v1.146.0
- v1.145.0
- v1.144.0
- v1.143.0
- v1.142.0
- v1.141.0
- v1.140.0
- v1.139.0
- v1.138.0
- v1.137.0
- v1.136.0
- v1.135.0
- v1.134.0
- v1.133.0
- v1.132.0
- v1.131.0
- v1.130.0
- v1.129.0
- v1.128.0
- v1.127.0
- v1.126.0
- v1.125.0
- v1.124.0
- v1.123.0
- v1.122.0
- v1.121.0
- v1.119.0
- v1.118.0
- v1.117.0
- v1.116.0
- v1.115.0
- v1.114.0
- v1.113.0
- v1.112.0
- v1.111.0
- v1.110.0
- v1.109.0
- v1.108.0
- v1.107.0
- v1.106.0
- v1.105.0
- v1.104.0
- v1.103.0
- v1.101.0
- v1.100.0
- v1.99.0
- v1.98.0
- v1.97.0
- v1.96.0
- v1.95.0
- v1.94.0
- v1.93.0
- v1.92.0
- v1.91.1
- v1.91.0
- v1.90.0
- v1.89.0
- v1.88.0
- v1.87.0
- v1.86.0
- v1.85.0
- v1.84.0
- v1.83.0
- v1.82.0
- v1.81.0
- v1.80.0
- v1.79.0
- v1.78.0
- v1.77.0
- v1.76.0
- v1.75.0
- v1.74.0
- v1.73.0
- v1.72.0
- v1.71.0
- v1.70.0
- v1.69.0
- v1.68.0
- v1.67.0
- v1.66.0
- v1.65.0
- v1.64.0
- v1.63.0
- v1.62.0
- v1.61.0
- v1.60
- v1.59.0
- v1.58.0
- v1.57.0
- v1.56.0
- v1.55.0
- v1.54.0
- v1.53.0
- v1.52.0
- v1.51.0
- v1.49.0
- v1.48.0
- v1.47.0
- v1.46.0
- v1.45.1
- v1.45.0
- v1.44.0
- v1.43.0
- v1.42.0
- v1.41.0
- v1.40.0
- v1.39.0
- v1.38.0
- v1.37.0
- v1.36.0
- v1.35.0
- v1.34.0
- v1.33.0
- v1.30.0
- v1.29.0
- v1.28.1
- v1.28.0
- v1.5.3
- v0.62.0
- v0.61.0
- v0.60.0
- v0.59.4
- v0.59.3
- v0.59.2
- v0.59.1
- v0.59.0
- v0.58.0
- v0.57.1
- v0.57.0
- v0.56.1
- v0.56.0
- v0.55.0
- v0.54.1
- v0.54.0
- v0.53.0
- v0.52.2
- v0.52.1
- v0.52.0
- v0.51.2
- v0.51.1
- v0.51.0
- v0.50.2
- v0.50.1
- v0.50.0
- v0.49.0
- v0.48.0
- v0.47.0
- v0.46.0
- v0.45.0
- v0.44.0
- v0.43.0
- v0.42.0
- v0.41.0
- v0.40.0
- v0.39.0
- v0.38.0
- v0.37.0
- v0.36.0
- v0.34.0
- v0.33.0
- v0.32.0
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.0
- v0.26.0
- v0.25.0
- v0.24.0
- v0.23.0
- v0.22.0
- v0.21.1
- v0.21.0
- v0.20.0
- v0.19.0
- v0.18.0
- v0.17.0
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.0
- v0.9.0
- v0.8.0
- v0.7.1
- v0.7.0
- v0.6.0
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.0
This package is auto-updated.
Last update: 2026-11-04 08:13:05 UTC
README
English
Desktop apps in PHP, with native UI or a web frontend.
MyPHP is a port of the ideas in mygo to PHP 8:
one process, one language, no C extension of its own. The operating system's
libraries are reached through ext-ffi.
Each window shows one of two kinds of interface:
- A web page in the webview the OS already has (WKWebView on macOS). The page calls PHP services through a typed bridge.
- Native UI, written in PHP alone and drawn by MyPHP on the GPU: no HTML,
no JavaScript, no webview. The view is a function of the app's state and
runs every frame (immediate mode), as in mygo's
uipackage.
A window with native UI, whose view is a function of the app's state:
use MyPHP\{App, Window, WindowOptions}; use MyPHP\UI\{Content, Context}; $n = 0; App::whenReady(function () use (&$n) { new Window(new WindowOptions(title: 'Counter', width: 320, height: 200, content: Content::view(function (Context $c) use (&$n) { $c->column(24, 12, 1); $c->text("Count: $n", 20); if ($c->clicked($c->button('Increment'))) { $n++; } $c->end(); }))); }); App::run();
Status
Early. What works today, on macOS / Apple Silicon:
| piece | state |
|---|---|
Objective-C bridge (objc_msgSend, classes with PHP methods) |
works |
| WKWebView window, JS → PHP → JS | works (examples/webview) |
| Native UI: Metal renderer, CoreText text, flex layout, scrolling on either axis, clicks, hover | works (examples/inbox, form) |
mygo's widgets and context, v1.08: toasts with actions, ids and closeToast; openURLThen; onClick, onChange, onSubmit, onShortcut; Handle and focusBind; toggle groups, step sliders with ticks, outlines and outline tables; a list's followAnimation, a table's roundRows and headerMenu; path gradients; Preferences (accent, Reduce Motion, Increase Contrast); registered fonts and Font::families; Oklch; Image::update; TextLayout for text controls of one's own; TextBuffer; every base widget without a look (checkboxBase … tooltipBase, selectBase, comboboxBase, the text inputs) |
works |
mygo's app, window, page and menus, v1.09: Page (Window::page(): loadFile, reload, back and forward, zoom, user agent, eval, window.open() handler, title, navigation, load and crash events, JavaScript panels as sheets); Event (emit, emitPage, broadcast); web views in native UI (Window::newWebView, Context::webView); the page's preferences, language and title bar room; App lifecycle events, activation policy, about panel, path, exit; Dock, Shell, Screen, Theme; Power::keepAwake; window state with its store; menu radio items, roles of the View, Format, Speech, Services, Window and Help menus; Tray::popUpMenu |
works |
mygo's element API: CSS flexbox (gaps, alignContent, wrapReverse, automatic margins, justify's spaces, least and largest sizes, percentages, aspect ratios), absolute and relative insets, attach/attachTo anchors, grid tracks; per-corner radii, gradients at any angle (sRGB or Oklab), stripes, dashed borders, box shadows inside and out with spread, dividers, debug, invisible, passThrough, inherited disabled, focusRing(false), scroll bars' insets, every cursor; pointerPosition, dragVelocity, dragEnded, pressedOutside, overlayShortcut, dismissed, a list's rowWidth with scrollWith, TextLines, textRanges, setTextSelection, textSelection, textAnchorCaret |
works |
Lists over a million rows: scroll bars, scrollTo glides, keyed rows, selection with keyboard navigation |
works (examples/inbox) |
| Images (PNG, JPEG, HEIC, ICNS…) in native UI, rounded and faded, fitted (contain, cover, fill, scale-down, natural size), in grayscale | works (examples/inbox, widgets) |
| Bold, italic and monospace text; rich text spans with color, underline, highlight, wrapping | works (examples/inbox, logview) |
| Text styles inherited from the nodes around (font family, size, weight, italics, line height, alignment, letter spacing, OpenType features, language, underlines, strikethrough, background), mygo's text system (CoreText typesetter, carets, selection geometry, bidi, ellipsis), paragraphs with links and styled text inside them | works |
Selectable text (mygo's Selectable): drag, double and triple clicks, Shift with the arrows across the paragraphs of a container, Copy and Select All, selectedTexts(), copyData() with data in several formats |
works |
Soft shadows, rings; materials (UI\Material); the glass plugin (mygo's plugins/glass, Plugins\Glass): Liquid Glass (blur, lens bend at the edge, lit rim, tint, interactive), progressive backdrop blur along a mask, scroll edges |
works (examples/glass) |
| Text fields and text areas: input methods, selection, wrapping, undo, Edit menu, Tab focus; checkbox, radio, slider, select, link, progress, tooltips | works (examples/form) |
| Switch, stepper, segmented control, tabs, collapsible section, split panes, table (sorted by one column or several, columns resized, fitted to their cells, moved, scrolled sideways), sidebar with sections, tree, accordion, rating, range slider, combobox, autocomplete, token field, date input, calendar, time input, color picker and well, find bar, form with fields and fieldsets, checkbox group, breadcrumbs, alert dialog, grid view (virtualized), multiple selection (Cmd/Shift-click, Shift+arrows, Cmd+A), rows and grid items dragged to reorder, grid layout (fixed/fr/fit tracks), SVG icons, avatars, hidden title bar (titleBar + dragWindow), toolbar with toggles, modal dialogs, popovers, menu buttons, toasts, search field, number input, meter, spinner, badge, divider, spacer; radio groups, editable text in place (a table's names), lists whose section headers stay at the top and whose rows typing their labels chooses, trimmed paths, tinted bitmaps, glyphs shaped and drawn one by one | works (examples/widgets, markdown-notes, http, gallery) |
| Theme: light/dark after the system, its accent color, live switching | works |
myphp init / dev / build: scaffold, run with restart, .app bundle (exec or static micro launcher) |
works |
Packaging as mygo's (v1.12): -platform darwin/arm64,darwin/amd64,darwin/universal, the resources directory and its platform directories (universal binaries combined), the icon from a PNG or an asset catalog, code signing inside out, the .dmg with its window laid out, notarization, -upload to GitHub releases or S3; myphp doctor, install-skills, version, help |
works (signing, the .dmg and notarizing need the developer's Mac) |
Development as mygo's (v1.13): myphp dev runs " Dev", a bundle with the app's icon and resources, built again on changes, r/c/q/h on the terminal; SIGINT and SIGTERM quit the app as Quit; myphp.config.ts; the pages' packages myphp-runtime (packages/runtime, which the generated client and the plugins import) and myphp-cli (packages/cli); the web template with Vite and TypeScript |
works (the development app needs the developer's Mac) |
Widgets and keys as mygo's (v1.14): password fields; a dialog keeps the window's shortcuts; lists whose place, choice and rows' state follow their items' keys, and scroll by the step into rows never measured; the keys scroll what the pointer is over, Page Up/Down in a list scroll as AppKit's; stepper Left/Right, slider Page Up/Down; tooltips for the keyboard focus, hidden by a press or Escape; Control-K in text fields; JPEG photos upright; a sidebar's typeahead; shortcuts by the key's place on other layouts; MYPHP_FRAME_STATS |
works (decoding photos, the frame statistics and other layouts' keys need the developer's Mac) |
Lists as mygo's tests have them (v1.15): rows of any height without gaps, a step of the wheel or a key moving them by the step, scrollTo at the top, middle or bottom as measured, the end followed as rows come or grow (gliding with a FollowAnimation), the focused row kept out of view, Tab showing the next row, the scroll bar's thumb as far through the rows as along its track, rows under a pinned header, a list as high as its rows in a scrolling container |
works |
Text fields and toasts as mygo's tests have them (v1.16): undo step by step (a caret move ends a step), the app's changes undone with the typing, the view kept where the wheel put it as the app adds text, fields showing their start without the focus and aligned with textAlign(), text input clients unmarked as the focus leaves them, toasts' time stopping under the pointer and in the background, Escape closing the toast the focus is in |
works |
Primitives, styles and the window as mygo's tests have them (v1.17): the focus into a modal overlay of the app's own, disabled sliders, onTextKey for the keys that type no text, the hover held by a press, grids aligning their cells, text areas' placeholders wrapping, form labels on a text area's first line |
works |
| Right to left, indicators, dates and comboboxes as mygo's tests have them (v1.18): texts aligned across their column, range sliders, splits, inputs, popovers and menus mirrored, word arrows the way they point in a right-to-left paragraph, steppers and range sliders taking quick clicks, color swatches named in the app's language, combobox popups read and scrolled as AppKit's | works |
Scopes, transforms, SVGs and web views as mygo's tests have them (v1.19): selects with mygo's popup in the window, popovers in the Tab order after their anchors, clips that cut off the pointer through transforms, carets and input clients through transforms, nodes taking text of their own (textCaret), icons in the font size and color around them |
works |
Choosing, tables, widgets and inline text as mygo's tests have them (v1.20): lists reporting their choice, typing rows' letters as AppKit does, Shift extending from its item, presses showing in their frame, clickModifiers(), tables' section headers across and chosen rows rounded as one block, calendars and color pickers at their own width |
works |
| Bases, forms, feedback, toggles and themes as mygo's tests have them (v1.21): Enter at a check box left to the window's shortcuts, Escape keeping the focus, an accordion's headers each a stop that the arrows move between, a toolbar's overflow menu with its toggles checked, fieldsets disabled after their build, accent colors as mygo's, text in the app's language, tooltips in the headless paint | works |
Outlines, repaints, scrolling, selections and following as mygo's tests have them (v1.22): frames asked for by the frame's clock, spinners painted again for their next spoke only, transformed and absolute children of containers out of view painted, inputs' text backgrounds, selection and text colors and accent carets, content transitions of rich text, scroll offsets kept within their content and scrollTo with an animation, lists following their end through resizes and rows going |
works |
Pastes, text lines, inline objects and buffers as mygo's tests have them (v1.23): the Edit menu's commands through the runtime (onPaste by the menu and Cmd+V alike, Undo and Redo of fields), inline objects as wide as they are when the text is shaped, text lines placed before their layout, malformed UTF-8 counted right in text buffers, lists reordering without a choice |
works |
Text wrapping at the width layout gives it, as mygo's (v1.24): in columns, beside what takes a row's room down to its widest word, Chinese between its characters, up to maxLines(), anew at each width in the frame of a resize |
works |
Wide colors, as mygo's (v1.25): Oklch::rgb outside sRGB keeps its Display P3 color, drawn on a P3 screen in float16 drawables for fills, shadows, text and decorations, its nearest sRGB color elsewhere; WideColor::srgb for the sRGB twin |
works (examples/gallery, Wide colors) |
Go's random numbers (v1.27): Random\Rand::pcg($a, $b) draws what Go's rand.New(rand.NewPCG(a, b)) draws (IntN, Float64, Shuffle, Perm, NormFloat64…), so seeded examples match mygo's; mygo's native, notes and drag-drop examples; text drawn with font smoothing (bolder the lighter it is, as AppKit's); borders taking room in layout; a button's label in its own style; dates in the system's time zone whatever php.ini names |
works (examples/farm, notes, native, drag-drop) |
Clicking through the examples on the Mac (v1.28): a tray stays in the menu bar; a drag's image shows the element's colors; a popover keeps the ids of what is built after it, so the notes' link field takes the typing; Go's generators (v1.27) taken out again, the examples drawing from PHP's engines; -launcher micro apps signable again (the phar inside the executable's __LINKEDIT) and with intl, xmlreader, curl, sockets and filter, which the framework calls (myphp micro rebuilds micro.sfx once), its catalogs found in its phar; an out inside frontendDist refused, the todo example built to build as mygo's; the first sound no longer crashes (a queue's address by ObjC::addr); a static app's terminal and frontend run from its phar; the 14 examples compared with mygo's side by side: fields as high as mygo's and taking fontSize(), their typing reported by changed(), a select's least width, a web view inside its box's border, a disabled button's border, a list's rows at its end with justify(END); the flyout example now mygo's |
works (examples/native, drag-drop, notes; myphp build -launcher micro) |
Releasing as mygo does (v1.29): CI runs the PHP and Bun tests on every push (.github/workflows/ci.yml); a tag v<version> checks that bin/myphp and the npm packages have it (php scripts/version.php, which sets it), publishes myphp-runtime, myphp-cli and the plugins' @myphp-plugins/* with npm's trusted publishing, and makes the GitHub release (release.yml); myphp-cli installed from npm runs the project's Composer-installed MyPHP (vendor/owner888/myphp/bin/myphp); the packages leave their tests out |
works (the first publish of the plugins and npm's trusted publishers are the developer's, on npmjs.com) |
| A frame's layout as fast as before text wrapping (v1.30): a text in a row too narrow for it keeps its widest word whole (its min-content width was one glyph's, laid out a line a glyph); a text's min-content width, which layout asks of every text that may wrap, taken from the line that measured it instead of laid out each frame: 2000 texts lay out in 4 ms instead of 14, a million-row list in 0.12 ms instead of 0.35; the tree a frame replaces freed at once instead of by PHP's collector, whose runs dropped frames while a list scrolled; frames paced by the view's display link, as mygo's, instead of drawn as soon as asked | works (tests/wrap_test.php, tests/lists_test.php) |
Images decoded off the main thread (v1.31): Image::fromBytes and fromFile decode through CoreImage on an NSOperationQueue's thread, the main thread spending about 0.2 ms an image (a HEIC took over 200 ms there); in a fiber (App::go) they wait while the app draws, outside one they block as before; HEIC photos come upright as their orientation says (a portrait one showed sideways); the photos and files examples and the Markdown view load images in fibers, the Markdown view showing the description until then, as mygo's |
works (tests/image_test.php, tests/markdown_view_test.php) |
Underlines skipping ink (v1.32): as AppKit's and mygo's, an underline breaks where g, y, p and the like cross it, a line's thickness clear on either side; Han, Kana, Hangul and spaces do not break it; the glyphs' outlines come from CoreText (CTFontCreatePathForGlyph), kept by font and glyph |
works (tests/text_test.php) |
Video (v1.33): Media\Player plays MKV, WebM, MP4, MOV and M4V (H.264, HEVC; AAC, AC-3, E-AC-3, MP3), PHP reading the packets (Matroska in PHP) and the system decoding and showing them (AVSampleBufferDisplayLayer, an audio renderer, a synchronizer), no frame through PHP; a two-hour 1080p film opens in 15–20 ms and plays at 2–3.5 % of a core; seeks land on the time, another audio track goes on from where it was; Context::movie lets the UI show over the picture (the video example: controls that hide, a seek bar, audio tracks, volume, full screen) |
works (tests/media_test.php, tests/player_test.php) |
Subtitles (v1.34): text tracks of a film (Matroska's SubRip, ASS and SSA, WebVTT; MP4's tx3g) and subtitle files (.srt, .ass, .ssa, .vtt; UTF-8, UTF-16 or GB18030), those beside the film named after it loaded, as IINA does; choosing a track, a delay; the video example draws them outlined over the picture and loads a file dropped on it |
works (tests/subtitles_test.php, tests/player_test.php) |
A player laid out as IINA's (v1.35): system materials under a node (Node::systemMaterial: the window's NSVisualEffectView materials, or macOS 26's glass, fading with the node), SF Symbols as images (Image::symbol), hiding the traffic lights (Window::setWindowButtonVisibility), keeping the window's shape (Window::setAspectRatio), Player::videoTrack; the video example as IINA's window: title bar, glass control bar, OSD, the quick settings sidebar, IINA's default keys |
works (tests/material_test.php, tests/image_test.php, tests/examples_test.php) |
Playing for less CPU (v1.36): the player's timer at four ticks a second, waking at a change of the subtitles between them; the video example draws a frame only when what it shows of the time changes, none while the controls are hidden |
works (tests/player_test.php, tests/subtitles_test.php) |
| VP9 and AV1 in hardware, Opus (v1.37): Matroska, WebM and MP4 files of VP9 or AV1 play where the Mac decodes them in hardware (VP9 on Apple silicon, AV1 from M3), with the system's decoders (VP9's vpcC made from its first keyframe); Opus sound, as WebM has it | works (tests/media_test.php, tests/player_test.php) |
A video player for apps (v1.38): Context::mediaPlayer builds IINA's window over a Media\Player (title bar, glass control bar, messages, the quick settings sidebar, IINA's keys; MediaPlayerState, MediaPlayerOptions), in the app's language; Player::setAspectRatio, setCrop, setRotation, setAudioDelay; the video example is a few lines of it |
works (tests/mediaplayer_test.php, tests/media_test.php, tests/player_test.php) |
The wheel and going on where a film was left (v1.39): the video player's wheel sets the volume (up and down) and seeks (across); it goes on from where a film was left (Media\PlaybackPositions, Player::path, MediaPlayerOptions::$resume) |
works (tests/mediaplayer_test.php) |
The playlist, picture in picture (v1.40): MediaPlayerState::open opens films (Context::mediaPlayer($state)), the films of its folder its playlist (Media\Playlist, Finder's order) in a sidebar from the left, the next at an end, Cmd+Right and Cmd+Left; picture in picture from the control bar |
works (tests/mediaplayer_test.php) |
Chapters, the playlist's loop, shuffle and sort (v1.41): Matroska's and MP4's chapters (Player::chapters) in the playlist sidebar's tab, a double click going to one; the loop (the film, the playlist), shuffle and the sort menu as IINA's; menu headers (UI\Menu::header); the display kept awake while a film plays (MediaPlayerOptions::$keepAwake), the pointer hidden with the controls in full screen too |
works (tests/mediaplayer_test.php, tests/media_test.php, tests/menu_test.php) |
| FLAC (v1.42): FLAC sound in Matroska and MP4, decoded by the system (its magic cookie the dfLa box AVFoundation makes) | works (tests/media_test.php, tests/player_test.php) |
Image subtitles (v1.43): Blu-ray's PGS and DVD's VobSub tracks of Matroska files, decoded in PHP (Media\Pgs, Media\VobSub) and shown over the picture where they are (Player::subtitlePictures, subtitlePlane) |
works (tests/subtitles_test.php, tests/player_test.php, tests/mediaplayer_test.php) |
ASS in its styles (v1.44): ASS and SSA lines in their fonts, sizes, colors, outlines, shadows and boxes, where they are (alignment, margins, \pos), with their override tags, moving, fading, animated (\t) and sung (\k), turned, scaled and clipped, laid out as libass does (Media\Ass, Player::subtitleLines) |
works (tests/subtitles_test.php, tests/player_test.php, tests/mediaplayer_test.php) |
Subtitles under the glass (v1.45): Node::belowMaterials paints a node in a layer of the window's under its system materials and over its videos; the video player's subtitles show under its control bar's glass, as IINA's; outlines and shadows of text (Node::textOutline, textShadow), its glyphs grown |
works (tests/material_test.php, tests/style_more_test.php) |
Gliding subtitles (v1.45.1): animationFrame() asked as a frame is built keeps the next frame coming (it was lost); a moving ASS line (a danmaku) is drawn at each refresh of the display and moved by a transform, in fractions of a pixel |
works (tests/repaint_test.php, tests/mediaplayer_test.php) |
The player's events, keyframe seeks, Now Playing, frame steps, screenshots (v1.46): Player::on tells play, pause, rate, seek and track as they change; Player::seek($s, exact: false) goes to the nearest keyframe that way, as IINA's arrow keys, buttons and wheel do (MediaPlayerState::seekBy); the film in the system's Now Playing and the media keys, as IINA's (MediaPlayerOptions::$nowPlaying); frame steps (Player::step, . ,) and screenshots of the frame showing (Player::snapshot, ⌘S to ~/Pictures/Screenshots) |
works (tests/player_test.php, tests/mediaplayer_test.php) |
The seek bar's thumbnails (v1.47): over the bar of the time, the time there and a thumbnail of the film, as IINA's; Player::thumbnail decodes the keyframe at a time apart from what plays (VideoToolbox, some 5 ms a thumbnail of 1080p, made between frames); kept on disk as IINA keeps them (Media\ThumbnailCache) |
works (tests/player_test.php, tests/mediaplayer_test.php, tests/thumbnail_cache_test.php) |
Boundaries and the A-B loop (v1.48): Player::at tells when it plays past a time, as AVPlayer's boundary observers; IINA's A-B loop (⌘L, MediaPlayerState::abLoop), its points knobs on the bar that drag |
works (tests/player_test.php, tests/mediaplayer_test.php) |
Films over HTTP (v1.49): new Player('https://…/film.mkv') plays Matroska and WebM as they come, a cache of IINA's size read ahead by libcurl driven by the run loop (Native\CurlLoop, Media\HttpBytes); paused for the cache as mpv is (the buffering event, bufferingPercent), IINA's buffering indicator; File > Open URL in the example |
works (tests/http_bytes_test.php, tests/player_test.php, tests/mediaplayer_test.php) |
MP4 over HTTP (v1.50): new Player('https://…/film.mp4') plays MP4 and MOV as they come, read in PHP as FFmpeg's mov demuxer reads them (Media\Mp4): the movie box at the start or the end, H.264, HEVC, VP9, AV1, AAC, MP3, AC-3, E-AC-3, Opus, FLAC, tx3g subtitles, the edit list, chapters |
works (tests/mp4_test.php, tests/player_test.php) |
HLS and MPEG-TS (v1.51): new Player('https://…/master.m3u8') plays HLS as mpv does: the variant of the highest bandwidth, its audio renditions as audio tracks (only the one played downloaded), MPEG-TS, fragmented MP4 or packed audio (AAC, AC-3) segments, AES-128, live playlists read again as they grow, discontinuities; segments downloaded ahead into IINA's 150 MiB. A local or remote .ts, .m2ts or .mts plays too (Media\Ts): H.264, HEVC, AAC, AC-3, E-AC-3, MP3 |
works (tests/hls_test.php, tests/ts_test.php, tests/player_test.php) |
yt-dlp and AV1 in software (v1.52): a web page's URL (YouTube's and the sites yt-dlp reads) plays as IINA plays it: yt-dlp's best video and audio as one (Media\YtDlp, Media\Merged), with the page's headers, title, chapters, subtitles and playlist; fragmented MP4 (DASH) read and seeked by its index; the two in IINA's one 150 MiB cache; AV1 decoded by libdav1d where the system has no decoder for it, or with Player::$hardwareDecoding off, which myphp build ships in the app |
works (tests/yt_dlp_test.php, tests/dav1d_test.php, tests/mp4_test.php, tests/player_test.php) |
Secondary subtitles and YouTube's ads (v1.53): a second text subtitle track at the top of the picture, plain, with its own delay and switch, as mpv's secondary-sid (Player::setSecondarySubtitleTrack); YouTube's and Twitch's pages read by yt-dlp first, as mpv's ytdl_hook, and a video's formats opened once the ad before it is over (yt-dlp's available_at), not refused; a node's id is mygo's, its parent's mixed with its place there, so what comes and goes renumbers only the siblings after it (a scroll, a field's focus keep their state) |
works (tests/player_test.php, tests/yt_dlp_test.php, tests/ids_test.php) |
| The ad's countdown (v1.54): opening a web page's video after the ad before it, the indicator says the seconds left ("Skipping ad… 13s"), then "Opening stream…" | works (tests/mediaplayer_test.php) |
Vorbis and external audio (v1.55): Matroska and WebM's Vorbis audio, which the system has no decoder of, decoded in PHP by the sound API's own codec (Audio\Vorbis\Codec) and played as PCM, stereo to 7.1; an audio file loaded beside the film (Player::loadAudio, the sidebar's Load External Audio, as IINA's); IINA's ten-band equalizer and its presets (Player::setEqualizer), the audio decoded and filtered here only while a band has a gain |
works (tests/soft_audio_test.php, tests/equalizer_test.php, tests/player_test.php) |
The equalizer's profiles (v1.56): IINA's saving, renaming and removing of the equalizer's gains as a profile of one's own, its pop-up's, the name asked as IINA asks it (none, or a preset's or a profile's, cannot be OK'd); ./run.sh video's log no longer hangs the app when a dialog stays open over 3 s |
works (tests/mediaplayer_test.php) |
| 10-bit AV1 in software (v1.57): AV1 of 10 bits (HDR's, YouTube's above 1080p) decoded by libdav1d as 8-bit is, given to the display layer as x420 pixel buffers (VideoToolbox's own of HEVC Main 10) with the colors the film says, its thumbnails too; a hardware-decoded film's colors as its container says them (an HDR Matroska AV1 no longer washed out); 60-frame films decoded in software no longer drop frames (the layer asks for each picture as it takes more), and a seek no longer holds the window while libdav1d decodes from the keyframe; a menu button clicked in the player's sidebar no longer keeps Space from play and pause | works (tests/dav1d_test.php, tests/player_test.php, tests/media_test.php, tests/menu_test.php) |
| A slider dragged (v1.58): the video given its renderer 10 ms of the main thread at a time, hardware- and software-decoded alike, the window drawn between; an exact seek asked while the last one lands kept and made once it landed, the last of them, as mpv's; a slider's change reported only when its value changes, as mygo's | works (tests/player_test.php, tests/primitives_test.php) |
| An HDR film's metadata (v1.59): its mastering display and light levels (MaxCLL, MaxFALL), a Matroska's or an MP4's, or in software each AV1 picture's own, given the system's tone mapping, as AVFoundation's formats and mpv's frames carry them | works (tests/media_test.php, tests/dav1d_test.php) |
Hardware decoding as it plays (v1.60): the video tab's switch, as IINA's quick setting, decodes an AV1 film on the system's decoder or by libdav1d from where it is, and the next film so too; fixed where only one way decodes; online subtitles of OpenSubtitles (the subtitles tab's Search Online, as IINA's: the film's hash and name, the preferred languages or the system's, IINA's list to choose from), of myphp's key or the app's (MediaPlayerOptions::$openSubtitlesKey), an optional account and the preferred languages in the player's settings (Context::mediaPlayerSettings, IINA's Subtitles pane); the film looked up on TMDB first by what its file's name tells (agc_player's parser: name, year, season, episode), its subtitles by its TMDB id, IINA's search where that finds none |
works (tests/mediaplayer_test.php, tests/opensubtitles_test.php) |
The player's settings window as IINA's (v1.61): its sidebar, search and section index, pages of cards; IINA's General page whole and applied (what a film opening does, pauses as the window is minimized, inactive or the Mac sleeps, keep the window open at the end, legacy full screen and blacked-out screens, the screen saver, resume, history and Open Recent, the playlist, repeat, screenshots' folder, format, clipboard and preview), its Video and Subtitles pages so far, IINA's strings in English and Chinese; the settings by IINA's names (Media\PlayerSettings); IINA's welcome and playback history windows (Context::mediaPlayerWelcome, Context::mediaPlayerHistory); examples/video in windows as IINA opens them |
works (tests/opensubtitles_test.php, tests/mediaplayer_windows_test.php) |
Every page of the player's settings as IINA's (v1.62): the UI, Video, Audio, Subtitles, Network, Control, Key Bindings, Advanced and Utilities pages, applied (the OSC's layouts and toolbar, the window's size and place, the OSD, thumbnails, picture in picture, HDR, the output device, volume past 100%, ReplayGain, gapless audio, preferred languages, text subtitles' style and place, IINA's matching of subtitle files, encodings, the cache, User-Agent and proxy, yt-dlp, the clicks, pinch, Force Touch and wheel, Set as Default, clearing caches); music mode; IINA's key binding sets and editor carrying out mpv's commands; Live Text by Vision; the app's log (MyPHP\Logger) and its viewer (Context::logViewer) |
works (tests/mediaplayer_test.php, tests/mediaplayer_keys_test.php, tests/subtitle_matcher_test.php, tests/logger_test.php) |
| A seek as the film plays off the main thread (v1.63): the audio renderer's flush and the clock's new time on a serial queue of the system's, no PHP on it, as mpv's play thread; a seek's main thread 74 ms → 12 ms (4K HDR AV1, hardware decoding) | works (tests/player_test.php, tests/mediaplayer_test.php) |
ASS as libass draws it (v1.64): drawings (\p, B-splines too), \frx \fry in perspective, \fax \fay, \blur \be, \clip and \iclip of rectangles and drawings (a GPU mask), the \kf sweep, each run with its own, outlines cut out where the fill is not opaque, BorderStyle 3's box per line; the settings' subtitle Blur; the glyph and image atlases evicting bitmap by bitmap instead of starting over |
works (tests/subtitles_test.php, tests/mediaplayer_test.php, tests/transform_test.php, tests/atlas_test.php) |
Memos (v1.65): Context::memo($key, $deps, $build) keeps a part of the view from frame to frame, neither built, laid out, recorded nor painted again while what it is built from is the same and neither the pointer, the keys nor the focus reach it (a settings page about 6 → 1.3–2.2 ms a frame); the frame's hot spots made cheaper (a translated column of 1000 rows 44 → 37 ms) |
works (tests/memo_test.php) |
Metal draws only what changed (v1.66): a frame the same as the last presents nothing; otherwise the rectangles that changed since the drawable was drawn (up to eight, apart from each other) are cleared and drawn again over what it holds, as mygo's; MYPHP_PARTIAL=0 draws every frame whole |
works (tests/damage_test.php) |
mygo's fixes, a first batch (v1.67): double clicks counted per element; documents follow files renamed right after another app saved them; a directory's renames paired by identity (20,000 files: 0.2 s); a row's percentage maximum binds; a diff fitting its content folds before it is drawn; tables, select popups, transitions, scroll bars and the tray as mygo's now; the updater checks at every launch, follows the app's language and relaunches into the update; flyouts' material and corners (FlyoutOptions::$vibrancy, $cornerRadius), WindowOptions::$hideWindowButtons, PageOptions::$userAgentSuffix, Audio::setLatency, a drag's Result::$escaped and $point, ListState::$keepOffset; wide colors as oklch() in the inspector |
works (tests/click_count_test.php, tests/documents_test.php, tests/file_watch_test.php, tests/flex_test.php, tests/code_editor_parts_test.php, tests/updater_test.php) |
Layout and text as CSS and mygo (v1.68): flexbox as Chrome lays it out (padding floors, shrinking by inner sizes, bases before the least and largest, minimums over maximums, a row's content by its items' bases); Node::BASELINE in rows and grids; Node::hidden(), out of the layout with its state kept; splits that give way in small windows and Context::splitPercent; each Chinese and Japanese character a word; a grid child's percentages of its area; paths past 4096 pixels drawn in tiles |
works (tests/flex_test.php, tests/widgets_more_test.php, tests/cjk_words_test.php, tests/svg_test.php) |
The feel of input (v1.69): a mouse wheel's notches glide as browsers scroll them, add up and give way to the app; the wheel passes from a container at its end to the one around it; pinches reach native UI as InputEvent::ZOOM; swipes go back and forward; a scroll's momentum is told, which the terminal's Command zoom ignores; PageOptions::$swipeNavigation and $pinchZoom; drag regions move the window after a quick press and drag; a native drag at a container's edge scrolls it |
works (tests/wheel_test.php, tests/window_drag_test.php, tests/datadrag_test.php, tests/terminal_test.php) |
Drawing vocabulary (v1.70): paths stroked with butt and square caps, miter and bevel joins and dashes that stay in place as a path draws itself in; radial gradients (Node::radialGradient, Painter::fillRadialGradient); Context::setZoom, a window's content zoomed sharply, the pointer, menus, drags, input methods and accessibility following; inline objects below the baseline drawn by Node::drawObjects, Markdown's math within text through Options::$inlineMath; justified and indented paragraphs, soft hyphens, spans shifted off the baseline and TextLayout::lines(); rich text's first-line indent, paragraph spacing, paragraph IDs and Document::visibleParagraphs |
works (tests/svg_test.php, tests/style_test.php, tests/zoom_test.php, tests/paragraph_test.php, tests/text_more_test.php, tests/markdown_view_test.php, tests/richtext_paragraphs_test.php) |
Menus drawn in the window (v1.71): Node::popoverMenu and popoverMenuButton draw context menus and menu buttons' menus with the theme's look, with the keys, type-ahead, submenus, focus and roles of the system's; radio items (MenuItem::radio); icons on menu items, tray menus included (MenuIcon::of, MenuItem::setIcon); a calendar that browses months without choosing (Node::browse), its month chevrons centered |
works (tests/popover_menu_test.php, tests/menu_icon_test.php, tests/datetime_test.php) |
Gradient stops, conic gradients, element blur (v1.72): linear and radial gradients through as many as 32 GradientStops; conic gradients (ConicGradient, Node::conicGradient, Painter::fillConicGradient, fillPathConicGradient, strokePathConicGradient); borders painted with a gradient (Node::borderLinearGradient, borderConicGradient); Node::blur, a node and its children blurred on the GPU as CSS's filter: blur() |
works (tests/gradient_test.php, tests/style_test.php) |
Choosing by dragging (v1.73): ListState::$dragSelect chooses the rows a drag goes over, scrolling near the edges; $cellSelect has a table choose blocks of cells (ColumnRange, TableColumn::$rowHeader, cellSelected, selectedColumns, selectColumns) by clicks, Shift, Cmd, drags and the arrow keys; a right-click chooses its row unless $menuKeepsSelection; $cellMenu gives cells a context menu, Context::rowDoubleClicked lets a cell see its row's double click |
works (tests/drag_select_test.php) |
| Occlusion culling (v1.74): what a later opaque panel, sheet or popover covers whole is left out of the frame the GPU draws, the pixels the same; the frame statistics count it | works (tests/cull_test.php) |
The terminal and the clipboard (v1.75): programs read the clipboard as Options::$clipboardRead allows (deny by default, allow, or ask through $onClipboardRequest or the terminal's own prompt); an image alone pastes as an empty bracketed paste, which Claude Code reads screenshots on; Select All copies with $copyOnSelect, which Terminal::setCopyOnSelect changes live |
works (tests/terminal_test.php) |
Distribution (v1.76): macos.provisioningProfile embedded before signing, for restricted entitlements such as Communication Notifications; an app's own InfoPlist.strings per language kept, so permission prompts show translated; notifications of incoming messages showing their sender's avatar (NotificationSender) |
works (tests/build_test.php, tests/app_test.php) |
File watches (v1.77): FSEvents on macOS, one stream per watched tree, 65536 entries by default; FileWatchOptions::$hintsOnly keeps no snapshot and stats no file, reporting FileHint events for the directories where something changed ("." after the system lost changes); watches enroll directories only |
works (tests/file_watch_test.php) |
Live chart lines (v1.78): Chart::minMax keeps the extremes of each pixel column, Painter::strokePolyline draws an opaque line as a capsule a segment, Chart::lineInto refills a kept path (examples/chart-streaming); TextState::textAnchorCaret; FSEvents streams start at now again |
works (tests/chart_test.php, tests/examples_test.php) |
| Markdown in linear time (v1.79): the inlines of a paragraph of many lines find their source offsets by halving, and a link closing spends the brackets before it in one step | works (tests/markdown_parser_test.php) |
Shaders (v1.80): mygo's ui/shader, fragment shaders in Go's syntax (Ebitengine's Kage) compiled in PHP to Metal: Shader::compile, draw, fill/fillPath/strokePath/text in place of colors, Values and Uniform, four images (images or canvases), element effects (Node::effect: ColorEffect, DistortionEffect, LayerEffect), the backdrop (backdropSample) |
works (examples/shaders, tests/shader_test.php, tests/shader_compiler_test.php, tests/shader_msl_test.php) |
Large canvases, styled inputs (v1.81): a canvas up to 8192 pixels a side (one larger than a page of their atlas renders into its own texture), so blackhole renders at the window's pixels; input() takes its padding, corners, background, border and shadows, as mygo's TextInput, so wind's composer and chat-glow's field are inputs; a shader draw's uniforms packed in runs (wind's 106 → 7 µs), compiling a quarter faster |
works (tests/canvas_test.php, tests/textarea_test.php, tests/examples_test.php, tests/shader_test.php) |
| A field's text clipped at its border (v1.82): a field without padding (chat-glow's) shows its first letter whole, as mygo's | works (tests/textarea_test.php) |
| A terminal's pointer as Ghostty's, avatars as PNGs (v1.83): the I-beam over a shell, the arrow while a program takes the mouse (the I-beam with Shift), the shape a program asks for with OSC 22; a notification's sender avatar given to the system as a PNG, so an SVG one shows | works (tests/terminal_test.php) |
A terminal's clears, copies, links and drops for apps (v1.84): Terminal::clearScreen/clearScrollback and Options::$onClear, the menu's Clear as ⌘K; Options::$onCopy with what copied; the link Command hovers underlined and Terminal::hoveredLink(); files dropped paste their paths quoted for the shell; snapshots keep the blank rows at the end; Terminal::useTheme answers color questions without a view and tells programs of light and dark (mode 2031) |
works (tests/terminal_test.php) |
A terminal's padding and minimum contrast (v1.85): Options::$padding and $paddingColor (the background, or the edge cells' backgrounds extended, as Ghostty's window-padding-color; a program's own cursor kept in its cell); Options::$minimumContrast and setMinimumContrast, text darkened or lightened as little as reaches the ratio, shapes keeping their colors |
works (tests/terminal_test.php) |
| Painting skips what shows nowhere (v1.86): each node's ink (its box, shadows and children, through its transform) found as the frame is recorded, and subtrees whose ink is out of the clip skipped whole: 1000 rows moved out of view paint in 0.7 ms rather than 13.5 | works (tests/paintcull_test.php) |
A terminal tells of commands that finished (v1.87): Ghostty's notify-on-command-finish by the shell's OSC 133 marks: Options::$notifyOnCommandFinish (never, unfocused, always), $notifyOnCommandFinishAction (the bell, a desktop notification "Command Succeeded/Failed" with how long it took and its exit code) and $notifyOnCommandFinishAfter |
works (tests/terminal_test.php) |
A shell integration for zsh and bash (v1.88): a zsh or bash started alone reads the user's startup files as it would and marks its prompts and commands with OSC 133, so commands that finish are told of in them too; Options::$shellIntegration turns it off |
works (tests/terminal_test.php) |
Bare file names, rounded caps, short lists, Japanese (v1.89): notes.md and the like are terminal links; Powerline's rounded caps drawn to the cell's height; a drag choosing rows in a list shorter than 64 points scrolls toward the pointer; the framework's strings in Japanese (ja) |
works (tests/terminal_test.php, tests/terminal_themes_test.php, tests/drag_select_test.php, tests/l10n_controls_test.php) |
The code editor: imports folded, text tinted, scrolled in points (v1.90): a run of imports folds as one (Language::$imports, State::foldImports()); State::$textBackgrounds tint ranges of text; scrollY(), setScrollY(), lineTop(); focusing an editor in a scroll view shows its caret |
works (tests/code_editor_test.php) |
| The terminal's scroll bar (v1.91): drag it by the thumb or press the track; it widens under the pointer, which is the arrow there, and fades out a second after the viewport last moved, as macOS's | works (tests/terminal_test.php) |
Audio: Player::position() is where the player is heard (v1.92): it lags the mixing by what the output holds, plays out a pause, waits for a play or a seek to be heard, so lyrics and progress bars keep time with the sound |
works (tests/audio_test.php) |
Now Playing and the media keys (v1.93): NowPlaying::set() shows what plays in Control Center and the menu bar, onCommand() gets the media keys' commands, control() lets them drive an Audio\Player; Player::onEnd() |
works (tests/nowplaying_test.php) |
An image lets its pixels go once shown (v1.94): Image::releasePixels() drops a decoded image's pixels and its levels', decoding them again from the bytes it keeps when the atlas, a level or a shader needs them |
works (tests/image_test.php) |
Content rules (v1.95): ContentRules::compile() compiles rules in WebKit's content blocker format, kept across launches, which PageOptions::$contentRules and Page::setContentRules() apply to block loads and hide elements, as an ad blocker does |
works (tests/content_rules_test.php; compiling and blocking by hand) |
| The code editor paints bold and italic tokens in their faces (v1.96): Markdown's headings, strong and emphasis, and any theme's bold or italic token styles, with carets, hits, selections and wraps following the runs | works (tests/code_editor_test.php) |
Glass clarity (v1.97): new Glass(clarity: 0.5) makes panes as large as sidebars clearer, blurring and toning less, the rim brighter and the bezel wider; controls keep macOS's material |
works (tests/glass_test.php; the pixels on the Mac) |
The terminal (v1.98): Shift-click extends the selection from its anchor, across lines and while dragging past an edge; Options::$scrollMultiplier sets the rows a wheel notch scrolls and scales a touchpad's |
works (tests/terminal_test.php) |
Pages (v1.99): WindowOpenRequest::$userInitiated; PageOptions::$autoplay, $backgroundThrottling, $preloadAllFrames and $sessionState, with Page::sessionState() to save the back/forward list; Page::processID() |
works (tests/webview_test.php; WebKit's side on the Mac) |
Diff stripes and the .icns icon (v1.100): deletion bars' stripes continue across lines and scroll with them; a filler draws the stripes in sight alone; macos.icon may be an .icns drawn per size |
works (tests/code_editor_diff_test.php, tests/build_test.php) |
Pages (v1.101): Page::themeColor() and themeColorChanged from the page's theme-color meta; isAudible(), setAudioMuted() and audibleChanged, as a tab's speaker; captureState(), setCaptureState() and captureStateChanged for the camera, the microphone and the display |
works (tests/webview_test.php, src/Native/theme-color.test.ts; WebKit's side on the Mac) |
Videos in native UI (v1.103): Context::video(VideoPlayer) plays a video in native UI, as an image shows, frame by frame as each comes due; GifDecoder for animated GIFs; VideoDecoder for other formats; it stands out of sight and shows its first frame with less motion |
works (tests/video_test.php; GIFs on the Mac) |
| The system's proxy (v1.103): myphp's requests (the updater, fetch, WebSocket, a film over HTTP) go through the proxy of System Settings, with its exceptions and PAC files; proxy environment variables win; loopback goes direct | works (tests/netproxy_test.php; CFNetwork on the Mac) |
HTTP sign-in and certificate errors (v1.104): Page::setAuthHandler() answers Basic, Digest, NTLM and Negotiate challenges of pages and proxies; Page::setCertificateErrorHandler() lets a page load a certificate that does not verify, as a dev server's |
works (tests/webview_test.php; WebKit's side on the Mac) |
Updater prereleases and Sparkle-style update windows (v1.105): Updater::setPrereleases() offers prereleases (the highest non-draft GitHub release) to users who chose a beta, and myphp build --upload marks a pre-release version's release --prerelease; a background check's update window waits for the app to become active, as Sparkle's alert; App::isActive(), App::activationPolicy() |
works (tests/updates_test.php, tests/updater_test.php, tests/build_test.php) |
Drawings at a lower frame rate (v1.106): Painter::animationFrameRate() / Runtime::animationFrameRate() paint a drawing again every so many display refreshes (60, 40 or 30 a second at 120 Hz) and have a ProMotion display refresh as often, unless anything else asks for each |
works (tests/repaint_test.php; the display link's rate on the Mac) |
A still subtree kept in a bitmap (v1.107): Node::cache() draws a node and its children into a GPU bitmap shown as one image, rendered again only when what they paint changes (content, size, scale, theme, an updated image); transforms move the bitmap; with blur() or effect() their layer is kept |
works (tests/cache_test.php; the pixels on the Mac) |
App slugs (v1.108): slug in myphp.json names the update archives and deltas; by default the name's, with Chinese read as pinyin (yun-yin-le of 云音乐), kana and Hangul romanized (ramen, seoul) |
works (tests/slug_test.php) |
Windows sized to their page (v1.109): FlyoutOptions::$fitContent (within $minWidth…$maxHeight) and WindowOptions::$fitContent size a flyout or a window to its page's content and follow it, as browsers size extension popups; the bridge measures the root, before dom-ready |
works (tests/fit_content_test.php, src/Native/fit-content.test.ts; resizing on the Mac) |
| Code editor languages (v1.110): Dart, GraphQL, R and Scala highlighted, found by name, extension and alias | works (tests/syntax_test.php) |
Update sources (v1.111): Updater::setSource() takes an UpdateSource of the app's own (a service, credentials, a channel) for the manifest and the files it names, checked against the build's key as the feed's |
works (tests/updates_test.php) |
Untrusted pages (v1.112): PageOptions::$untrusted shows someone else's page (a plugin's UI, user HTML) calling no PHP method, hearing no broadcast, without permissions, windows, downloads or navigation off its origin unless the app's handlers decide; ProtocolRequest::$page tells a scheme which page asks |
works (tests/webview_test.php; WebKit's side on the Mac) |
Picture in picture for pages (v1.112): PageOptions::$pictureInPicture lets a page's videos go into picture in picture (controls, context menu, requestPictureInPicture()); Page::togglePictureInPicture() and isPictureInPictureActive() |
works (tests/webview_test.php, src/Native/pip.test.ts; WebKit's side on the Mac) |
Sessions (v1.112): PageOptions::$session puts a page in a Session's website data store (Session::newSession('work') kept on disk, newEphemeralSession() in memory), with its own $cookies and clearData(SiteData::…) |
works (tests/session_test.php; WebKit's stores on the Mac) |
Context menus of pages (v1.112): Page::on('contextMenu', …) hears what was right-clicked and changes, extends or replaces WebKit's menu (ContextMenuEvent, Menu::remove()) |
works (tests/context_menu_test.php; WebKit's menu on the Mac) |
Frames that only paint again (v1.113): while only drawings move (animationFrame() or after() asked as they paint, indicators, content transitions), a frame paints the last frame's nodes again without building or laying out the view, and only the draw callbacks that asked when they alone did, as mygo's |
works (tests/repaint_test.php) |
| Rings and shadows in bands (v1.115): rings, border-only boxes and box shadows draw only in bands of rows around their round corners, not their whole box, and upright shapes only within their clip, as mygo's | works (tests/bands_test.php; the pixels on the Mac) |
Automation (v1.116, mygo's plugins/automation): App::use(Automation::plugin()) with MYPHP_AUTOMATION set lets another process drive the running app over a Unix socket in newline-delimited JSON: windows, the elements of native UI with their Context::key keys, actions on them, pointer, wheel, key and text input, the application menu, captures, pages' eval, and waiting for windows to settle (docs/automation.md) |
works (tests/automation_test.php; the app's side on the Mac) |
Nodes ignoring input (v1.117): Node::ignoreInput() lets the pointer and the keyboard pass a node and those inside it by, while they show and assistive technology still presses them, as mygo's; the terminal draws block sextants (QR codes); myphp build makes disk images with diskutil on macOS 27 |
works (tests/ignore_input_test.php, tests/terminal_test.php, tests/build_test.php) |
mygo's names for videos (v1.118): Context::video, UI\VideoPlayer, VideoDecoder and VideoFrame are mygo's frame-by-frame videos (formerly Context::clip, ClipPlayer…); a Media\Player's film shows with Context::movie, and the IINA player is UI\MediaPlayer (Context::mediaPlayer, mediaPlayerSettings, mediaPlayerWelcome, mediaPlayerHistory) |
works (tests/video_test.php, tests/mediaplayer_test.php) |
A smoke test on the Mac (v1.119): php -d ffi.enable=1 scripts/smoke-automation.php drives examples/automation through the automation plugin, a check a line, and saves its capture; a grid child's automatic margins place it in its area, as mygo's |
works (tests/examples_test.php, tests/layout_test.php; the smoke test on the Mac) |
Fixes from the smoke test (v1.120): a window of native UI made hidden draws its first frame and becomes ready to show; Page::eval of statements (return …, throw …) no longer crashes the app |
works (the smoke test on the Mac) |
A part of the window in another theme (v1.121): Context::themed($theme) builds and paints a column, and what it opens over the window, with another theme, as mygo's Themed (a light preview in dark chrome) |
works (tests/themed_test.php) |
Aligned with mygo (v1.122): Markdown images fitted by percentage and aspect ratio, code blocks left to right in right-to-left UI; reverse() and percentage widths in wrapping rows; ListState::visible()/atEnd(), GridState::scrollTo($item, $align); a held press released as the window loses the keyboard; TIFF clipboard images as PNG; web views' pages in VoiceOver's tree; VoiceOver selecting text in the rich text editor |
works (tests/markdown_view_test.php, tests/flex_test.php, tests/lists_test.php, tests/click_count_test.php, tests/richtext_editor_test.php, tests/automation_test.php; the pasteboard and VoiceOver on the Mac) |
Aligned with mygo (v1.123): mygo's setter names (alignItems(), radius(), minWidth()/minHeight(), border($width, $color); the old names gone); draw()/drawOver() called with the painter and the node's box, (Painter $p, array $r); Left and Right in selectable text by visual graphemes |
works (tests/flex_test.php, tests/repaint_test.php, tests/textselection_test.php) |
Aligned with mygo (v1.124): decimalInput, an exact decimal input for money; message dialogs' destructiveButtons, Escape and Cmd+. canceling also when the cancel is the default; a tray's bounds those of the copy of its icon clicked; delta updates comparing at most 64 KiB of a match, so a long shifted one no longer hangs them |
works (tests/decimal_input_test.php, tests/dialog_test.php, tests/appkit_test.php, tests/updates_test.php, tests/code_editor_diff_review_test.php; the dialogs' keys and the tray on the Mac) |
Aligned with mygo (v1.125): numberInput's − and + padded as mygo's and disabled at its bounds, its field telling assistive technology its range, value and step |
works (tests/widgets_more_test.php) |
Aligned with mygo (v1.126): the code editor's scroll bar thumbs only while the pointer is over it, over no track; WebViewOptions::$scrollToContainer, a web view giving the wheel and the touchpad to the native UI around it; a button's padding() sizing it |
works (tests/code_editor_test.php, tests/widgets_more_test.php; the web view's scrolling on the Mac, tests/webview_test.php) |
| The code editor's hover and completions attached to the text they are of, where they show from their first frame: the hover no longer covers its line for a frame before jumping above it, and shows after the pointer rests 300 ms, fading in over 100 ms, as VS Code's; the code editor example opening a PHP file (v1.127) | works (tests/code_editor_parts_test.php) |
PHP in the code editor: heredocs and nowdocs to their closing label with a heredoc's variables, attributes' #[ no comment, <?php and ?> tags, the names after -> properties, enum and the scalar types (v1.128) |
works (tests/code_editor_php_test.php) |
A text wrapping at its maxWidth() rather than running past its container; a wrapping row as high as its lines at the width it gets in the same frame, its lines broken at its children's basis(), a scroll container scrolling over all of it; alerts and modals centered over the whole window wherever they are built (v1.129) |
works (tests/wrap_test.php, tests/flex_test.php, tests/feedback_test.php) |
| Interactive glass lit while the pointer is over it; rounded corners at most half a side; a scroll container's bars staying while a press on its content is held (v1.129) | works (tests/glass_test.php, tests/ui_more_test.php) |
| Aligned with mygo (v1.130): a horizontal scroll bar for a diff as high as its lines and for the old side of a split one, at the bottom of what shows; the gallery's chat bubbles and rows of buttons at a narrow window | works (tests/code_editor_diff_review_test.php) |
| The gallery at the narrowest window, 640 points: its cards one to a row, the List page's tree and table and the Languages page's locales wrapping, the Drawing page's wave within its box (v1.131) | works (the examples test, and on the Mac) |
| The gallery against mygo's, pixel by pixel: text placed as Core Graphics places it, weights 500 and 600, SF Mono and tabular digits; fills, borders and clips on whole pixels, edges a pixel soft; fields, absolute children and automatic margins inside borders and maxima; mygo's stepper, stars, range ticks, segmented track and color well; no frames for a window out of sight (v1.132) | works (the headless tests, and compared on the Mac) |
Continuous corners, as macOS draws them, on fills, borders, shadows and images; clips with rounded corners; Scrollbar as a value (Painter::scrollbar), the editor's, rich text's and the terminal's bars shown while hovered; a web view takes the first click; the pointer tracked while the app is active; the gallery's radial bar and Overlays row as upstream's (v1.133) |
works (the headless tests, and compared on the Mac) |
Aligned with upstream (v1.134): dotted underlines, an input's placeholder color and caret width, scroll containers without bars, Context::instant(), Markdown's anchor and link weight, the Edit menu's items enabled only while the focus can act on them, assistive technology editing a text client of its own, the updater's availableVersion(), a message's cancelButton as upstream's (0 the label search, else the first button; -1 none); examples/wavelength; Native\ThumbnailDecoder |
works (the headless tests) |
IINA's menus of the player (v1.135): Playback, Video, Audio and Subtitles, File's items of the film, Window's Log Viewer, their keys the player's bindings' (UI\MediaPlayerMenus); MenuItem::$alternate, $onOpen and setSubmenu(); Subtitles > Encoding reading the files again |
works (the headless tests) |
| The picture's adjustments (v1.136): IINA's video equalizer (brightness, contrast, saturation, gamma, hue) as Core Image filters of the video layer, Video > Flip (mirror and flip), the video tab's HDR switch | works (the headless tests) |
IINA's Inspector (v1.137): General, Tracks, File and Status with its watch table, of the player last in front; Audio > Audio Device; the player's classes and Context methods named MediaPlayer (MediaPlayerState, Context::mediaPlayer…; MoviePlayer before) |
works (the headless tests) |
The loading plugin (v1.138, mygo's plugins/loading, Plugins\Loading): loading.dev's 29 indicators drawn by MyPHP (arcs, rings, dots, bars, blocks), their size, color, pace, easing, caps and directions as options, still where the desktop asks for less motion, paused where they are |
works (tests/loading_test.php, examples/loading) |
| IINA's other sources of subtitles online (v1.139): assrt.net (by the film's name, IINA's list to choose from, a subtitle's archive's files; the user's API token in the settings, else IINA's for trying after its alert) and shooter.cn (by the file's hash, all found loaded); Subtitles > Find Online Subtitles from each, the settings' Subtitle source choosing the one searched | works (tests/online_subtitles_test.php) |
| IINA's Video Filters and Audio Filters windows (v1.140): the filters on, edited, saved with a key (Saved Video Filters, Saved Audio Filters), IINA's New Filter presets; done without mpv: crop, expand, flips, sharpen and blur, negative, delogo and 3D LUTs (.cube) on the video by Core Image, volume, equalizer, bass, treble, highpass, lowpass and pan on the audio; the key bindings' vf and af | works (tests/filters_test.php) |
Deinterlace (v1.141): Video > Deinterlace and the key bindings' deinterlace, the video then decoded here by VideoToolbox and its lines blended by vImage before the layer shows it, kept for the next film |
works (tests/deinterlace_test.php) |
| The interactive mode (v1.142): Video > Crop > Custom… and Video > Delogo, a box dragged over the paused picture (its edges resized, IINA's ratios), Done a crop or a delogo labeled among the video filters | works (tests/interactive_test.php) |
Player plugins, of PHP (v1.143): IINA's plugin folders, Info.json and permissions, an entry returning a function of Iina (console, preferences, menu, utils, file, http, core, mpv, event, playlist, subtitle, global, timers), the Plugin menu, Settings > Plugins (install a local package, on and off, uninstall, order, About), plugins' subtitle providers in Find Online Subtitles |
works (tests/plugins_test.php) |
Plugins' views (v1.144): overlay over the picture (its data-clickable parts taking clicks), sidebar tab in the plugins panel, standalone window, simple mode and messages both ways; the plugin's Settings (preferences page) and Help tabs in its details sheet |
works (tests/plugins_test.php) |
Plugins' last part (v1.145): iina.input (keys and mouse, priorities), iina.ws (a WebSocket server), the playlist's context menu with plugins' items, install from GitHub, update checks and updates, the plugin store, the developer tool (PHP console), mpv's hooks of a film opening; the official plugins ported to PHP (plugins/: Online Media, User Scripts, OpenSubtitles) |
works (tests/plugins_test.php, tests/official_plugins_test.php) |
| The examples beside mygo's (v1.26): color emoji, tinted images, text areas wrapping a paragraph at a time (a document of 100 000 lines opens at once), padding of text, borders without a background, sliders, disabled controls dimmed, local time | works (examples/farm, examples/text-buffer, examples/text-input) |
| App / Window API: mygo's window methods (showing, focus, minimize, maximize, full screen, bounds and sizes, limits, controls, on top, opacity, shadow, represented file, edited, Dock progress, attention, all Spaces, child windows and sheets) and events | works, with vibrancy, transparency and background colors, panels, flyouts (anchored to a rectangle or a tray icon, flipping and sliding into the screen, closing on a press outside or Escape) or in the system's popovers |
Router (mygo's): a history of pages with paths and queries, {name} and {name...} patterns, layouts, pages keeping their state and the focus, sliding and fading in, Back and Forward buttons, Cmd+[ and Cmd+], a mouse's back and forward buttons, links to paths |
works (examples/router) |
| Draggable nodes (mygo's Draggable): following the pointer in the parent's axes, thrown to snap points at the pointer's speed, bounds rubber-banded or rigid, caught moving, lifting with a pose; values dragged within a window to nodes taking their type, with a copy under the pointer, scrolling at the edges, Escape giving up | works (examples/drag) |
Native drag and drop (mygo's data_drag): data dragged out to other apps in several formats, made lazily; text, URLs, Markdown and other formats dragged in, offered unread and read once dropped; copy and move negotiated; files from the Finder to nodes, then the window's fileDrop; a value dropped as it is inside the app; Window::startDataDrag |
works (examples/transfer) |
| Markdown parser (mygo's ui/markdown/parser): CommonMark 0.31.2 and GitHub's extensions (all their examples), footnotes, math; text appended or edited parsed again only where it changed; a streaming reply's half-written end healed | works |
| Markdown view and stream (mygo's ui/markdown): headings, paragraphs with links and footnote references, lists and task lists, quotes, highlighted code blocks with a copy button, tables, images, math and HTML as text or by hooks; selectable, copied as text, HTML and Markdown, or as Markdown with Shift+Cmd+C; a reply being written revealed at an even pace, fading in | works |
Rich text (mygo's ui/richtext): Text of attributed runs in styled paragraphs (headings, lists, task lists, quotes, code blocks, alignment, links, tags, attachments), persistent; Document with selection, typing attributes, undo and redo, and the Format commands; Markdown, HTML, RTF and JSON in and out; the editor (RichText::editor): word processors' shortcuts, Markdown shortcuts as you type, lists and tasks, undo of formats with the text, input methods, rich copy and paste |
works (examples/richtext) |
| Terminal (mygo's plugins/terminal): the user's shell, or any program, in a pseudo-terminal, emulated by Ghostty's libghostty-vt (mygo's build, loaded through FFI, downloaded into the cache once); colors, Unicode, the scrollback, full-screen programs, the Kitty keyboard protocol, the mouse, bracketed paste, hyperlinks, titles, OSC 52, pastes a program reads itself; selecting as Ghostty does, keybinds, Ghostty's themes, links opened with Command+click | works (examples/terminal) |
Updates (mygo's Updater): myphp keygen, then myphp build signs an archive of the app (Ed25519), makes delta updates from the published versions (bsdiff) and writes the manifest, for GitHub releases or a server; Updater::check() and Update::install() in the app, the bundle replaced, App::relaunch(); App::go() runs waiting work in a fiber |
works |
updater (mygo's plugins/updater): the update window of Sparkle: background checks once a day, the release notes of a new version with Install, Remind Me Later and Skip This Version, the download's progress, Relaunch; "Check for Updates…" (Updater::menuItem()), the user's choices in updater.json, 15 languages; the window as a web page or in native UI (Native::plugin()) |
works (examples/updater) |
game (mygo's plugins/game): Game::view() runs a game in fixed steps (60 a second, or its rate()) with its player's keys by position, pointer and wheel, and draws it every frame through a Painter; Camera (zoom, rotation, whole-pixel panning, toWorld, visible), Game::integerFit() for pixel art; Gamepad\Gamepad::pads() reads controllers through GameController.framework in the standard layout |
works (examples/game) |
UI\Canvas (mygo's): a bitmap a painter draws on the GPU, kept until drawn anew, shown as any image (pixelated for pixel art); UI\Chart (mygo's ui/chart): Linear, Band and Time scales with d3's ticks, Chart::plot with axes, marks that are nodes (rect, point, ruleX, ruleY, anchor), lines, areas and pie sectors as paths, stacks, a selection for the pointer and the keys |
works (examples/charts, examples/game) |
UI\Direction (mygo's): right to left for windows in such a language or a subtree of its own (Node::direction), logical edges (paddingStart, marginEnd, borderStartWidth…), rows, columns, grids and sideways scrolling mirrored, text from the right, the arrow keys of sliders, ratings, trees, calendars and the like along the line; text fields not yet |
works (MYPHP_DIRECTION=rtl) |
UI\Inspector (mygo's): developer tools for native UI, docked beside the content: the tree of nodes as markup, the styles, box model and properties of the node chosen or picked in the window, highlighted over it, frame times against the display's, issues such as duplicate keys, a right-to-left toggle; F12, Alt+Cmd+I, the menu role toggleDevTools; PageOptions::$devTools (DevTools), which opens a page's web inspector too; see docs/inspector.md |
works |
Audio\Audio (mygo's package audio): sounds decoded into memory that play over themselves, streams decoded a second ahead, Decoders an app writes (a synthesizer); players with volume, pan, speed, loops and loop ranges without a gap, seeking, 5 ms ramps; mixed in PHP into an Audio Queue; WAV and Ogg Vorbis decoded in PHP, MP3, AAC, ALAC, FLAC, AIFF and CAF by the system; see docs/audio.md |
works (examples/piano) |
Plugins (mygo's Use), in PHP: App::use(Plugin) binds a plugin's service as plugin:<name>, its page side a TypeScript client of its own; a CallContext parameter tells the calling page and ends as it goes; App::onQuit |
works |
File watches (mygo's WatchFiles): App::watchFiles on a file or a directory, recursive or not, Ignore leaving out what is never opened; batches of create, write, remove and rename once changes stop, a file saved as editors do a write, the end when the root goes away; FSEvents through FFI, or with hintsOnly only the directories where something changed |
works |
watch (mygo's plugins/watch): pages watch the directories and files the app names (src/Plugins/Watch/client/index.ts, @myphp-plugins/watch), recursive only where the app allows, Resolve and Allow, quotas per page and in all, failures as codes that never say a path |
works (examples/watch) |
WebSocket (mygo's plugins/websocket): pages open WebSocket connections from PHP with the browser's API (src/Plugins/WebSocket/client/index.ts, @myphp-plugins/websocket): any header in the handshake (Authorization, Cookie, Origin), subprotocols, text and binary messages in order, bufferedAmount, closing either way, connections ending with their page; libcurl, the system's, through FFI |
works (examples/websocket) |
fetch (mygo's plugins/fetch): pages make HTTP requests from PHP with a standard fetch (src/Plugins/Fetch/client/index.ts): no CORS, any header (Origin, Cookie, Host…), the body streamed as it arrives, aborting stops the request; libcurl, the system's, through FFI |
works |
SQLite (mygo's plugins/sqlite): mygo's build of SQLite 3.53.4 and its shim through FFI; Sqlite::open, one statement at a time checked whole before it runs, parameters, RETURNING, transactions rolled back on any error, read-only connections, busy timeouts, FTS5, R*Tree and JSON; for pages, App::use(Sqlite::plugin()) and src/Plugins/Sqlite/client/index.ts (@myphp-plugins/sqlite), connections owned by their page |
works |
Web pages: Ipc::bind() a PHP service, call it from the page, events back, streaming channels, page-side window controls, preload script, generated TypeScript client |
works (examples/webview) |
| Menu bar with roles and accelerators, context menus, open/save/message dialogs | works (examples/desktop) |
App services (mygo's): deep links (App::onOpenURL, registerURLScheme, myphp.json's urlSchemes) and files opened with the app (onOpenFile, fileAssociations), a single instance (requestSingleInstanceLock, onSecondInstance), recent documents, opening at login, Network (online, metered), Clipboard (data in several formats, files, flush), Secrets (the Keychain), Cookies; pages: find in page, print, print to PDF, permissions (camera, microphone), downloads |
works (examples/services) |
Documents (mygo's): Documents, Document — a window per file, the File menu's roles (new, open, openRecent, save, saveAs, duplicate, rename, moveTo, revert), autosave in place, recovery copies after a crash, asking before closing and quitting, files changed by other apps, a DocumentService for pages; see docs/documents.md |
works (examples/documents) |
Custom schemes served by PHP (mygo's Protocol): Protocol::handle, ProtocolRequest/ProtocolResponse (streamed bodies), Protocol::fileServer; the frontend at myphp://localhost/ (App::setFrontend, myphp.json's devUrl, devCommand, buildCommand, frontendDist); only trusted pages call PHP (PageOptions::$trustedOrigins) |
works (examples/protocol) |
| Scrolling a node into view (mygo's ScrollIntoView), and the focus Tab brings into view, through nested scrolling containers | works |
Context menus and menu buttons of the native UI (mygo's ContextMenu, Menu, MenuButton): items, check marks, submenus, shortcuts, the innermost menu, the menu key and Shift+F10, text inputs' and selectable text's editing items, editItems() |
works |
| Tray icon, global shortcuts, notifications (from a built .app), window events, file drops, remembered window frames | works (examples/desktop) |
Child processes without blocking the UI (Process): curl, scripts, tools |
works (examples/http) |
SVG: icons as masks in any color (turned by rotate), pictures in their own colors — paths, shapes, transforms, gradients, strokes and dashes, opacity, clip paths, masks, style sheets — drawn by a renderer in PHP, which also rasterizes path masks (no CoreGraphics) |
works (examples/widgets, files) |
Languages and regional formats (mygo's i18n, v1.10): the app's language from the user's (or App::setLocale, kept in its defaults); dates, numbers, months, weekdays and day periods in a locale (I18n, DateFormatter, NumberFormatter, ICU through ext-intl); CLDR's parents and plural categories; ICU messages (Message: plural, selectordinal, select, offsets, number and date styles); the app's catalogs (Catalog, myphp.json i18n), myphp generate writing their PHP class and TypeScript module, myphp i18n check, myphp dev --lang, the pseudo locales en-XA and ar-XB, the bundle's localizations; MyPHP's own strings and menus (en, zh-Hans, zh-Hant) changed by myphp.<key> |
works (examples/widgets) |
| A file manager: sidebar of places, breadcrumbs, the folder as icons or a sortable table sharing one selection, find bar, image preview, Trash through Finder | works (examples/files) |
| Code editor (mygo's ui/editor): rope text, undo, several carets and selections, soft wraps, folding, find and replace (PCRE), go to line, completions, hovers, diagnostics, inlay hints and suggestions, the changes against a base in the gutter, Zed's commands and key bindings, input methods at every caret; 28 languages highlighted incrementally; only the lines in view laid out. Diff viewer: split or unified, incremental, words or characters changed, collapsed unchanged lines, Revert and Keep, line selection, annotations, git patches | works (examples/code-editor) |
Accessibility: VoiceOver reads and works every widget, the code editor and the diff viewer (mygo's tree of roles, names, states and actions over NSAccessibilityElements) |
new, to try with VoiceOver |
| Transforms (translate, scale, rotate, skew, and in 3D rotateX/rotateY/perspective, backface hidden) and opacity of a node and its children, drawn by the GPU through each quad's map, hit-tested through its inverse (mygo's transform.go) | new |
| Poses (a node moved, scaled, turned and faded after its transform) and content transitions (text rolling as a counter's digits or fading across, icons replaced, pictures crossfading) (mygo's pose.go and content.go) | new |
| Transitions: nodes moving and resizing from where the last frame showed them, entering and leaving (moved, faded, collapsed, scaled, turned), colors fading, matched nodes coming from where their match was (mygo's transition.go) | new |
| Animation: curves, springs, delays, jumps, colors, keyframes and loops by node and key (mygo's motion), timelines of tweens placed along one playhead (paused, sought, reversed, repeated), Reduce Motion; the switch, sliders, collapsibles, accordions and sidebar sections move as mygo's | new |
| Linux and Windows | next |
| Linux (GTK/WebKitGTK), Windows (Win32/WebView2) | planned |
Measured in examples/inbox 1000 on an M-series Mac: 9 000 nodes rebuilt,
laid out and painted per frame in about 6 ms with real text, 60 fps while
scrolling; frames are only built when something changes.
The command line
bin/myphp init my-app # a native-UI project (-template web for a page calling PHP) cd my-app && ../myphp/bin/myphp dev # runs it, restarts on changes, regenerates the TS client ../myphp/bin/myphp build # build/darwin-arm64/my-app.app and its .dmg, a launcher that execs this machine's php; the plugins' native libraries (the terminal's libghostty-vt) in its Resources ../myphp/bin/myphp build -launcher micro # one static binary: fetches static-php-cli and builds phpmicro the first time
Requirements
- PHP ≥ 8.3 with
ext-ffiandext-intl(Homebrew'sphphas both), OPcache JIT recommended - macOS on Apple Silicon for now
A window with a web page, whose frontend calls PHP:
/** Greeter says hello. */ final class Greeter { public function greet(string $name): string { return "Hello, $name"; } } Ipc::bind(new Greeter()); App::whenReady(fn() => new Window(new WindowOptions(title: 'Hello', html: '<script> myphp.call("Greeter.greet", "Ada").then(s => document.body.textContent = s)</script>'))); App::run(); // MYPHP_GENERATE=src/myphp.ts php app.php writes the typed client instead
Try it
./run.sh webview # Notes: a page calling a bound PHP service, with events ./run.sh gen # writes examples/webview/myphp.ts, the TypeScript client ./run.sh desktop # menus and dialogs ./run.sh form # text fields, input methods ./run.sh logview /var/log/system.log # a log viewer: a table that follows the file, filters, highlights, Cmd+F finds ./run.sh titlebar [hidden|hiddenInset] [x,y] # a hidden title bar: the view draws its own under the traffic lights (moved to x,y) and drags the window by it ./run.sh files [folder] # a file manager: places, breadcrumbs, icons or a sortable table, Cmd+F finds, Space previews, Delete… trashes ./run.sh code-editor # a code editor: files of several languages, completions of their words, hovers, TODO diagnostics, changes in the gutter, and their diff ./run.sh drag # draggable nodes: a card, a puck thrown to stops, a sheet; words dragged into a basket ./run.sh transfer # native drag and drop: a note dragged out to other apps; text, links and files dropped in; cards between lists ./run.sh router # a router: a sidebar, Back and Forward, links, notes sliding in, a settings layout ./run.sh flyout # flyouts: a list of colors under its button, a note editor beside its button, an About box in a system popover php tests/run.php # the headless tests of the UI (no window): layout, keys, focus, the widgets and files examples php tests/bench.php # the cost of a frame, headless: build, layout, record, paint, for the examples' views ./run.sh markdown-notes # Markdown notes: search, toolbar, resizable panes, live preview, autosave ./run.sh notes # mygo's notes: rich text notes, a toolbar of their formats, the Format menu, links, Copy Markdown ./run.sh native # mygo's native: a page calling PHP for dialogs, the clipboard, notifications, a context menu, the tray, a global shortcut, dark mode ./run.sh drag-drop # mygo's drag-drop: notes, a URL and a file dragged between two windows and other apps ./run.sh richtext # a rich text editor with a toolbar of its formats ./run.sh terminal # a terminal: your shell, emulated by Ghostty's libghostty-vt ./run.sh sqlite # SQLite from a web page through the PHP plugin; Reload shows a connection ends with its page ./run.sh watch [folder] # a web page watching a folder through the PHP plugin; the buttons change files ./run.sh websocket [url] # a WebSocket from a web page through the PHP plugin, to an echo server ./run.sh updater --demo # the update window, with a made-up update to install (--native: in native UI) ./run.sh game # a little game: WASD, the arrows or a gamepad, the wheel zooms ./run.sh charts # charts: bars, a live line, stacked areas, a scatter plot, a donut ./run.sh piano # a piano of synthesized keys: the pointer, the keyboard, a sustain pedal, tunes ./run.sh services # the app's services from a page: network, clipboard, Keychain, cookies, find, PDF… ./run.sh documents [x.md] # document notes: a window per file, the File menu, autosave, crash recovery ./run.sh protocol # a frontend at myphp://localhost/, images a scheme draws, a streamed body, a download ./run.sh photos ~/Pictures # a photo browser: thumbnail grid, one full size, arrow keys ./run.sh http # an HTTP client over curl: requests kept in a list, JSON colored ./run.sh glass # Liquid Glass over a photo ./run.sh widgets # the small controls on one page ./run.sh inbox # native UI, 300-row list, prints fps once a second ./run.sh inbox 1000 # 9 000 nodes per frame ./run.sh hello # mygo's examples, ported: a page greeting through a bound service ./run.sh counter-native # a counter in native UI, its buttons and the arrow keys ./run.sh wavelength # a GIF looping behind the window's content, paused and played by a button ./run.sh text-buffer 100000 # a text area over a TextBuffer of 100 000 lines ./run.sh text-input # a text field of the app's own drawing, through TextInputClient ./run.sh frameless # a frameless, transparent window with vibrancy, its page moving it ./run.sh clipboard # the clipboard in several formats, one made lazily; data dragged out and dropped in ./run.sh timeline # a timeline of tweens, placed after each other, at times and labels ./run.sh markdown-chat # replies streamed token by token as Markdown, revealed word by word ./run.sh embed-webview # web views inside native UI, and native popovers, selects and modals over them ./run.sh menubar-flyout # a tray icon's flyout in a system popover ./run.sh menubar-native # a menu bar app: a window under its tray icon that hides as it loses the focus ./run.sh launcher # a launcher: a global shortcut, the installed apps ranked as you type, their icons ./run.sh vibrancy # a window of vibrancy: a sidebar of materials sliding out ./run.sh todo # a todo list in a Vite page, its typed PHP service and events (needs bun) ./run.sh confetti # canvas-confetti's demos, every piece moved by poses that only paint ./run.sh effort-slider # an effort slider stepping into Galaxy, letters blurring as they change ./run.sh farm # a farming game: till, plant, water, harvest, day and night, music ./run.sh mayoi # a survivor game: sing at stage fright, gather stones, find your band ./run.sh gallery # the tour of the toolkit: every widget, lists, charts, motion, languages ./run.sh loading # loading.dev's 29 indicators, their size, color, easing and caps
Documentation
Getting started, then native UI: every widget, layout, input,
styling, motion and drawing, with examples that run (tests/docs_examples_test.php builds them). The app's
side: the app, windows, menus, desktop APIs,
bindings, the frontend, clipboard and drag data,
languages, the CLI, configuration,
building and distributing. Plugins: fetch,
SQLite, watch, WebSocket,
updater, loading, glass,
game, terminal, automation.
Layout
bin/myphp the command line: init, dev, build, micro
src/ App, Window, WindowOptions, Menu, Dialog, Ipc, TypeScript
src/Native/ OS bridges: ObjC runtime, Metal, CoreText, the event view
src/UI/ the toolkit: Context (tree building), Layout (flex), Text, Runtime (input), Painter
examples/ runnable apps
docs/ the guides (start with docs/getting-started.md; native UI: docs/ui/README.md) and design notes
AGENTS.md entry point for coding agents and contributors
License
MIT.
中文
用 PHP 写桌面应用,原生 UI 或网页前端二选一。
MyPHP 把 mygo 的思路搬到 PHP 8 上:
一个进程、一种语言、不写自己的 C 扩展,系统库全部通过 ext-ffi 调用。
每个窗口显示两种界面之一:
- 网页:用系统自带的 webview(macOS 上是 WKWebView),页面通过类型化的桥接调用 PHP 服务。
- 原生 UI:纯 PHP 编写,由 MyPHP 自己画到 GPU 上,没有 HTML、JavaScript 和 webview。
视图是应用状态的函数,每帧重跑(immediate mode),与 mygo 的
ui包同一思路。
一个原生 UI 窗口,视图是应用状态的函数:
use MyPHP\{App, Window, WindowOptions}; use MyPHP\UI\{Content, Context}; $n = 0; App::whenReady(function () use (&$n) { new Window(new WindowOptions(title: 'Counter', width: 320, height: 200, content: Content::view(function (Context $c) use (&$n) { $c->column(24, 12, 1); $c->text("Count: $n", 20); if ($c->clicked($c->button('Increment'))) { $n++; } $c->end(); }))); }); App::run();
现状
早期。目前在 macOS / Apple Silicon 上能跑的:
| 部分 | 状态 |
|---|---|
Objective-C 桥(objc_msgSend、方法为 PHP 闭包的类) |
可用 |
| WKWebView 窗口,JS → PHP → JS 往返 | 可用(examples/webview) |
| 原生 UI:Metal 渲染、CoreText 文字、flex 布局、横竖滚动、点击、hover | 可用(examples/inbox、form) |
mygo 的部件和上下文(v1.08):带操作、ID 和 closeToast 的 toast;openURLThen;onClick、onChange、onSubmit、onShortcut;Handle 和 focusBind;切换组、带刻度的步进滑块、大纲和大纲表格;列表的 followAnimation、表格的 roundRows 和 headerMenu;路径渐变;Preferences(强调色、减少动态效果、增强对比度);注册字体和 Font::families;Oklch;Image::update;自绘文字控件用的 TextLayout;TextBuffer;全部无外观的基础部件(checkboxBase … tooltipBase、selectBase、comboboxBase、文本输入) |
可用 |
mygo 的 app、窗口、页面与菜单,v1.09:Page(Window::page():loadFile、reload、前进后退、缩放、user agent、eval、window.open() 处理、标题/导航/加载/崩溃事件、JavaScript 弹窗以 sheet 显示);Event(emit、emitPage、broadcast);原生 UI 中的 web view(Window::newWebView、Context::webView);页面的偏好设置、语言和标题栏空间;App 生命周期事件、激活策略、关于面板、path、exit;Dock、Shell、Screen、Theme;Power::keepAwake;可自定义存储的窗口状态;菜单单选项,View/Format/Speech/Services/Window/Help 菜单角色;Tray::popUpMenu |
可用 |
mygo 的元素 API:CSS flexbox(间距、alignContent、wrapReverse、自动外边距、justify 的分布方式、最小最大尺寸、百分比、宽高比),绝对和相对定位的 inset,attach/attachTo 锚点,网格轨道;每角圆角、任意角度的渐变(sRGB 或 Oklab)、条纹、虚线边框、带扩展的内外阴影、分隔线、debug、invisible、passThrough、可继承的 disabled、focusRing(false)、滚动条内缩、全部光标;pointerPosition、dragVelocity、dragEnded、pressedOutside、overlayShortcut、dismissed、列表的 rowWidth 配合 scrollWith、TextLines、textRanges、setTextSelection、textSelection、textAnchorCaret |
可用 |
百万行列表:滚动条、scrollTo 平滑滚动、按行 key、选中与键盘导航 |
可用(examples/inbox) |
| 原生 UI 里的图片(PNG、JPEG、HEIC、ICNS…),可圆角、可透明、五种适配方式(contain/cover/fill/scale-down/natural)、可灰度 | 可用(examples/inbox、widgets) |
| 粗体、斜体、等宽文字;富文本 span(颜色、下划线、高亮、换行) | 可用(examples/inbox、logview) |
| 文字样式沿节点继承(字体族、字号、字重、斜体、行高、对齐、字距、OpenType 特性、语言、下划线、删除线、背景),移植 mygo 的文字系统(CoreText 排版器、光标、选区几何、双向文字、省略号),段落内可嵌链接和带样式的文字 | 可用 |
可选中文字(mygo 的 Selectable):拖选、双击选词、三击选行、Shift 加方向键跨段落扩展选区、复制与全选、selectedTexts()、copyData() 以多种格式复制 |
可用 |
| 把节点滚动到可见(mygo 的 ScrollIntoView),Tab 移到的焦点也会滚动到可见,嵌套滚动容器逐层处理 | 可用 |
原生界面的右键菜单和菜单按钮(mygo 的 ContextMenu、Menu、MenuButton):菜单项、勾选、子菜单、快捷键提示、最内层菜单优先、菜单键和 Shift+F10、输入框和可选中文字的编辑项、editItems() |
可用 |
软阴影、描边环;材质(UI\Material);glass 插件(mygo 的 plugins/glass,Plugins\Glass):Liquid Glass(模糊、边缘透镜折射、高光边、着色、按压放大)、沿遮罩渐变的背景模糊、滚动边缘 |
可用(examples/glass) |
| 文本框和多行文本区:输入法、选区、自动换行、撤销、Edit 菜单、Tab 焦点;复选框、单选、滑块、下拉选择、链接、进度条、提示 | 可用(examples/form) |
| 开关、步进器、分段控件、标签页、折叠区、可拖分栏、表格(按一列或多列排序,列可拖宽、双击适配内容、拖动换位,过宽时横向滚动)、分组侧栏、树、手风琴、星级、范围滑块、工具栏(含开关按钮)、模态对话框、弹出面板、菜单按钮、toast 提示、搜索框、数字输入、水平指示条、加载指示、徽标、分隔线、占位;单选组、原地编辑的文字(表格里的文件名)、分节标题吸顶且可输入首字母选行的列表、截取部分长度的路径、着色位图、逐个排版和绘制的字形 | 可用(examples/widgets、markdown-notes、http、gallery) |
| 主题:跟随系统明暗和强调色,实时切换 | 可用 |
myphp init / dev / build:脚手架、改动重启、.app 打包(exec 或静态 micro 启动器) |
可用 |
与 mygo 一致的打包(v1.12):-platform darwin/arm64,darwin/amd64,darwin/universal,resources 目录及按平台的子目录(通用二进制自动合并),PNG 或 asset catalog 生成图标,由内而外代码签名,排好窗口布局的 .dmg,公证,-upload 到 GitHub Releases 或 S3;myphp doctor、install-skills、version、help |
可用(签名、.dmg、公证要在开发者的 Mac 上跑) |
与 mygo 一致的开发流程(v1.13):myphp dev 运行 " Dev",一个带应用图标和资源的 bundle,改动后自动重建,终端里 r/c/q/h;SIGINT、SIGTERM 让应用像 Quit 一样退出;myphp.config.ts;页面用的包 myphp-runtime(packages/runtime,生成的 client 和插件都从这里 import)和 myphp-cli(packages/cli);Vite + TypeScript 的 web 模板 |
可用(开发版应用要在开发者的 Mac 上跑) |
与 mygo 一致的控件和按键(v1.14):密码框;对话框打开时窗口快捷键不生效;列表按条目的 key 保持位置、选中项和行状态,滚进未测量的行时按步长精确滚动;按键滚动指针下的容器,列表里 Page Up/Down 像 AppKit 一样只滚动;步进器左右键、滑块 Page Up/Down;键盘焦点也显示提示,按下或 Esc 隐藏;文本框 Control-K;JPEG 照片按 EXIF 方向摆正;侧栏输入首字母选择;其他键盘布局下快捷键按键位匹配;MYPHP_FRAME_STATS |
可用(照片解码、帧统计和其他布局的按键要在开发者的 Mac 上验证) |
按 mygo 测试补齐的列表(v1.15):任意行高无缝隙,滚轮或按键按步长精确滚动,scrollTo 按实测高度对齐到顶部、居中或底部,跟随末尾(新行或行变高时,可配 FollowAnimation 平滑),焦点所在行滚出视野仍保留,Tab 会把下一行滚进来,滚动条拖动与行的位置一致,行停在吸顶分节标题之下,滚动容器里的列表高度等于所有行 |
可用 |
按 mygo 测试补齐的文本框和 toast(v1.16):逐步撤销(移动光标结束一步),撤销连同应用对输入的改写,应用追加文本时视图停在滚轮所在处,文本框失焦时显示开头、按 textAlign() 对齐,焦点离开时 text input client 取消标记,toast 在指针悬停和窗口转入后台时暂停计时,焦点在 toast 里时 Esc 关闭它 |
可用 |
按 mygo 测试补齐的基础控件、样式和窗口(v1.17):焦点进入应用自建的模态层,禁用的滑块不响应,onTextKey 只接收不产生文字的按键,按住时保持悬停,网格按 align 对齐单元格,文本区占位符自动换行,表单标签对齐文本区第一行 |
可用 |
| 按 mygo 测试补齐的从右到左布局、指示器、日期和组合框(v1.18):文字在列中按对齐方式铺满,范围滑块、分栏、输入框、弹出层和菜单镜像,从右到左段落里按词移动跟随箭头方向,步进器和范围滑块响应快速点击,颜色色板按应用语言命名,组合框弹出列表的辅助功能和滚动与 AppKit 一致 | 可用 |
按 mygo 测试补齐的焦点作用域、变换、SVG 和网页视图(v1.19):下拉选择改用 mygo 的窗口内弹出列表,弹出层在 Tab 顺序中紧跟锚点,裁剪区域经变换后也限制指针,光标和文本输入客户端的几何经变换换算,自定义节点可接收输入法文字(textCaret),图标继承周围的字号和颜色 |
可用 |
按 mygo 测试补齐的多选、表格、控件和行内文字(v1.20):列表报告选择变化,按字母选行与 AppKit 一致,Shift 扩选跟随原条目,按下当帧即显示结果,clickModifiers(),表格分组标题横跨各列、连续选中行圆角连成一块,日历和取色器弹层保持自身宽度 |
可用 |
| 按 mygo 测试补齐的基础组件、表单、反馈、开关和主题(v1.21):焦点在复选框时 Enter 先交给窗口快捷键,Esc 不再丢失焦点,手风琴每个标题都是一个 Tab 停靠点、方向键在标题间移动,工具栏溢出菜单标出已按下的开关,构建后才禁用的 fieldset 也禁用其内容,强调色计算与 mygo 一致,未指定语言的文字按应用语言排版,无头测试绘制工具提示 | 可用 |
按 mygo 测试补齐的大纲、重绘、滚动、选区和跟随末尾(v1.22):after() 按帧时钟计时,转圈指示器只在下一根辐条时重绘,容器在视口外时其绝对定位或变换后的子节点仍会绘制,输入框支持文字背景色、选区色和文字颜色、光标用强调色,富文本也有内容过渡动画,滚动偏移始终限制在内容范围内、scrollTo 可指定动画,跟随末尾的列表在改变高度和删行时不再错位 |
可用 |
按 mygo 测试补齐的粘贴、文本行、行内对象和文本缓冲(v1.23):编辑菜单命令统一经由运行时处理(菜单和 Cmd+V 都会触发 onPaste,输入框支持撤销和重做),行内对象在排版时就占据自身宽度,文本区排版前也能估算行位置,文本缓冲正确计算非法 UTF-8,未启用选择的列表也能拖动排序 |
可用 |
文字按布局分配的宽度自动折行,与 mygo 一致(v1.24):在列中折行;在行中可收缩到最长的单词再折行;中文可在字间断行;配合 maxLines() 限制行数;窗口改变大小时当帧重新折行 |
可用 |
广色域颜色,与 mygo 一致(v1.25):超出 sRGB 的 Oklch::rgb 颜色保留 Display P3 色值,在 P3 屏幕上以 float16 绘制填充、阴影、文字和装饰线,其他情况绘制最接近的 sRGB 颜色;WideColor::srgb 取 sRGB 近似色 |
可用(examples/gallery 的 Wide colors) |
Go 的随机数(v1.27):Random\Rand::pcg($a, $b) 与 Go 的 rand.New(rand.NewPCG(a, b)) 产生相同序列(IntN、Float64、Shuffle、Perm、NormFloat64…),带种子的例子与 mygo 一致;移植 mygo 的 native、notes、drag-drop 例子;文字按字体平滑绘制(颜色越浅越粗,与 AppKit 一致);边框在布局中占位;按钮文字可单独设样式;无论 php.ini 怎么设,日期都用系统时区 |
可用(examples/farm、notes、native、drag-drop) |
在 Mac 上逐个点按例子(v1.28):托盘图标留在菜单栏;拖动预览显示元素本来的颜色;弹出框不再打乱其后节点的 id,notes 的链接输入框能接收键入;去掉 v1.27 移植的 Go 随机数,例子改回 PHP 自带的引擎;-launcher micro 构建的 app 重新可以签名(phar 放进可执行文件的 __LINKEDIT),并带上框架用到的 intl、xmlreader、curl、sockets、filter(myphp micro 会重新编译一次 micro.sfx),phar 里的翻译目录也能读到;out 位于 frontendDist 内时拒绝构建,todo 例子与 mygo 一样输出到 build;第一次播放声音不再闪退(音频队列地址改用 ObjC::addr);静态 app 的终端和前端页面能从 phar 里运行;14 个例子与 mygo 逐对比对:输入框高度与 mygo 一致且支持 fontSize(),打字后 changed() 能报告,select 有最小宽度,web view 不再盖住外框边框,禁用按钮的边框不再消失,列表可用 justify(END) 把行贴到底部;flyout 例子改为 mygo 的版本 |
可用(examples/native、drag-drop、notes;myphp build -launcher micro) |
与 mygo 一样的发布流程(v1.29):每次 push 由 CI 跑 PHP 和 Bun 测试(.github/workflows/ci.yml);推 v<版本> tag 时检查 bin/myphp 和各 npm 包的版本一致(php scripts/version.php 负责设置),用 npm 的 Trusted Publishing 发布 myphp-runtime、myphp-cli 和插件的 @myphp-plugins/*,并创建 GitHub Release(release.yml);从 npm 装的 myphp-cli 会运行项目里 Composer 装的 MyPHP(vendor/owner888/myphp/bin/myphp);npm 包里不再带测试文件 |
可用(插件包的第一次发布和 npm 上的 Trusted Publisher 配置由开发者在 npmjs.com 完成) |
| 布局速度找回文字折行之前的水平(v1.30):放在太窄的行里的文字,最长的单词不再被拆开(之前最小内容宽度算成了一个字形的宽度,每个字形都排成一行);每段可能折行的文字,其最小内容宽度直接取自测量它的那一行,不再每帧重新排版:2000 段文字的布局从 14 ms 降到 4 ms,百万行列表从 0.35 ms 降到 0.12 ms;每帧被替换掉的节点树立即释放,不再等 PHP 的垃圾回收(滚动列表时回收会造成掉帧);改为按显示器刷新出帧(视图的 display link,与 mygo 一致),不再一有变化就立刻出帧 | 可用(tests/wrap_test.php、tests/lists_test.php) |
图片解码挪出主线程(v1.31):Image::fromBytes / fromFile 交给 CoreImage 在 NSOperationQueue 的线程上解码,主线程每张图只花约 0.2 ms(之前一张 HEIC 要 200 ms 以上);在 fiber(App::go)里等待时 app 照常绘制,fiber 外照旧阻塞到解完;HEIC 照片按自身方向转正(之前竖拍的会横着显示);photos、files 例子和 Markdown 视图都在 fiber 里加载图片,Markdown 在图片就绪前显示描述,与 mygo 一致 |
可用(tests/image_test.php、tests/markdown_view_test.php) |
下划线避开字母下伸部分(v1.32):与 AppKit、mygo 一样,下划线在 g、y、p 等字形穿过它的地方断开,两侧各留一个线宽;汉字、假名、谚文和空格不断开;字形轮廓来自 CoreText(CTFontCreatePathForGlyph),按字体、字形缓存 |
可用(tests/text_test.php) |
视频播放(v1.33):Media\Player 播放 MKV、WebM、MP4、MOV、M4V(H.264、HEVC;AAC、AC-3、E-AC-3、MP3),PHP 只读数据包(MKV 由 PHP 解析),解码和显示都交给系统(AVSampleBufferDisplayLayer 与音频渲染器、同步器),画面不经过 PHP;两小时的 1080p 影片打开 15–20 ms,播放时进程占 2–3.5 % CPU;跳转落在精确时间,切换音轨从当前位置接上;Context::movie 让界面叠在画面上(示例 video:控件自动隐藏、进度条、音轨、音量、全屏) |
可用(tests/media_test.php、tests/player_test.php) |
字幕(v1.34):内嵌文字字幕(MKV 的 SRT、ASS/SSA、WebVTT,MP4 的 tx3g)和外挂字幕文件(.srt、.ass、.ssa、.vtt,UTF-8、UTF-16 或 GB18030);影片旁边同名的字幕文件自动加载,与 IINA 一致;切换字幕轨、字幕延迟;示例 video 在画面上画带描边的字幕,拖入字幕文件即可加载 |
可用(tests/subtitles_test.php、tests/player_test.php) |
仿 IINA 的播放器(v1.35):节点下的系统材质(Node::systemMaterial:窗口的 NSVisualEffectView 各材质或 macOS 26 的玻璃,随节点淡入淡出)、SF Symbols 图像(Image::symbol)、隐藏红绿灯(Window::setWindowButtonVisibility)、锁定窗口比例(Window::setAspectRatio)、Player::videoTrack;示例 video 按 IINA 窗口布局:标题栏、玻璃控制条、OSD、快速设置侧边栏、IINA 默认快捷键 |
可用(tests/material_test.php、tests/image_test.php、tests/examples_test.php) |
播放更省 CPU(v1.36):播放器定时器降到每秒 4 次,两次之间字幕变化时按时唤醒;示例 video 只在显示的时间有变化时才画新帧,控制条隐藏时不画 |
可用(tests/player_test.php、tests/subtitles_test.php) |
| VP9、AV1 硬件解码与 Opus(v1.37):Mac 能硬件解码时(VP9 所有 Apple 芯片,AV1 需 M3 起),MKV、WebM、MP4 里的 VP9、AV1 交给系统解码器播放(VP9 的 vpcC 由首个关键帧生成);支持 WebM 常用的 Opus 音频 | 可用(tests/media_test.php、tests/player_test.php) |
给应用用的视频播放器(v1.38):Context::mediaPlayer 在 Media\Player 上搭出 IINA 式窗口(标题栏、玻璃控制条、OSD、快速设置侧边栏、IINA 快捷键;MediaPlayerState、MediaPlayerOptions),界面文字跟随应用语言;Player::setAspectRatio、setCrop、setRotation、setAudioDelay;示例 video 只剩几十行 |
可用(tests/mediaplayer_test.php、tests/media_test.php、tests/player_test.php) |
滚轮与续播(v1.39):播放器上下滚动调音量、左右滚动拖进度;记住每部片子停在哪里,下次打开接着播(Media\PlaybackPositions、Player::path、MediaPlayerOptions::$resume) |
可用(tests/mediaplayer_test.php) |
播放列表与画中画(v1.40):MediaPlayerState::open 打开影片(Context::mediaPlayer($state)),同文件夹的影片按 Finder 顺序成为播放列表(Media\Playlist),左侧侧边栏显示,播完自动下一集,⌘→ / ⌘← 切换;控制条可开画中画 |
可用(tests/mediaplayer_test.php) |
章节与播放列表的循环、随机、排序(v1.41):读取 MKV、MP4 的章节(Player::chapters),显示在播放列表侧边栏的章节页,双击跳转;单集循环、列表循环、随机播放和排序菜单与 IINA 一致;菜单支持小标题(UI\Menu::header);播放时阻止熄屏(MediaPlayerOptions::$keepAwake 可关),全屏时鼠标也随控制条隐藏 |
可用(tests/mediaplayer_test.php、tests/media_test.php、tests/menu_test.php) |
| FLAC(v1.42):MKV、MP4 里的 FLAC 音频交给系统解码(magic cookie 按 AVFoundation 的 dfLa 盒子生成) | 可用(tests/media_test.php、tests/player_test.php) |
图形字幕(v1.43):MKV 里蓝光的 PGS、DVD 的 VobSub 字幕轨,用 PHP 解码(Media\Pgs、Media\VobSub),按原位置叠在画面上(Player::subtitlePictures、subtitlePlane) |
可用(tests/subtitles_test.php、tests/player_test.php、tests/mediaplayer_test.php) |
ASS 样式(v1.44):ASS、SSA 字幕按样式表显示字体、字号、颜色、描边、阴影和底框,按对齐、边距、\pos 定位,支持常用行内标签,以及移动、淡入淡出、\t 动画、卡拉 OK、旋转、缩放和裁切,排版照 libass(Media\Ass、Player::subtitleLines) |
可用(tests/subtitles_test.php、tests/player_test.php、tests/mediaplayer_test.php) |
字幕在玻璃下面(v1.45):Node::belowMaterials 把节点画进窗口里一个位于系统材质之下、视频之上的图层;播放器的字幕因此在控制条玻璃下面,和 IINA 一致;文字描边和阴影(Node::textOutline、textShadow),由字形自身外扩生成 |
可用(tests/material_test.php、tests/style_more_test.php) |
平滑的移动字幕(v1.45.1):构建帧时调用 animationFrame() 现在能请求到下一帧(之前会丢);移动的 ASS 字幕(弹幕)按屏幕刷新率逐帧绘制,用变换按小数像素平移 |
可用(tests/repaint_test.php、tests/mediaplayer_test.php) |
播放器事件、关键帧 seek、正在播放、逐帧、截图(v1.46):Player::on 新增 play、pause、rate、seek、track 事件,变化时各通知一次;Player::seek($s, exact: false) 按方向跳到最近的关键帧,方向键、前进后退按钮和滚轮与 IINA 一样用它(MediaPlayerState::seekBy);影片显示在系统的"正在播放"里,并响应媒体键,与 IINA 一致(MediaPlayerOptions::$nowPlaying);逐帧前进后退(Player::step,. ,)和当前帧截图(Player::snapshot,⌘S 存到 ~/Pictures/Screenshots) |
可用(tests/player_test.php、tests/mediaplayer_test.php) |
进度条缩略图(v1.47):鼠标悬停在进度条上时显示该处的时间和画面缩略图,与 IINA 一致;Player::thumbnail 用 VideoToolbox 单独解码指定时间的关键帧,不影响正在播放的画面(1080p 每张约 5 毫秒,在两帧之间生成);缩略图照 IINA 缓存到磁盘(Media\ThumbnailCache) |
可用(tests/player_test.php、tests/mediaplayer_test.php、tests/thumbnail_cache_test.php) |
时间点回调与 A-B 循环(v1.48):Player::at 在播放经过某个时间点时回调,对应 AVPlayer 的边界时间观察者;IINA 的 A-B 循环(⌘L,MediaPlayerState::abLoop),循环点以小滑块显示在进度条上,可拖动 |
可用(tests/player_test.php、tests/mediaplayer_test.php) |
网络播放(v1.49):new Player('https://…/film.mkv') 边下边播 Matroska 和 WebM,由 run loop 驱动的 libcurl 预读到与 IINA 同样大小的缓存(Native\CurlLoop、Media\HttpBytes);缓存不足时像 mpv 一样暂停等待(buffering 事件、bufferingPercent),并显示 IINA 的缓冲提示;示例应用加了“打开网址” |
可用(tests/http_bytes_test.php、tests/player_test.php、tests/mediaplayer_test.php) |
网络 MP4(v1.50):new Player('https://…/film.mp4') 边下边播 MP4 和 MOV,像 FFmpeg 的 mov 解复用器一样用 PHP 读取(Media\Mp4):moov 在开头或结尾都行,支持 H.264、HEVC、VP9、AV1、AAC、MP3、AC-3、E-AC-3、Opus、FLAC、tx3g 字幕、编辑列表和章节 |
可用(tests/mp4_test.php、tests/player_test.php) |
HLS 和 MPEG-TS(v1.51):new Player('https://…/master.m3u8') 像 mpv 一样播放 HLS:选最高码率的变体,它的音轨分组作为音轨列出(只下载正在播的那条),支持 MPEG-TS、fMP4 和纯音频(AAC、AC-3)分段、AES-128、会持续刷新的直播列表和不连续点;分段预读进与 IINA 同样大小的 150 MiB 缓存。本地或网络的 .ts、.m2ts、.mts 也能直接播(Media\Ts):H.264、HEVC、AAC、AC-3、E-AC-3、MP3 |
可用(tests/hls_test.php、tests/ts_test.php、tests/player_test.php) |
yt-dlp 与 AV1 软解(v1.52):网页地址(YouTube 及 yt-dlp 支持的站点)像 IINA 一样播放:yt-dlp 选出的最佳视频和音频合并播放(Media\YtDlp、Media\Merged),带上页面的请求头、标题、章节、字幕和播放列表;读取并按索引定位分片 MP4(DASH);视频、音频两路合用 IINA 的一个 150 MiB 缓存;系统没有 AV1 解码器时(或 Player::$hardwareDecoding 关闭时)用 libdav1d 软解 AV1,myphp build 会把它打包进 app |
可用(tests/yt_dlp_test.php、tests/dav1d_test.php、tests/mp4_test.php、tests/player_test.php) |
副字幕与 YouTube 广告(v1.53):第二条文字字幕显示在画面顶部,纯文本,有独立的延迟和开关,和 mpv 的 secondary-sid 一致(Player::setSecondarySubtitleTrack);YouTube、Twitch 的网页照 mpv 的 ytdl_hook 先交给 yt-dlp;视频前有广告时,等广告时长过去再打开各路格式(yt-dlp 的 available_at),不再被拒(HTTP 403);节点 id 改用 mygo 的规则(父节点 id 与自身位置混合),内容出现或消失只影响其后的兄弟节点,滚动位置、输入焦点不再错乱 |
可用(tests/player_test.php、tests/yt_dlp_test.php、tests/ids_test.php) |
| 广告倒数(v1.54):打开前面带广告的网页视频时,加载提示显示广告剩余秒数(「正在跳过广告… 13 秒」),之后显示「正在打开流媒体……」 | 可用(tests/mediaplayer_test.php) |
Vorbis 与外挂音轨(v1.55):系统没有 Vorbis 解码器,Matroska、WebM 里的 Vorbis 音频改用音频模块自带的 PHP 解码器(Audio\Vorbis\Codec)解码,以 PCM 播放,支持立体声到 7.1;可在侧栏「Load External Audio」加载外部音频文件,与影片同步播放(Player::loadAudio,和 IINA 一致);IINA 的十段均衡器及其预设(Player::setEqualizer),只有某段增益不为 0 时才自行解码并滤波,平直时走原来的播放路径 |
可用(tests/soft_audio_test.php、tests/equalizer_test.php、tests/player_test.php) |
均衡器自定义配置(v1.56):和 IINA 一样,可在均衡器弹出菜单里把当前增益保存为自定义配置,并重命名、删除;命名框与 IINA 相同,名字为空或与预设、已有配置重名时不能确定;修复 ./run.sh video 的调试日志在弹窗停留超过 3 秒时卡死程序 |
可用(tests/mediaplayer_test.php) |
| 10-bit AV1 软件解码(v1.57):10-bit AV1(HDR 片源及 YouTube 1080p 以上常用)和 8-bit 一样由 libdav1d 解码,以 x420 像素缓冲(与 VideoToolbox 解 HEVC Main 10 的格式相同)交给显示层,带上片源标注的色彩信息,缩略图也支持;硬件解码时按容器标注的色彩信息显示(HDR 的 Matroska AV1 不再发灰);软件解码 60 帧影片不再持续掉帧(显示层能接收时主动要帧),拖动进度时也不再因 libdav1d 从关键帧解码而卡住窗口;修复点过播放器侧栏里的下拉按钮后空格不再播放/暂停 | 可用(tests/dav1d_test.php、tests/player_test.php、tests/media_test.php、tests/menu_test.php) |
| 拖动进度(v1.58):硬解和软解的视频都分片交给渲染器,每次最多占主线程 10 ms,期间照常绘制窗口;上一次精确跳转还没完成时,新的跳转只保留最后一次,完成后再执行,和 mpv 一致;滑块只在值真正变化时才报告 changed,和 mygo 一致 | 可用(tests/player_test.php、tests/primitives_test.php) |
| HDR 元数据(v1.59):把片源的母版显示参数和亮度信息(MaxCLL、MaxFALL)交给系统做色调映射,来源是 Matroska 或 MP4 容器,软件解码时优先用每帧 AV1 码流自带的,和 AVFoundation、mpv 的做法一致 | 可用(tests/media_test.php、tests/dav1d_test.php) |
播放中切换硬件解码(v1.60):视频页的开关和 IINA 的快速设置一样,可在播放中把 AV1 影片在系统硬解和 libdav1d 软解之间切换,从当前位置继续,下一部影片沿用;只支持一种解码方式的影片开关固定不可切换;OpenSubtitles 在线字幕(字幕页的「在线查找」,和 IINA 一样按文件哈希和文件名搜索,语言用偏好语言或系统语言,多个结果时弹出 IINA 的选择列表),默认使用 myphp 内置 key,也可由应用提供(MediaPlayerOptions::$openSubtitlesKey);设置窗口里可选填 OpenSubtitles 账号、设置偏好语言(Context::mediaPlayerSettings,对应 IINA 的字幕设置页);先按文件名解析出片名、年份、季集(移植 agc_player 的解析器)去 TMDB 查到 tmdb id,再按 id 搜字幕,查不到时回到 IINA 的哈希加文件名搜索 |
可用(tests/mediaplayer_test.php、tests/opensubtitles_test.php) |
播放器设置窗口对齐 IINA(v1.61):侧栏、搜索、小节索引、卡片式页面;IINA 的「通用」页全部做完并生效(打开媒体时的行为、最小化/失焦/休眠时暂停、播放完保持窗口、旧式全屏和其他屏幕黑屏、屏保、续播、播放历史和最近打开、播放列表、循环、截图的位置/格式/剪贴板/预览),「视频」「字幕」页暂有部分,文案用 IINA 的中英文字符串;设置按 IINA 的键名存取(Media\PlayerSettings);IINA 的欢迎窗口和播放历史窗口(Context::mediaPlayerWelcome、Context::mediaPlayerHistory);examples/video 按 IINA 的方式多窗口打开 |
可用(tests/opensubtitles_test.php、tests/mediaplayer_windows_test.php) |
播放器设置全部对齐 IINA(v1.62):「界面」「视频」「音频」「字幕」「网络」「控制」「按键绑定」「高级」「工具」页全部做完并生效(OSC 布局和工具栏、窗口初始大小位置、OSD、缩略图、画中画、HDR、输出设备、超过 100% 的音量、ReplayGain、无缝播放、首选语言、文本字幕样式和位置、IINA 的外挂字幕匹配、编码、缓存、User-Agent 和代理、yt-dlp、各种点击、捏合、Force Touch 和滚轮、设为默认应用、清除缓存);音乐模式;IINA 的按键绑定套件和编辑器,执行 mpv 命令;基于 Vision 的 Live Text;应用日志(MyPHP\Logger)和日志查看器(Context::logViewer) |
可用(tests/mediaplayer_test.php、tests/mediaplayer_keys_test.php、tests/subtitle_matcher_test.php、tests/logger_test.php) |
| 播放中跳转不再卡主线程(v1.63):音频渲染器的 flush 和时钟的新时间交给系统的串行队列去做,那个线程上不跑 PHP,做法同 mpv 的播放线程;一次跳转占用主线程从 74 ms 降到 12 ms(4K HDR AV1,硬解) | 可用(tests/player_test.php、tests/mediaplayer_test.php) |
ASS 按 libass 的方式绘制(v1.64):绘图(\p,含 B 样条)、带透视的 \frx \fry、\fax \fay、\blur \be、矩形和绘图的 \clip \iclip(GPU 遮罩)、\kf 扫色,每个片段各用自己的参数;填充半透明时描边挖空;BorderStyle 3 底框按行绘制;字幕设置里的模糊;字形和图片图集按单个位图淘汰,不再整块重建 |
可用(tests/subtitles_test.php、tests/mediaplayer_test.php、tests/transform_test.php、tests/atlas_test.php) |
子树复用(v1.65):Context::memo($key, $deps, $build) 让界面的一部分跨帧保留,只要构建它所依赖的值不变、指针、按键和焦点都没有触及它,就不再重新构建、布局、记录和绘制(设置页每帧约 6 → 1.3–2.2 ms);每帧的热点也做了优化(1000 行平移列 44 → 37 ms) |
可用(tests/memo_test.php) |
Metal 只重绘变化的部分(v1.66):与上一帧完全相同的帧不提交;否则只把这个 drawable 上次绘制以来变化的矩形(最多八个,互不相邻)清掉重画,其余保留,与 mygo 相同;MYPHP_PARTIAL=0 每帧整帧绘制 |
可用(tests/damage_test.php) |
对照 mygo 的修复,第一批(v1.67):双击按元素计数;别的应用保存后立刻改名的文件,文档也能跟上;目录改名按文件标识配对(2 万个文件 0.2 s);行里百分比的最大宽度生效;按内容撑开的 diff 在绘制前就折叠未改动的行;表格、下拉弹出层、过渡动画、滚动条和托盘与 mygo 一致;更新器每次启动都检查、跟随应用语言、重启进入新版本;flyout 的材质和圆角(FlyoutOptions::$vibrancy、$cornerRadius)、WindowOptions::$hideWindowButtons、PageOptions::$userAgentSuffix、Audio::setLatency、拖拽结束的 Result::$escaped 和 $point、ListState::$keepOffset;检查器里广色域颜色显示为 oklch() |
可用(tests/click_count_test.php、tests/documents_test.php、tests/file_watch_test.php、tests/flex_test.php、tests/code_editor_parts_test.php、tests/updater_test.php) |
布局和文字对齐 CSS 与 mygo(v1.68):flexbox 按 Chrome 的方式排布(不小于内边距、按内尺寸收缩、基准尺寸不受最小最大值影响、最小值压过最大值、行按子项的 basis 算内容宽度);行和网格支持 Node::BASELINE 基线对齐;Node::hidden() 移出布局但保留状态;分栏在小窗口里让出空间,新增 Context::splitPercent;中文和日文每个字算一个词;网格子项的百分比按所在区域算;超过 4096 像素的路径分块绘制 |
可用(tests/flex_test.php、tests/widgets_more_test.php、tests/cjk_words_test.php、tests/svg_test.php) |
输入手感(v1.69):鼠标滚轮每一格平滑滚动,和浏览器一样,连续多格会累加,应用自己滚动时立即接管;内层容器滚到头后滚轮交给外层;触控板捏合以 InputEvent::ZOOM 传给原生 UI;左右轻扫前进后退;滚动带惯性标记,终端的 Command 缩放会忽略惯性;PageOptions::$swipeNavigation 和 $pinchZoom;快速按下拖动拖拽区也能移动窗口;原生拖拽停在容器边缘时自动滚动 |
可用(tests/wheel_test.php、tests/window_drag_test.php、tests/datadrag_test.php、tests/terminal_test.php) |
绘图能力(v1.70):路径描边支持平头和方头线帽、尖角和斜角连接、虚线,路径逐渐画出时虚线位置不变;径向渐变(Node::radialGradient、Painter::fillRadialGradient);Context::setZoom 清晰缩放窗口内容,指针、菜单、拖拽、输入法和辅助功能都跟着换算;内联对象可以伸到基线以下,由 Node::drawObjects 绘制,Markdown 通过 Options::$inlineMath 绘制行内公式;两端对齐和首行缩进的段落、软连字符、偏离基线的文字片段和 TextLayout::lines();富文本的首行缩进、段间距、段落 ID 和 Document::visibleParagraphs |
可用(tests/svg_test.php、tests/style_test.php、tests/zoom_test.php、tests/paragraph_test.php、tests/text_more_test.php、tests/markdown_view_test.php、tests/richtext_paragraphs_test.php) |
窗口内绘制的菜单(v1.71):Node::popoverMenu 和 popoverMenuButton 用主题样式在窗口里绘制右键菜单和菜单按钮的菜单,键盘操作、首字母跳转、子菜单、焦点和辅助功能角色与系统菜单一致;单选菜单项(MenuItem::radio);菜单项图标,托盘菜单也支持(MenuIcon::of、MenuItem::setIcon);日历可以只翻月份而不选中日期(Node::browse),翻月箭头居中 |
可用(tests/popover_menu_test.php、tests/menu_icon_test.php、tests/datetime_test.php) |
多色标渐变、圆锥渐变、元素模糊(v1.72):线性和径向渐变最多支持 32 个 GradientStop 色标;圆锥渐变(ConicGradient、Node::conicGradient、Painter::fillConicGradient、fillPathConicGradient、strokePathConicGradient);用渐变绘制边框(Node::borderLinearGradient、borderConicGradient);Node::blur 在 GPU 上模糊节点及其子节点,效果同 CSS 的 filter: blur() |
可用(tests/gradient_test.php、tests/style_test.php) |
拖选(v1.73):ListState::$dragSelect 拖过哪些行就选中哪些行,靠近边缘时自动滚动;$cellSelect 让表格选中一块单元格(ColumnRange、TableColumn::$rowHeader、cellSelected、selectedColumns、selectColumns),支持单击、Shift、Cmd、拖动和方向键;右键会先选中所在行,$menuKeepsSelection 可保留原选择;$cellMenu 给单元格加右键菜单,Context::rowDoubleClicked 让单元格得知所在行被双击 |
可用(tests/drag_select_test.php) |
| 遮挡剔除(v1.74):被后面不透明的面板、表单或弹出层完全盖住的内容不再交给 GPU 绘制,画面完全一致;帧统计会记录剔除数量 | 可用(tests/cull_test.php) |
终端与剪贴板(v1.75):程序读剪贴板按 Options::$clipboardRead 处理(默认拒绝,可允许,或通过 $onClipboardRequest 或终端自带的提示框询问);剪贴板里只有图片时发送空的 bracketed paste,Claude Code 借此读取截图;全选也按 $copyOnSelect 复制,Terminal::setCopyOnSelect 可随时切换 |
可用(tests/terminal_test.php) |
分发(v1.76):签名前嵌入 macos.provisioningProfile,供 Communication Notifications 等受限权限使用;保留 App 自带的各语言 InfoPlist.strings,权限弹窗的说明文字能按系统语言显示;来信通知可以显示发送者头像(NotificationSender) |
可用(tests/build_test.php、tests/app_test.php) |
文件监视(v1.77):macOS 改用 FSEvents,每棵监视的目录树一条流,默认上限 65536 个条目;FileWatchOptions::$hintsOnly 不保留快照、不 stat 文件,只给出发生变化的目录的 FileHint 事件(系统丢了变化时给 ".");监视只登记目录 |
可用(tests/file_watch_test.php) |
实时图表折线(v1.78):Chart::minMax 只保留每个像素列的最高点和最低点,Painter::strokePolyline 把不透明折线逐段画成胶囊形,Chart::lineInto 复用已有路径(examples/chart-streaming);TextState::textAnchorCaret;FSEvents 流重新从当前时刻开始 |
可用(tests/chart_test.php、tests/examples_test.php) |
| Markdown 线性时间解析(v1.79):多行段落里的行内元素用二分查找源码偏移;一个链接闭合时,一步就让它之前的方括号全部失效 | 可用(tests/markdown_parser_test.php) |
着色器(v1.80):mygo 的 ui/shader,用 Go 语法(Ebitengine 的 Kage)写片段着色器,在 PHP 里编译成 Metal:Shader::compile、draw,以 fill/fillPath/strokePath/text 代替颜色填充,Values 和 Uniform,四张图片(图片或 canvas),元素特效(Node::effect:ColorEffect、DistortionEffect、LayerEffect),读取背后画面(backdropSample) |
可用(examples/shaders、tests/shader_test.php、tests/shader_compiler_test.php、tests/shader_msl_test.php) |
大 canvas、可设置样式的输入框(v1.81):canvas 每边最大 8192 像素(超过图集一页的 canvas 渲染到独立纹理),blackhole 按窗口实际像素渲染;input() 采用设置的内边距、圆角、背景、边框和阴影,与 mygo 的 TextInput 一致,wind 的输入框和 chat-glow 的输入栏改用 input;着色器每次绘制的 uniform 按连续段一次打包(wind 从 106 µs 降到 7 µs),编译快约四分之一 |
可用(tests/canvas_test.php、tests/textarea_test.php、tests/examples_test.php、tests/shader_test.php) |
| 输入框文字按边框裁剪(v1.82):没有左右内边距的输入框(如 chat-glow 的输入栏)第一个字母不再被遮住一部分,与 mygo 一致 | 可用(tests/textarea_test.php) |
| 终端指针与 Ghostty 一致、头像转 PNG(v1.83):shell 上显示 I 形光标,程序接管鼠标时显示箭头(按住 Shift 仍是 I 形),程序用 OSC 22 指定的指针形状照样显示;通知发送者头像先转成 PNG 再交给系统,SVG 头像也能显示 | 可用(tests/terminal_test.php) |
终端的清屏、复制、链接、拖放与配色接口(v1.84):Terminal::clearScreen/clearScrollback 和 Options::$onClear,右键菜单的「清除」与 ⌘K 相同;Options::$onCopy 告知复制内容和来源;按住 Cmd 悬停的链接加下划线,Terminal::hoveredLink() 返回链接和位置;拖入文件会粘贴按 shell 规则转义的路径;快照保留屏幕末尾的空行;Terminal::useTheme 让没有视图的终端也能回答颜色查询,并在明暗切换时通知订阅的程序(mode 2031) |
可用(tests/terminal_test.php) |
终端内边距与最小对比度(v1.85):Options::$padding 和 $paddingColor(内边距显示背景色,或像 Ghostty 的 window-padding-color 那样把边缘单元格的背景色延伸进去;程序自绘的光标不外延);Options::$minimumContrast 和 setMinimumContrast,文字与背景对比度不足时以最小幅度调深或调浅,制表符、色块等图形保持原色 |
可用(tests/terminal_test.php) |
| 跳过不可见的绘制(v1.86):记录每帧时算出每个节点的绘制范围(自身、阴影和子节点,含变换),范围完全在裁剪区外的子树整棵跳过:移出视野的 1000 行,一次绘制从 13.5 ms 降到 0.7 ms | 可用(tests/paintcull_test.php) |
终端命令结束通知(v1.87):按 shell 的 OSC 133 标记实现 Ghostty 的 notify-on-command-finish:Options::$notifyOnCommandFinish(从不、未聚焦时、总是)、$notifyOnCommandFinishAction(响铃、桌面通知「命令执行成功/失败」,附运行时长和退出码)、$notifyOnCommandFinishAfter |
可用(tests/terminal_test.php) |
zsh 与 bash 的 shell 集成(v1.88):单独启动的 zsh 或 bash 照常读取用户的启动文件,并用 OSC 133 标记提示符和命令,命令结束通知在这两个 shell 里也能用;Options::$shellIntegration 可关闭 |
可用(tests/terminal_test.php) |
裸文件名链接、圆角端帽、矮列表拖选、日语(v1.89):终端里 notes.md 这类裸文件名也是链接;Powerline 的圆角端帽按单元格高度绘制;矮于 64 点的列表拖选时朝指针方向滚动;框架字符串新增日语(ja) |
可用(tests/terminal_test.php、tests/terminal_themes_test.php、tests/drag_select_test.php、tests/l10n_controls_test.php) |
代码编辑器:折叠 import、文字底色、按点滚动(v1.90):连续的 import 折叠成一块(Language::$imports、State::foldImports());State::$textBackgrounds 给文字区间加底色;scrollY()、setScrollY()、lineTop();滚动视图里的编辑器获得焦点时滚到光标处 |
可用(tests/code_editor_test.php) |
| 终端滚动条(v1.91):拖动滑块或点击轨道滚动;指针悬停时变宽并显示箭头,视口最后一次移动一秒后淡出,和 macOS 一样 | 可用(tests/terminal_test.php) |
音频:Player::position() 是实际听到的位置(v1.92):比混音落后输出缓冲的那一段,暂停后继续走完已缓冲的部分,播放或跳转后等声音真正放出来才前进,歌词、进度条和声音同步 |
可用(tests/audio_test.php) |
Now Playing 和媒体键(v1.93):NowPlaying::set() 在控制中心和菜单栏显示正在播放的内容,onCommand() 接收媒体键的命令,control() 让它们直接控制 Audio\Player;Player::onEnd() |
可用(tests/nowplaying_test.php) |
图片显示后释放像素(v1.94):Image::releasePixels() 释放解码图片及其缩小层级的像素,图集、层级或 shader 需要时再从保留的原始字节重新解码 |
可用(tests/image_test.php) |
内容规则(v1.95):ContentRules::compile() 编译 WebKit 内容拦截格式的规则,跨启动保留;PageOptions::$contentRules 和 Page::setContentRules() 用它拦截加载、隐藏元素,就像广告拦截器 |
可用(tests/content_rules_test.php;编译和拦截需手动验证) |
| 代码编辑器用粗体、斜体字形绘制对应 token(v1.96):Markdown 的标题、加粗、强调,以及主题里任何粗体或斜体的 token 样式;光标、点击、选区和换行都按字形分段计算 | 可用(tests/code_editor_test.php) |
毛玻璃清晰度(v1.97):new Glass(clarity: 0.5) 让侧栏大小的玻璃面板更通透,模糊和着色更少、边缘光更亮、弧边更宽;控件大小的面板保持 macOS 原样 |
可用(tests/glass_test.php;像素效果在 Mac 上看) |
终端(v1.98):Shift 点击从原锚点延伸选区,可跨行,拖出边缘时会滚动;Options::$scrollMultiplier 设置滚轮一格滚几行,并缩放触控板滚动 |
可用(tests/terminal_test.php) |
页面(v1.99):WindowOpenRequest::$userInitiated;PageOptions::$autoplay、$backgroundThrottling、$preloadAllFrames、$sessionState,配合 Page::sessionState() 保存前进后退历史;Page::processID() |
可用(tests/webview_test.php;WebKit 部分在 Mac 上验证) |
diff 条纹与 .icns 图标(v1.100):删除条的条纹跨行连续、随内容滚动;filler 只画可见区域的条纹;macos.icon 可以是按尺寸绘制的 .icns |
可用(tests/code_editor_diff_test.php、tests/build_test.php) |
页面(v1.101):Page::themeColor() 和 themeColorChanged 读取页面的 theme-color meta;isAudible()、setAudioMuted() 和 audibleChanged,像标签页的扬声器图标;captureState()、setCaptureState() 和 captureStateChanged 管理摄像头、麦克风和屏幕共享 |
可用(tests/webview_test.php、src/Native/theme-color.test.ts;WebKit 部分在 Mac 上验证) |
原生界面里的视频(v1.103):Context::video(VideoPlayer) 在原生界面里像图片一样逐帧播放视频,每帧到点才显示;GifDecoder 播放 GIF 动图,VideoDecoder 可接其他格式;看不见时暂停,减少动态效果时只显示第一帧 |
可用(tests/video_test.php;GIF 在 Mac 上验证) |
| 系统代理(v1.103):myphp 自己的请求(更新器、fetch、WebSocket、网络视频)走系统设置里的代理,含例外列表和 PAC;设置了代理环境变量时以环境变量为准;本机地址直连 | 可用(tests/netproxy_test.php;CFNetwork 在 Mac 上验证) |
HTTP 登录与证书错误(v1.104):Page::setAuthHandler() 回应页面和代理的 Basic、Digest、NTLM、Negotiate 认证;Page::setCertificateErrorHandler() 允许页面加载未通过校验的证书,比如开发服务器的自签证书 |
可用(tests/webview_test.php;WebKit 部分在 Mac 上验证) |
更新器预发布版本与 Sparkle 式更新窗口(v1.105):Updater::setPrereleases() 为选择 beta 的用户提供预发布版本(GitHub 上版本最高的非草稿 release),myphp build --upload 把带预发布后缀的版本建成 --prerelease;后台检查到更新时,窗口等 app 被激活后再显示,与 Sparkle 一致;App::isActive()、App::activationPolicy() |
可用(tests/updates_test.php、tests/updater_test.php、tests/build_test.php) |
低帧率绘制(v1.106):Painter::animationFrameRate() / Runtime::animationFrameRate() 让一个绘制每隔几次屏幕刷新才重画一次(120 Hz 屏上 60、40 或 30 帧/秒),ProMotion 屏也随之降低刷新率;有其他东西要求逐帧刷新时仍按屏幕刷新率 |
可用(tests/repaint_test.php;display link 的帧率在 Mac 上验证) |
静止子树缓存为位图(v1.107):Node::cache() 把节点及其子节点画进一张 GPU 位图,作为一张图片显示,只有画出的内容变了(内容、尺寸、缩放、主题、图片更新)才重新渲染;变换只移动位图;与 blur()、effect() 同用时缓存它们的图层 |
可用(tests/cache_test.php;像素在 Mac 上验证) |
App slug(v1.108):myphp.json 的 slug 决定更新包和差量包的文件名;默认由名字生成,中文转拼音(云音乐 → yun-yin-le),假名和韩文转罗马字(ramen、seoul) |
可用(tests/slug_test.php) |
窗口随页面内容定尺寸(v1.109):FlyoutOptions::$fitContent(受 $minWidth…$maxHeight 约束)和 WindowOptions::$fitContent 让 flyout 或窗口取页面内容的大小并跟随变化,像浏览器扩展的弹窗那样;由 bridge 在 dom-ready 之前测量根元素 |
可用(tests/fit_content_test.php、src/Native/fit-content.test.ts;调整大小在 Mac 上验证) |
| 代码编辑器新语言(v1.110):高亮 Dart、GraphQL、R、Scala,可按名称、扩展名、别名查找 | 可用(tests/syntax_test.php) |
自定义更新来源(v1.111):Updater::setSource() 接受 app 自己的 UpdateSource(自有服务、需凭据的接口、运行时选择的渠道)来提供 manifest 和它列出的文件,仍按构建时的公钥校验签名 |
可用(tests/updates_test.php) |
不受信任的页面(v1.112):PageOptions::$untrusted 用来显示别人的页面(插件界面、用户 HTML),不能调用 PHP 方法、收不到广播,除非 app 的处理器允许,否则没有权限、不能开窗口、不能下载、不能导航到别的源;ProtocolRequest::$page 告诉 scheme 处理器是哪个页面发的请求 |
可用(tests/webview_test.php;WebKit 部分在 Mac 上验证) |
页面视频画中画(v1.112):PageOptions::$pictureInPicture 允许页面视频进入画中画(控件按钮、右键菜单、requestPictureInPicture());Page::togglePictureInPicture()、isPictureInPictureActive() |
可用(tests/webview_test.php、src/Native/pip.test.ts;WebKit 部分在 Mac 上验证) |
会话(v1.112):PageOptions::$session 让页面使用某个 Session 的网站数据存储(Session::newSession('work') 持久保存,newEphemeralSession() 只在内存),各有自己的 $cookies 和 clearData(SiteData::…) |
可用(tests/session_test.php;WebKit 存储在 Mac 上验证) |
页面右键菜单(v1.112):Page::on('contextMenu', …) 得知右键点了什么,并修改、扩充或替换 WebKit 的菜单(ContextMenuEvent、Menu::remove()) |
可用(tests/context_menu_test.php;WebKit 菜单在 Mac 上验证) |
只重绘不重建的帧(v1.113):只有绘制在动时(绘制中调用的 animationFrame()、after(),指示器,内容过渡),帧只把上一帧的节点重画一遍,不重建视图、不重新布局;只有绘制回调请求时,只重画这些回调,其余沿用上一帧,与 mygo 一致 |
可用(tests/repaint_test.php) |
| 描边和阴影分条带绘制(v1.115):圆环、只有边框的方框和盒阴影只在沿圆角的条带里着色,不再整块着色;正放的形状只在裁剪区域内着色,与 mygo 一致 | 可用(tests/bands_test.php;像素在 Mac 上验证) |
自动化(v1.116,mygo 的 plugins/automation):设置 MYPHP_AUTOMATION 并 App::use(Automation::plugin()) 后,其他进程可经 Unix socket 用按行分隔的 JSON 驱动运行中的应用:列窗口、读原生 UI 的元素(带 Context::key 的 key)并对其执行操作、发送指针/滚轮/按键/文字输入、点应用菜单项、截图、在页面里 eval、等待窗口静止(docs/automation.md) |
可用(tests/automation_test.php;应用侧在 Mac 上验证) |
忽略输入的节点(v1.117):Node::ignoreInput() 让指针和键盘越过节点及其内部,节点照常显示、辅助功能仍可按下它,与 mygo 一致;终端自绘六分块字符(二维码);myphp build 在 macOS 27 上用 diskutil 制作磁盘映像 |
可用(tests/ignore_input_test.php、tests/terminal_test.php、tests/build_test.php) |
视频命名与 mygo 一致(v1.118):Context::video、UI\VideoPlayer、VideoDecoder、VideoFrame 即 mygo 的逐帧视频(原 Context::clip、ClipPlayer…);Media\Player 的影片用 Context::movie 显示,复刻 IINA 的播放器改名为 UI\MediaPlayer(Context::mediaPlayer、mediaPlayerSettings、mediaPlayerWelcome、mediaPlayerHistory) |
可用(tests/video_test.php、tests/mediaplayer_test.php) |
Mac 上的冒烟测试(v1.119):php -d ffi.enable=1 scripts/smoke-automation.php 通过自动化插件驱动 examples/automation,逐项报告并保存截图;网格子节点的自动外边距在所在区域内定位,与 mygo 一致 |
可用(tests/examples_test.php、tests/layout_test.php;冒烟测试在 Mac 上跑) |
冒烟测试发现的修复(v1.120):隐藏创建的原生 UI 窗口会画出第一帧并触发 readyToShow;Page::eval 执行语句(return …、throw …)不再让应用崩溃 |
可用(Mac 上的冒烟测试) |
局部换主题(v1.121):Context::themed($theme) 让一个列容器及从它里面打开的弹层用另一套主题构建和绘制,与 mygo 的 Themed 一致(如深色界面里的浅色预览) |
可用(tests/themed_test.php) |
与 mygo 对齐(v1.122):Markdown 图片按百分比和宽高比缩放,代码块在从右到左界面中仍从左到右;换行行支持 reverse() 和百分比宽度;ListState::visible()/atEnd()、GridState::scrollTo($item, $align);窗口失去键盘焦点时结束按住的按压;剪贴板的 TIFF 图片按 PNG 读取;VoiceOver 树中包含 web view 的页面;VoiceOver 可在富文本编辑器中选择文字 |
可用(tests/markdown_view_test.php、tests/flex_test.php、tests/lists_test.php、tests/click_count_test.php、tests/richtext_editor_test.php、tests/automation_test.php;剪贴板与 VoiceOver 在 Mac 上验证) |
与 mygo 对齐(v1.123):采用 mygo 的设置方法名(alignItems()、radius()、minWidth()/minHeight()、border($width, $color),旧名移除);draw()/drawOver() 回调接收画笔和节点矩形 (Painter $p, array $r);可选择文字中左右方向键按视觉字素移动 |
可用(tests/flex_test.php、tests/repaint_test.php、tests/textselection_test.php) |
与 mygo 对齐(v1.124):decimalInput,精确的十进制金额输入;消息框的 destructiveButtons,取消按钮为默认按钮时 Esc 与 Cmd+. 仍能取消;托盘图标的位置取用户点击的那个显示器上的副本;增量更新比较匹配最多 64 KiB,不再因长偏移匹配卡住 |
可用(tests/decimal_input_test.php、tests/dialog_test.php、tests/appkit_test.php、tests/updates_test.php、tests/code_editor_diff_review_test.php;对话框按键与托盘在 Mac 上验证) |
与 mygo 对齐(v1.125):numberInput 的 −/+ 按 mygo 的内边距绘制、到边界时禁用,输入框向辅助功能报告范围、当前值和步长 |
可用(tests/widgets_more_test.php) |
与 mygo 对齐(v1.126):代码编辑器的滚动条滑块只在指针位于编辑器上方时显示,滑块下不画轨道;WebViewOptions::$scrollToContainer,网页视图把滚轮和触控板滚动交给外围的原生界面;按钮的 padding() 决定其尺寸 |
可用(tests/code_editor_test.php、tests/widgets_more_test.php;网页视图的滚动在 Mac 上,tests/webview_test.php) |
| 代码编辑器的悬停提示和补全列表附着在所指文字上,出现的那一帧就在正确位置,不再先盖住该行再跳到上方,并像 VS Code 一样在指针停留 300 毫秒后弹出、用 100 毫秒淡入;代码编辑器示例默认打开 PHP 文件(v1.127) | 可用(tests/code_editor_parts_test.php) |
代码编辑器的 PHP 高亮:heredoc/nowdoc 到结束标签为止并突出其中的变量,#[ 属性不再被当作注释,<?php/?> 标签,-> 后的属性名,enum 和标量类型(v1.128) |
可用(tests/code_editor_php_test.php) |
设了 maxWidth() 的文字在该宽度处换行,不再越出容器;换行的行在同一帧按实际宽度折行并撑开高度,折行按子元素的 basis() 计算,外层滚动容器可滚动到全部内容;警告框和模态框无论在哪里构建都覆盖整个窗口并居中(v1.129) |
可用(tests/wrap_test.php、tests/flex_test.php、tests/feedback_test.php) |
| 可交互的玻璃在鼠标悬停时提亮;圆角最大取短边一半;在内容上按住鼠标时滚动条不再消失(v1.129) | 可用(tests/glass_test.php、tests/ui_more_test.php) |
| 与 mygo 对齐(v1.130):自适应高度的 diff 和左右分栏 diff 的左侧也显示横向滚动条,位于可见区域底部;gallery 在窄窗口下的聊天气泡宽度和按钮行折行 | 可用(tests/code_editor_diff_review_test.php) |
| gallery 在最窄窗口(640 点)下卡片改为每行一张,List 页的树和表格、Languages 页的地区行自动换行,Drawing 页的波形不再画出框外(v1.131) | 可用(examples 测试与 Mac 上查看) |
| gallery 与 mygo 逐像素对比:文字按 Core Graphics 的位置摆放,500/600 字重、SF Mono 与等宽数字;填充、边框、裁剪落在整像素上,边缘 1 像素抗锯齿;输入框、绝对定位子节点、自动外边距考虑边框与最大宽度;步进器、星级、范围滑块刻度、分段控件、颜色块与 mygo 一致;窗口不可见时不再画帧(v1.132) | 可用(无头测试与 Mac 上对比) |
连续圆角(与 macOS 一致),覆盖填充、边框、阴影和图片;圆角裁剪;Scrollbar 改为值对象(Painter::scrollbar),编辑器、富文本、终端的滚动条悬停时显示;网页视图接受首次点击;应用处于活动状态时才跟踪指针;gallery 的径向渐变条与 Overlays 按钮行与上游一致(v1.133) |
可用(无头测试与 Mac 上对比) |
与上游对齐(v1.134):点状下划线、输入框占位符颜色与光标宽度、可隐藏滚动条的滚动容器、Context::instant()、Markdown 锚点与链接字重、编辑菜单项仅在焦点可执行时启用、辅助功能可编辑自定义文本客户端、更新插件的 availableVersion()、消息框 cancelButton 与上游一致(0 按 Cancel/No 查找、找不到用第一个按钮,-1 表示没有);examples/wavelength 例子;Native\ThumbnailDecoder |
可用(无头测试) |
播放器的 IINA 菜单(v1.135):播放、视频、音频、字幕菜单,文件菜单中与影片相关的项,窗口菜单的日志查看器,快捷键取自播放器的按键绑定(UI\MediaPlayerMenus);MenuItem::$alternate、$onOpen 与 setSubmenu();字幕 > 编码会重新读取字幕文件 |
可用(无头测试) |
| 画面调节(v1.136):IINA 的视频均衡器(亮度、对比度、饱和度、伽玛、色调),用视频图层的 Core Image 滤镜实现;视频 > 翻转(水平镜像、垂直翻转);视频面板的 HDR 开关 | 可用(无头测试) |
IINA 检查器(v1.137):通用、轨道、文件、状态四个标签页(含属性监视表),显示最近一个在前台的播放器;音频 > 音频设备;播放器的类和 Context 方法改名为 MediaPlayer(MediaPlayerState、Context::mediaPlayer 等,原 MoviePlayer) |
可用(无头测试) |
加载动画插件(v1.138,对应 mygo 的 plugins/loading,Plugins\Loading):loading.dev 的 29 个加载指示器由 MyPHP 自绘(弧、环、点、条、方块),尺寸、颜色、速度、缓动、线端与方向可选;系统要求减少动态效果时静止,可暂停在当前位置 |
可用(tests/loading_test.php、examples/loading) |
| IINA 的另外两个在线字幕来源(v1.139):assrt.net(射手网(伪),按文件名搜索,多个结果时弹出 IINA 的选择列表,压缩包里的文件逐个下载;设置里可填自己的 API token,没填时先弹 IINA 的提示,再用 IINA 自带的试用 token)和 shooter.cn(按文件哈希搜索,找到的全部加载);字幕 > 从…查找在线字幕 可选任一来源,设置里的「字幕来源」决定默认搜索哪个 | 可用(tests/online_subtitles_test.php) |
| IINA 的视频滤镜、音频滤镜窗口(v1.140):当前滤镜可编辑、可保存并绑定快捷键(已保存的视频滤镜、已保存的音频滤镜菜单),IINA 的新建滤镜预设;不依赖 mpv 自行实现:视频用 Core Image 做裁剪、扩展、翻转、锐化与模糊、反色、去台标、3D LUT(.cube),音频做音量、均衡器、低音、高音、高通、低通、声道映射(pan);按键绑定的 vf、af 命令 | 可用(tests/filters_test.php) |
去隔行(v1.141):视频 > 去隔行,以及按键绑定的 deinterlace 属性;打开后视频改由 VideoToolbox 在本地解码,用 vImage 做行间混合后再交给显示层,换片后保持 |
可用(tests/deinterlace_test.php) |
| 框选模式(v1.142):视频 > 裁剪 > 自定义…,以及视频 > 去台标;在暂停的画面上拖出矩形(可拖边调整、可选 IINA 的比例),点完成后作为带标签的视频滤镜加上裁剪或去台标 | 可用(tests/interactive_test.php) |
播放器插件(PHP,v1.143):沿用 IINA 的插件目录、Info.json 和权限,入口返回一个接收 Iina 的函数(console、preferences、menu、utils、file、http、core、mpv、event、playlist、subtitle、global、定时器),插件菜单,设置 > 插件(本地包安装、开关、卸载、排序、关于),插件提供的在线字幕来源 |
可用(tests/plugins_test.php) |
插件界面(v1.144):画面上的 overlay(只有 data-clickable 元素接收点击)、插件面板里的侧栏标签、独立窗口、简单模式和双向消息;插件详情里的「设置」(偏好页)和「帮助」标签 |
可用(tests/plugins_test.php) |
插件系统收尾(v1.145):iina.input(键盘和鼠标,优先级)、iina.ws(WebSocket 服务器)、播放列表右键菜单(含插件菜单项)、从 GitHub 安装、检查更新和更新、插件商店、开发者工具(PHP 控制台)、打开影片时的 mpv 钩子;官方插件改写成 PHP(plugins/:Online Media、User Scripts、OpenSubtitles) |
可用(tests/plugins_test.php、tests/official_plugins_test.php) |
| 对照 mygo 逐个运行例子后修正(v1.26):彩色 emoji、带着色的图片、文本区按段落折行(10 万行文档即时打开)、文字内边距、无背景的边框、滑块、禁用控件变暗、本地时区 | 可用(examples/farm、examples/text-buffer、examples/text-input) |
| App / Window API:mygo 的窗口方法(显示、焦点、最小化、最大化、全屏、位置与尺寸、尺寸限制、窗口按钮、置顶、透明度、阴影、关联文件、已编辑标记、Dock 进度、请求注意、所有桌面、子窗口与 sheet)和事件 | 可用,含 vibrancy、透明窗口和背景色、面板、flyout(锚定到矩形或托盘图标,空间不够时翻转、滑回屏幕内,点外部或 Esc 关闭),也可以用系统 popover 样式 |
路由(mygo 的 Router):带路径和查询的页面历史、{name} 与 {name...} 模式、嵌套布局、页面保留状态和焦点、滑入淡入、前进后退按钮、Cmd+[ 与 Cmd+]、鼠标侧键、指向路径的链接 |
可用(examples/router) |
| 可拖动节点(mygo 的 Draggable):按父节点坐标跟随指针、按松手速度甩到吸附点、边界橡皮筋或硬边界、移动中可接住、配合 pose 抬起;窗口内拖拽值到接收该类型的节点,指针下有半透明副本,靠近边缘自动滚动,Esc 取消 | 可用(examples/drag) |
系统拖放(mygo 的 data_drag):把数据以多种格式拖到其他应用,格式按需生成;从其他应用拖入文本、链接、Markdown 等格式,悬停时只看格式不读内容,放下才读取;复制/移动协商;Finder 文件先给节点,再给窗口的 fileDrop;应用内拖放直接拿到原值 |
可用(examples/transfer) |
| Markdown 解析器(mygo 的 ui/markdown/parser):CommonMark 0.31.2 和 GitHub 扩展(规范示例全部通过)、脚注、公式;追加或编辑的文本只重新解析改动处;流式回复末尾写到一半的语法自动补全 | 可用 |
| Markdown 视图与流式视图(mygo 的 ui/markdown):标题、带链接和脚注引用的段落、列表和任务列表、引用、带复制按钮的高亮代码块、表格、图片,公式和 HTML 按文本显示或交给钩子;可选中,复制为文本、HTML 和 Markdown,Shift+Cmd+C 复制为 Markdown;正在生成的回复匀速显示、渐入 | 可用 |
富文本(mygo 的 ui/richtext):带属性的文本段、带样式的段落(标题、列表、任务列表、引用、代码块、对齐、链接、标签、附件),持久化数据结构;Document 管选区、输入属性、撤销重做和格式命令;Markdown、HTML、RTF、JSON 双向转换;编辑器(RichText::editor):文字处理软件的快捷键、边打边转的 Markdown 快捷写法、列表和任务、格式随文本一起撤销、输入法、富文本复制粘贴 |
可用(examples/richtext) |
| 终端(mygo 的 plugins/terminal):在伪终端里运行用户的 shell 或任意程序,由 Ghostty 的 libghostty-vt 模拟(mygo 编好的库,经 FFI 加载,首次运行下载到缓存);颜色、Unicode、回滚缓冲、全屏程序、Kitty 键盘协议、鼠标、括号粘贴、超链接、标题、OSC 52、程序自己读取的粘贴;像 Ghostty 一样选择文本、快捷键绑定、Ghostty 的主题、Command+点击打开链接 | 可用(examples/terminal) |
自动更新(mygo 的 Updater):myphp keygen 生成密钥,myphp build 给应用的归档签名(Ed25519),从已发布的版本做增量更新(bsdiff)并写出清单,发布到 GitHub Releases 或自己的服务器;应用里 Updater::check()、Update::install() 替换整个 bundle,App::relaunch() 重启;App::go() 在 Fiber 里跑会等待的工作 |
可用 |
updater(mygo 的 plugins/updater):Sparkle 式的更新窗口:每天后台检查一次,新版本显示发布说明并提供「安装更新」「以后提醒我」「跳过此版本」,下载进度,「立即重新启动」;菜单项「检查更新…」(Updater::menuItem()),用户选择存在 updater.json,15 种语言;窗口可以是网页,也可以用原生 UI 绘制(Native::plugin()) |
可用(examples/updater) |
game(mygo 的 plugins/game):Game::view() 以固定步长(每秒 60 步,或游戏自己的 rate())运行游戏,按键位读取按键,另有指针和滚轮,每帧经 Painter 绘制;Camera(缩放、旋转、按整像素平移、toWorld、visible),像素画用 Game::integerFit();Gamepad\Gamepad::pads() 经 GameController.framework 按标准布局读取手柄 |
可用(examples/game) |
UI\Canvas(mygo 的):画笔在 GPU 上绘制的位图,重画之前一直保留,像图片一样显示(像素画可 pixelated);UI\Chart(mygo 的 ui/chart):按 d3 取刻度的 Linear、Band、Time 比例尺,带坐标轴的 Chart::plot,本身就是节点的标记(rect、point、ruleX、ruleY、anchor),折线、面积和扇形路径,堆叠,指针和键盘都能移动的选择 |
可用(examples/charts、examples/game) |
UI\Direction(mygo 的):从右到左书写语言的窗口或单独一棵子树(Node::direction),逻辑边(paddingStart、marginEnd、borderStartWidth…),行、列、网格和横向滚动镜像,文字从右起,滑块、评分、树、日历等的方向键顺着行走;文本框尚未支持 |
可用(MYPHP_DIRECTION=rtl) |
UI\Inspector(mygo 的):原生 UI 的开发者工具,停靠在内容旁边:节点树(标记形式)、所选或在窗口中拾取的节点的样式、盒模型和属性并在内容上高亮、对照显示器刷新率的帧耗时、重复 key 等问题、从右到左开关;F12、Alt+Cmd+I、菜单角色 toggleDevTools;PageOptions::$devTools(DevTools)也打开网页的 Web 检查器;见 docs/inspector.md |
可用 |
Audio\Audio(mygo 的 audio 包):解码进内存、可叠加播放的音效,提前一秒解码的流,应用自己写的 Decoder(合成器);播放器支持音量、声像、速度、无缝循环和循环区间、跳转、5 ms 渐变;在 PHP 里混音后送进 Audio Queue;WAV 和 Ogg Vorbis 用 PHP 解码,MP3、AAC、ALAC、FLAC、AIFF、CAF 用系统解码器;见 docs/audio.md |
可用(examples/piano) |
插件(mygo 的 Use),PHP 插件:App::use(Plugin) 把插件的服务绑定为 plugin:<名字>,网页侧是插件自带的 TypeScript 客户端;CallContext 参数告诉方法是哪个页面调用的,页面离开时结束;App::onQuit |
可用 |
文件监视(mygo 的 WatchFiles):App::watchFiles 监视文件或目录,可递归,Ignore 排除的目录从不打开;改动停下后成批给出 create、write、remove、rename,编辑器式保存算一次 write,根目录没了监视即结束;经 FFI 调用 FSEvents;hintsOnly 只报告发生变化的目录 |
可用 |
watch(mygo 的 plugins/watch):网页按名字监视应用开放的目录和文件(src/Plugins/Watch/client/index.ts,@myphp-plugins/watch),只在应用允许处递归,Resolve 与 Allow,每页和总数配额,失败只给代码、从不暴露路径 |
可用(examples/watch) |
WebSocket(mygo 的 plugins/websocket):网页用浏览器同款 API 从 PHP 建 WebSocket 连接(src/Plugins/WebSocket/client/index.ts,@myphp-plugins/websocket):握手可带任意请求头(Authorization、Cookie、Origin),子协议,文本和二进制消息按序发送,bufferedAmount,双方都可关闭,连接随页面结束;用系统自带的 libcurl,经 FFI |
可用(examples/websocket) |
fetch(mygo 的 plugins/fetch):网页用标准的 fetch(src/Plugins/Fetch/client/index.ts)从 PHP 发 HTTP 请求:没有 CORS 限制,任意请求头(Origin、Cookie、Host…),响应体边收边流给页面,abort 即停止请求;用系统自带的 libcurl,经 FFI |
可用 |
SQLite(mygo 的 plugins/sqlite):mygo 编好的 SQLite 3.53.4 和它的 C shim,经 FFI 调用;Sqlite::open,每次一条语句、执行前整体校验,参数绑定,RETURNING,出错整体回滚的事务,只读连接,忙等超时,FTS5、R*Tree、JSON;网页用 App::use(Sqlite::plugin()) 和 src/Plugins/Sqlite/client/index.ts(@myphp-plugins/sqlite),连接归调用它的页面 |
可用 |
网页:Ipc::bind() 绑定 PHP 服务,页面直接调用,事件回推,流式 channel,页面侧窗口控制,preload 脚本,生成 TypeScript client |
可用(examples/webview) |
| 菜单栏(角色、快捷键)、右键菜单、打开/保存/消息对话框 | 可用(examples/desktop) |
App 服务(mygo 的):深链接(App::onOpenURL、registerURLScheme、myphp.json 的 urlSchemes)与用本应用打开的文件(onOpenFile、fileAssociations)、单实例(requestSingleInstanceLock、onSecondInstance)、最近文档、登录时启动、Network(是否联网、是否按流量计费)、Clipboard(多格式数据、文件、flush)、Secrets(钥匙串)、Cookies;网页:页内查找、打印、打印为 PDF、权限(摄像头、麦克风)、下载 |
可用(examples/services) |
文档型应用(mygo 的):Documents、Document——每个文件一个窗口、文件菜单的角色(new、open、openRecent、save、saveAs、duplicate、rename、moveTo、revert)、原地自动保存、崩溃后的恢复副本、关闭和退出前询问、被其他应用改动的文件、给网页用的 DocumentService;见 docs/documents.md |
可用(examples/documents) |
PHP 服务的自定义 scheme(mygo 的 Protocol):Protocol::handle、ProtocolRequest/ProtocolResponse(可流式输出)、Protocol::fileServer;前端在 myphp://localhost/(App::setFrontend,myphp.json 的 devUrl、devCommand、buildCommand、frontendDist);只有受信任的页面能调用 PHP(PageOptions::$trustedOrigins) |
可用(examples/protocol) |
| 托盘图标、全局快捷键、通知(需打包的 .app)、窗口事件、文件拖放、记住窗口位置 | 可用(examples/desktop) |
不阻塞界面的子进程(Process):curl、脚本、命令行工具 |
可用(examples/http) |
SVG:图标按蒙版任意着色(rotate 可旋转),图片按自身颜色绘制——路径、基本图形、变换、渐变、描边与虚线、不透明度、裁剪、遮罩、样式表——由纯 PHP 渲染器绘制,路径蒙版也由它栅格化(不再用 CoreGraphics) |
可用(examples/widgets、files) |
语言与地区格式(mygo 的 i18n,v1.10):应用语言随用户设置(或 App::setLocale,记在应用 defaults 里);按 locale 写日期、数字、月份、星期和上下午(I18n、DateFormatter、NumberFormatter,通过 ext-intl 用 ICU);CLDR 的父 locale 和复数类别;ICU 消息(Message:plural、selectordinal、select、offset、数字和日期样式);应用自己的目录(Catalog,myphp.json 的 i18n),myphp generate 生成 PHP 类和 TypeScript 模块,myphp i18n check,myphp dev --lang,伪 locale en-XA 和 ar-XB,bundle 的本地化目录;MyPHP 自带文案和菜单(en、zh-Hans、zh-Hant)可用 myphp.<key> 改写 |
可用(examples/widgets) |
| 文件管理器:常用位置侧栏、路径面包屑、目录以图标或可排序表格显示且共用一个选择、查找条、图片预览、经 Finder 移到废纸篓 | 可用(examples/files) |
无障碍:VoiceOver 可朗读并操作所有控件、代码编辑器和 diff 查看器(mygo 的角色、名称、状态与动作树,基于 NSAccessibilityElement) |
新加,待 VoiceOver 实测 |
| 节点及其子节点的变换(平移、缩放、旋转、斜切,以及 3D 的 rotateX/rotateY/perspective、背面隐藏)和透明度,由 GPU 按每个 quad 的映射绘制,命中测试走逆映射(mygo 的 transform.go) | 新加 |
| 姿态(在变换之后平移、缩放、旋转、淡出节点)和内容过渡(文字像计数器数字那样滚动或淡入淡出、图标替换、图片交叉淡化)(mygo 的 pose.go 和 content.go) | 新加 |
| 过渡:节点从上一帧的位置和尺寸移动、缩放到新布局,进场和离场(平移、淡入淡出、折叠、缩放、旋转),颜色渐变,匹配节点从对应节点原来的位置过来(mygo 的 transition.go) | 新加 |
| 动画:按节点和键的曲线、弹簧、延迟、跳变、颜色、关键帧和循环(mygo 的 motion),沿同一播放头编排补间的时间线(可暂停、拖动定位、倒放、重复),遵循「减弱动态效果」;开关、滑块、折叠区、手风琴和侧边栏分组按 mygo 的方式动起来 | 新加 |
| Linux 和 Windows | 下一步 |
| Linux(GTK/WebKitGTK)、Windows(Win32/WebView2) | 计划中 |
在 M 系列 Mac 上用 examples/inbox 1000 实测:每帧重建、布局并绘制 9 000 个节点(含真实文字)约 6 ms,滚动时 60 fps;没有变化时不构建帧。
命令行
bin/myphp init my-app # 原生 UI 项目(-template web 是调用 PHP 的网页项目) cd my-app && ../myphp/bin/myphp dev # 运行,文件改动自动重启,重新生成 TS client ../myphp/bin/myphp build # build/darwin-arm64/my-app.app 和它的 .dmg,启动器 exec 本机的 php;插件要的原生库(终端的 libghostty-vt)放进 Resources ../myphp/bin/myphp build -launcher micro # 单个静态二进制:第一次自动下载 static-php-cli 并构建 phpmicro
环境要求
- PHP ≥ 8.3,带
ext-ffi和ext-intl(Homebrew 的php都自带),建议开 OPcache JIT - 目前仅支持 Apple Silicon 的 macOS
一个网页窗口,前端直接调用 PHP:
/** Greeter says hello. */ final class Greeter { public function greet(string $name): string { return "Hello, $name"; } } Ipc::bind(new Greeter()); App::whenReady(fn() => new Window(new WindowOptions(title: 'Hello', html: '<script> myphp.call("Greeter.greet", "Ada").then(s => document.body.textContent = s)</script>'))); App::run(); // MYPHP_GENERATE=src/myphp.ts php app.php 则只生成带类型的 client
运行
./run.sh webview # Notes:页面调用绑定的 PHP 服务,带事件 ./run.sh gen # 生成 examples/webview/myphp.ts(TypeScript client) ./run.sh desktop # 菜单和对话框 ./run.sh form # 文本框、输入法 ./run.sh logview /var/log/system.log # 日志查看器:跟随文件增长、过滤、高亮、Cmd+F 查找 ./run.sh titlebar [hidden|hiddenInset] [x,y] # 隐藏标题栏:视图在红绿灯下自己画标题栏(x,y 可挪红绿灯),拖它移动窗口 ./run.sh files [目录] # 文件管理器:常用位置、路径面包屑、图标或可排序表格、Cmd+F 查找、空格预览、Delete… 移到废纸篓 ./run.sh drag # 可拖动:卡片、甩到吸附点的圆块、底部弹出面板;把词拖进篮子 ./run.sh transfer # 系统拖放:把便签拖到其他应用;拖入文本、链接和文件;卡片在两列间拖动 ./run.sh router # 路由:侧栏、前进后退、链接、笔记滑入、设置页布局 ./run.sh flyout # flyout:按钮下的颜色列表、按钮旁的便签编辑框、系统 popover 里的 About 框 php tests/run.php # UI 的无窗口测试:布局、按键、焦点、widgets 和 files 例子 php tests/bench.php # 无窗口压测:一帧的 build / layout / record / paint 耗时 ./run.sh markdown-notes # Markdown 笔记:搜索、工具栏、可拖分栏、实时预览、自动保存 ./run.sh notes # mygo 的 notes:富文本笔记、格式工具栏、格式菜单、链接、复制为 Markdown ./run.sh native # mygo 的 native:网页调用 PHP,演示对话框、剪贴板、通知、右键菜单、托盘、全局快捷键、深色模式 ./run.sh drag-drop # mygo 的 drag-drop:笔记、URL 和文件在两个窗口及其他应用之间拖放 ./run.sh richtext # 富文本编辑器,带格式工具栏 ./run.sh terminal # 终端:你的 shell,由 Ghostty 的 libghostty-vt 模拟 ./run.sh sqlite # 网页经 PHP 插件用 SQLite;点 Reload 可以看到连接随页面结束 ./run.sh watch [folder] # 网页经 PHP 插件监视一个文件夹;按钮会改动其中的文件 ./run.sh websocket [url] # 网页经 PHP 插件连 WebSocket,默认连一个回显服务器 ./run.sh updater --demo # 更新窗口,用一个虚构的更新演示安装流程(--native:原生 UI 版) ./run.sh game # 一个小游戏:WASD、方向键或手柄移动,滚轮缩放 ./run.sh charts # 图表:柱状图、实时折线、堆叠面积、散点图、环形图 ./run.sh piano # 合成音色的钢琴:鼠标、键盘、延音踏板、自动演奏曲子 ./run.sh services # 从网页调用 App 服务:网络、剪贴板、钥匙串、Cookie、页内查找、PDF… ./run.sh documents [x.md] # 文档型笔记:每个文件一个窗口、文件菜单、自动保存、崩溃恢复 ./run.sh protocol # myphp://localhost/ 上的前端、自定义 scheme 画的图、流式响应、下载 ./run.sh photos ~/Pictures # 照片浏览:缩略图网格、单张全尺寸、方向键翻页 ./run.sh http # HTTP 客户端(curl 子进程):请求列表、JSON 着色 ./run.sh glass # 照片上的 Liquid Glass ./run.sh widgets # 小控件合集 ./run.sh inbox # 原生 UI,300 行列表,每秒打印一次 fps ./run.sh inbox 1000 # 每帧 9 000 个节点 ./run.sh hello # 移植自 mygo 的示例:页面通过绑定的服务打招呼 ./run.sh counter-native # 原生 UI 计数器:按钮和方向键 ./run.sh wavelength # 循环播放的 GIF 作窗口背景,按钮暂停和继续 ./run.sh text-buffer 100000 # 基于 TextBuffer 的十万行文本区 ./run.sh text-input # 应用自己绘制的文本框,经 TextInputClient 接输入法 ./run.sh frameless # 无边框透明窗口加 vibrancy,页面拖动窗口 ./run.sh clipboard # 多种格式的剪贴板(其一延迟生成);数据拖出与拖入 ./run.sh timeline # tween 时间轴:先后、定时、按标签排布 ./run.sh markdown-chat # 回复按 token 流式到达,以 Markdown 逐词显现 ./run.sh embed-webview # 原生 UI 里的 web view,其上叠原生 popover、下拉和模态框 ./run.sh menubar-flyout # 托盘图标的 flyout,放在系统 popover 里 ./run.sh menubar-native # 菜单栏应用:托盘图标下的窗口,失去焦点即隐藏 ./run.sh launcher # 启动器:全局快捷键,边输边排序的已装应用及其图标 ./run.sh vibrancy # vibrancy 窗口:材质侧栏滑出收起 ./run.sh todo # Vite 页面里的待办:带类型的 PHP 服务与事件(需要 bun) ./run.sh confetti # canvas-confetti 的演示,每片纸屑由只重绘的 pose 驱动 ./run.sh effort-slider # 力度滑杆,越过 Max 进入 Galaxy,字母变化时模糊 ./run.sh farm # 种田小游戏:翻地、播种、浇水、收获,昼夜与音乐 ./run.sh mayoi # 幸存者小游戏:对舞台恐惧唱歌、收集石子、找齐乐队 ./run.sh gallery # 工具包总览:所有控件、列表、图表、动效、语言 ./run.sh loading # loading.dev 的 29 个加载指示器,可调尺寸、颜色、缓动、线端
文档
先看入门,再看原生 UI:每个控件、布局、输入、样式、动效和绘制,
示例都能运行(tests/docs_examples_test.php 会构建它们)。应用侧:应用、窗口、
菜单、桌面 API、绑定、前端、
剪贴板与拖拽数据、多语言、命令行、配置、
构建与分发。插件:fetch、SQLite、
watch、WebSocket、updater、
loading、glass、game、
terminal、automation。
目录
bin/myphp 命令行:init、dev、build、micro
src/ App、Window、WindowOptions、Menu、Dialog、Ipc、TypeScript
src/Native/ 操作系统桥接:ObjC 运行时、Metal、CoreText、事件视图
src/UI/ 工具包:Context(建树)、Layout(flex)、Text、Runtime(输入)、Painter
examples/ 可运行的示例
docs/ 使用指南(从 docs/getting-started.md 开始;原生 UI 见 docs/ui/README.md)和设计笔记
AGENTS.md 编码代理与贡献者的入口
许可
MIT。