| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
text: Fade streamed text in with `TextView::stream_fade` (#3082) ## Summary A chat reply streams into a `TextView` chunk by chunk, and today each chunk pops onto the screen at full color. This fades it in instead, the way claude.ai reveals a reply: the text an update appends starts transparent and reaches full color over 350 ms, so consecutive chunks overlap into one gradient tail rather than the newest chunk blinking in. Nothing animates unless the view opts in: ```rust TextView::new(&self.reply).stream_fade(true) // Other timing, or a word-by-word reveal TextView::new(&self.reply).motion( TextViewMotion::default() .with_stream_fade(Duration::from_millis(350)) .with_stream_fade_stagger(Duration::from_millis(30)) .with_stream_fade_easing(Easing::EaseOut), ) ``` - **Base** owns the mechanism and ships no timing, the way `ScrollbarMotion` does. `TextViewMotion` (`stream_fade`, `stream_fade_stagger`, `stream_fade_easing`) defaults to zero durations, so an unstyled view adopts streamed text at once. `TextViewState` notes whether each update extends or replaces the text (`push_str`, and `set_text` whose text starts with the current one, extend), and when a parse lands, `text/stream_fade.rs` compares the rendered text of the blocks the update reached against the previous document, block by block, and records the diverging tail as fading segments. Rendering samples them once per frame through `motion::Timing`, hands the `fade_out` factors down `NodeContext`, and `Paragraph`, `CodeBlock` and table cells merge them into their inline highlights (`fade_highlights`, which fades an inline code background along with its text). Frames are requested only while something is still fading. - Comparing rendered text rather than source bytes means Markdown that completes as it streams (`**bo` becoming bold `bold`) re-fades only the glyphs that changed, and a stateless `markdown(text)` that is re-rendered with a longer string fades like `push_str` would. - **GPUI Component** projects the timing: `TextView::stream_fade(true)` installs 350 ms with `Easing::EaseOut`, and `TextView::motion(..)` passes a policy through. `stream_fade(false)` turns a fade off that an earlier frame turned on. - **Example**: `cargo run -p example-stream-markdown` now fades by default, with a switch to compare. - Docs: component and base TextView pages in `en` and `zh-CN`; `docs/STYLING-AND-MOTION.md` records why the timing is a component constant rather than a `MotionTokens` tier. https://github.com/user-attachments/assets/492a1fdd-5242-4d5a-96ab-6609d2e34296 The timing comes from a screen recording of claude.ai streaming a Chinese reply, sampled frame by frame: | measured on claude.ai | value | here | |---|---|---| | fade unit | the whole chunk, no stagger inside it | same | | fade duration | 300–400 ms to full color | 350 ms | | curve | near-linear, easing at the end | `Easing::EaseOut` | | chunk cadence | 170–330 ms, 3–11 CJK characters | model-dependent | | blur / translate | none | none | Performance: with the fade off there is one `Option` check per render and nothing else. With it on, an update compares only the blocks it reached, walking both documents from the tail (one paragraph for a typical chunk) with slice comparisons and no string concatenation; the shaping cache key does not include color, so the fade never re-shapes text. ## Test Plan - `cargo test -p gpui-base --lib` (935 passed): `push_str` and prefix `set_text` fade only the appended range; a replacing `set_text` shows at once; `**bo` completing into `bold` re-fades from the divergence; new paragraphs, fenced code and table cells fade by their own key; zero duration and reduced motion record nothing; word splitting keeps trailing whitespace with its word and splits CJK by character; a stagger is compressed into one fade; `fade_highlights` fades text and background only inside the range. - `cargo test -p gpui-component --lib text::`, `cargo clippy --all-targets -- --deny warnings`, `cargo fmt --check`, `typos` all pass. - Verified on macOS with `cargo run -p example-stream-markdown`: the tail of the streamed text shows a gradient across the last several chunks while earlier text stays at full color; switching the fade off shows new text at full color at once; switching it back on resumes the fade. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 20 天前 | |
kit: Add optional gpui-fast backend (#3375) Add an optional `gpui-fast` feature across GPUI Kit’s existing crates: ```toml gpui-kit = { version = "0.7", features = ["gpui-fast"] } ``` No new crates. Preserve the existing `gpui-pre-*` workspace dependencies and default backend; optional Fast dependencies and internal conditional aliases select the engine. Existing application imports and macros continue to work. This is additive, with no breaking changes. Upstream packages still compile because Cargo features are additive. ## Public API - **gpui-kit:** `gpui-fast` selects Fast for core, platforms, and enabled layers. - **gpui-base, gpui-component, gpui-kit-assets, gpui-fps, gpui-wry, gpui-shell, gpui-component-shell:** `gpui-fast` selects the matching backend for standalone consumers. - **gpui-component-macros:** `#[derive(IntoPlot)]` additionally recognizes direct Fast dependencies. - Gallery and examples expose the same feature for comparisons. ## Validation 27 focused UI/macro/assets tests and 9 documentation tests pass. Default/Fast configurations, examples, WebView, Shell, benchmarks, and nightly WebAssembly compile. Clippy, formatting, dependency, and snapshot-pin checks pass. CI adds native and WebAssembly coverage. AI-assisted with Codex. | 15 小时前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 20 天前 | ||
| 15 小时前 |