//! 缩放层(M256):把一棵子树整体缩放(模态卡片进出场用)。 //! //! ## 为什么自己写一个 //! //! iced 0.14 的控件清单里**没有**通用的 transform / opacity 控件(`iced_widget` 只有 `stack` / //! `opaque` / `float` / `pin`)。但渲染器层是有的:`Renderer::with_transformation` 在**两个后端 //! 都实现了**——`iced_tiny_skia-0.14.1/src/lib.rs:211-217` 与 `iced_wgpu-0.14.0/src/lib.rs:682-688` //! 各是一行 `layers.push_transformation(...)`。而且 tiny_skia 对文字也乘了 `transformation`: //! `layer.rs:73-96` 把字号、行高、bounds 都按 `scale_factor()` 缩过 ⇒ **文字是真缩放**, //! 不是"只缩背景、字不动"。 //! //! ## 静息态**逐像素不变** //! //! `scale == 1.0` 时直接 `content.draw(..)`,**不套**变换:出图基线(D30)不受这层影响, //! 也不给渲染器多压一层矩阵。 //! //! ## 已知局限(登记在报告里) //! //! 命中测试用的是**布局矩形**(未缩放),所以缩放途中的点击落点仍按 1.0 的框算。 //! 模态只在开/关那 150~200 ms 里缩放,而那两段本来就不该被点击(`app/update/dialog.rs` //! 在退场期间直接吞掉弹窗消息),所以这件事没有可观察的后果。 use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer; use iced::advanced::widget::{tree, Tree}; use iced::advanced::{mouse, Clipboard, Shell, Widget}; use iced::{Element, Event, Length, Rectangle, Size, Transformation, Vector}; /// 把 `content` 缩放 `scale` 倍(**绕自身中心**),布局尺寸不变。 pub fn scaled<'a, Message, Theme, Renderer>( content: impl Into>, scale: f32, ) -> Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: iced::advanced::Renderer + 'a, { Element::new(Scaled { content: content.into(), scale, }) } /// 绕**中心**缩放的变换:`T(-c) · S · T(c)`(`Mul` 的乘法序 = 先右后左,见 iced_core 的 /// `impl Mul for Transformation`)。 fn about_center(bounds: Rectangle, scale: f32) -> Transformation { let (cx, cy) = ( bounds.x + bounds.width / 2.0, bounds.y + bounds.height / 2.0, ); Transformation::translate(-cx, -cy) * Transformation::scale(scale) * Transformation::translate(cx, cy) } struct Scaled<'a, Message, Theme, Renderer> { content: Element<'a, Message, Theme, Renderer>, scale: f32, } impl Scaled<'_, Message, Theme, Renderer> { /// 这条变换要不要真的套(`1.0` 时什么都不做)。 fn transformation(&self, bounds: Rectangle) -> Option { // 0.1% 以下视为到位:亚像素缩放只会让文字发虚,没有观感收益。 if (self.scale - 1.0).abs() < 1.0e-3 { return None; } Some(about_center(bounds, self.scale.max(0.01))) } } impl Widget for Scaled<'_, Message, Theme, Renderer> where Renderer: iced::advanced::Renderer, { fn tag(&self) -> tree::Tag { self.content.as_widget().tag() } fn state(&self) -> tree::State { self.content.as_widget().state() } fn children(&self) -> Vec { self.content.as_widget().children() } fn diff(&self, tree: &mut Tree) { self.content.as_widget().diff(tree); } fn size(&self) -> Size { self.content.as_widget().size() } fn size_hint(&self) -> Size { self.content.as_widget().size_hint() } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.content.as_widget_mut().layout(tree, renderer, limits) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { // 先算变换(借 layout 的 bounds),再借用 content —— 两个借用互不重叠。 let transformation = self.transformation(layout.bounds()); let content = self.content.as_widget(); match transformation { None => content.draw(tree, renderer, theme, style, layout, cursor, viewport), Some(transformation) => renderer.with_transformation(transformation, |renderer| { content.draw(tree, renderer, theme, style, layout, cursor, viewport); }), } } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn iced::advanced::widget::Operation, ) { self.content .as_widget_mut() .operate(tree, layout, renderer, operation); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.content.as_widget_mut().update( tree, event, layout, cursor, renderer, clipboard, shell, viewport, ); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.content .as_widget() .mouse_interaction(tree, layout, cursor, viewport, renderer) } fn overlay<'a>( &'a mut self, tree: &'a mut Tree, layout: Layout<'a>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { self.content .as_widget_mut() .overlay(tree, layout, renderer, viewport, translation) } }