Skip to main content

AnimationBlendBinding

Struct AnimationBlendBinding 

Source
pub struct AnimationBlendBinding {
    pub input: String,
    pub from: String,
    pub to: String,
    pub min: f32,
    pub max: f32,
}

Fields§

§input: String§from: String§to: String§min: f32§max: f32

Implementations§

Source§

impl AnimationBlendBinding

Source

pub fn new( input: impl Into<String>, from: impl Into<String>, to: impl Into<String>, ) -> Self

Examples found in repository?
examples/showcase.rs (line 8006)
7986fn animation_blend_machine(
7987    input: &'static str,
7988    value: f32,
7989    translate: UiPoint,
7990    start_scale: f32,
7991    end_scale: f32,
7992    end_opacity: f32,
7993) -> AnimationMachine {
7994    let start_values = AnimatedValues::new(0.45, UiPoint::new(0.0, 0.0), start_scale);
7995    let end_values = AnimatedValues::new(end_opacity, translate, end_scale).with_morph(1.0);
7996    AnimationMachine::new(
7997        vec![
7998            AnimationState::new("start", start_values),
7999            AnimationState::new("end", end_values),
8000        ],
8001        Vec::new(),
8002        "start",
8003    )
8004    .unwrap_or_else(|_| AnimationMachine::single_state("start", start_values))
8005    .with_number_input(input, value)
8006    .with_blend_binding(AnimationBlendBinding::new(input, "start", "end"))
8007}
8008
8009fn animation_open_machine(open: bool) -> AnimationMachine {
8010    let closed_values = AnimatedValues::new(0.35, UiPoint::new(0.0, 0.0), 1.0);
8011    let open_values = AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0);
8012    let fallback_values = if open { open_values } else { closed_values };
8013    AnimationMachine::new(
8014        vec![
8015            AnimationState::new("closed", closed_values),
8016            AnimationState::new("open", open_values),
8017        ],
8018        vec![
8019            AnimationTransition::when(
8020                "closed",
8021                "open",
8022                AnimationCondition::bool(ANIMATION_INPUT_OPEN, true),
8023                0.18,
8024            ),
8025            AnimationTransition::when(
8026                "open",
8027                "closed",
8028                AnimationCondition::bool(ANIMATION_INPUT_OPEN, false),
8029                0.14,
8030            ),
8031        ],
8032        "closed",
8033    )
8034    .unwrap_or_else(|_| AnimationMachine::single_state("closed", fallback_values))
8035    .with_bool_input(ANIMATION_INPUT_OPEN, open)
8036}
8037
8038fn animation_interaction_machine() -> AnimationMachine {
8039    let rest_values = AnimatedValues::new(0.72, UiPoint::new(0.0, 0.0), 1.0);
8040    let right_values = AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0).with_morph(1.0);
8041    AnimationMachine::new(
8042        vec![
8043            AnimationState::new("rest", rest_values),
8044            AnimationState::new("right", right_values),
8045        ],
8046        Vec::new(),
8047        "rest",
8048    )
8049    .unwrap_or_else(|_| AnimationMachine::single_state("rest", rest_values))
8050    .with_number_input(ANIMATION_INPUT_POINTER_NORM_X, 0.0)
8051    .with_blend_binding(AnimationBlendBinding::new(
8052        ANIMATION_INPUT_POINTER_NORM_X,
8053        "rest",
8054        "right",
8055    ))
8056}
8057
8058fn animation_interaction_primitives(
8059    fill: ColorRgba,
8060    size: f32,
8061    offset: UiPoint,
8062) -> Vec<ScenePrimitive> {
8063    vec![
8064        ScenePrimitive::MorphPolygon {
8065            from_points: animation_square_points(size, offset),
8066            to_points: animation_pentagon_points(size, offset),
8067            amount: 0.0,
8068            fill,
8069            stroke: Some(StrokeStyle::new(color(236, 244, 255), 1.0)),
8070        },
8071        ScenePrimitive::Circle {
8072            center: UiPoint::new(offset.x + size * 0.34, offset.y + size * 0.30),
8073            radius: size * 0.10,
8074            fill: color(244, 248, 255),
8075            stroke: None,
8076        },
8077    ]
8078}
8079
8080fn animation_orb_primitives(fill: ColorRgba, size: f32, offset: UiPoint) -> Vec<ScenePrimitive> {
8081    let center = size * 0.5;
8082    let radius = size * 0.44;
8083    vec![
8084        ScenePrimitive::Circle {
8085            center: UiPoint::new(offset.x + center, offset.y + center),
8086            radius,
8087            fill,
8088            stroke: Some(StrokeStyle::new(color(236, 244, 255), 1.0)),
8089        },
8090        ScenePrimitive::Circle {
8091            center: UiPoint::new(offset.x + size * 0.34, offset.y + size * 0.30),
8092            radius: size * 0.12,
8093            fill: color(244, 248, 255),
8094            stroke: None,
8095        },
8096    ]
8097}
8098
8099fn animation_morph_shape_primitives(
8100    fill: ColorRgba,
8101    size: f32,
8102    offset: UiPoint,
8103    amount: f32,
8104) -> Vec<ScenePrimitive> {
8105    vec![ScenePrimitive::MorphPolygon {
8106        from_points: animation_square_points(size, offset),
8107        to_points: animation_pentagon_points(size, offset),
8108        amount,
8109        fill,
8110        stroke: Some(StrokeStyle::new(color(226, 246, 236), 1.0)),
8111    }]
8112}
8113
8114fn animation_square_points(size: f32, offset: UiPoint) -> Vec<UiPoint> {
8115    let inset = size * 0.08;
8116    let left = offset.x + inset;
8117    let top = offset.y + inset;
8118    let right = offset.x + size - inset;
8119    let bottom = offset.y + size - inset;
8120    let center_x = offset.x + size * 0.5;
8121    vec![
8122        UiPoint::new(center_x, top),
8123        UiPoint::new(right, top),
8124        UiPoint::new(right, bottom),
8125        UiPoint::new(left, bottom),
8126        UiPoint::new(left, top),
8127    ]
8128}
8129
8130fn animation_pentagon_points(size: f32, offset: UiPoint) -> Vec<UiPoint> {
8131    let center = size * 0.5;
8132    let radius = size * 0.46;
8133    (0..5)
8134        .map(|index| {
8135            let angle = -std::f32::consts::FRAC_PI_2 + index as f32 * std::f32::consts::TAU / 5.0;
8136            UiPoint::new(
8137                offset.x + center + angle.cos() * radius,
8138                offset.y + center + angle.sin() * radius,
8139            )
8140        })
8141        .collect()
8142}
8143
8144fn animation_panel_primitives(offset: UiPoint) -> Vec<ScenePrimitive> {
8145    vec![ScenePrimitive::Rect(
8146        PaintRect::solid(
8147            UiRect::new(
8148                offset.x,
8149                offset.y,
8150                ANIMATION_PANEL_WIDTH,
8151                ANIMATION_PANEL_HEIGHT,
8152            ),
8153            color(232, 186, 88),
8154        )
8155        .stroke(AlignedStroke::inside(StrokeStyle::new(
8156            color(255, 226, 154),
8157            1.0,
8158        )))
8159        .corner_radii(CornerRadii::uniform(6.0)),
8160    )]
8161}
8162
8163fn list_and_table_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
8164    let body = section_with_min_viewport(
8165        ui,
8166        parent,
8167        "lists_tables",
8168        "Lists and tables",
8169        UiSize::new(520.0, 0.0),
8170    );
8171
8172    let list_row = ui.add_child(
8173        body,
8174        UiNode::container(
8175            "lists_tables.list_row",
8176            Layout::row()
8177                .size(LayoutSize::new(
8178                    LayoutDimension::percent(1.0),
8179                    LayoutDimension::Auto,
8180                ))
8181                .gap(LayoutGap::points(10.0, 10.0))
8182                .flex_wrap(LayoutFlexWrap::Wrap)
8183                .to_layout_style(),
8184        ),
8185    );
8186    let scroll_column = ui.add_child(
8187        list_row,
8188        UiNode::container(
8189            "lists_tables.scroll_area.column",
8190            Layout::column()
8191                .min_size(LayoutSize::points(220.0, 0.0))
8192                .gap(LayoutGap::points(6.0, 6.0))
8193                .flex(1.0, 1.0, LayoutDimension::points(245.0))
8194                .to_layout_style(),
8195        ),
8196    );
8197    widgets::label(
8198        ui,
8199        scroll_column,
8200        "lists_tables.scroll_area.title",
8201        "Scrollable list",
8202        text(12.0, color(166, 176, 190)),
8203        LayoutStyle::new().with_width_percent(1.0),
8204    );
8205    let nested_scroll = widgets::scroll_area(
8206        ui,
8207        scroll_column,
8208        "lists_tables.scroll_area",
8209        ScrollAxes::VERTICAL,
8210        LayoutStyle::column()
8211            .with_width_percent(1.0)
8212            .with_height(104.0),
8213    );
8214    ui.node_mut(nested_scroll)
8215        .set_action("lists_tables.scroll_area.scroll");
8216    if let Some(scroll) = ui.node_mut(nested_scroll).scroll_mut() {
8217        scroll.set_offset(UiPoint::new(0.0, state.list_scroll));
8218    }
8219    for index in 0..6 {
8220        widgets::label(
8221            ui,
8222            nested_scroll,
8223            format!("lists_tables.scroll_area.row.{index}"),
8224            format!("Scroll row {}", index + 1),
8225            text(12.0, color(200, 212, 228)),
8226            LayoutStyle::new()
8227                .with_width_percent(1.0)
8228                .with_height(26.0)
8229                .with_flex_shrink(0.0),
8230        );
8231    }
8232
8233    let virtual_list_column = ui.add_child(
8234        list_row,
8235        UiNode::container(
8236            "lists_tables.virtual_list.column",
8237            Layout::column()
8238                .min_size(LayoutSize::points(220.0, 0.0))
8239                .gap(LayoutGap::points(6.0, 6.0))
8240                .flex(1.0, 1.0, LayoutDimension::points(245.0))
8241                .to_layout_style(),
8242        ),
8243    );
8244
8245    widgets::label(
8246        ui,
8247        virtual_list_column,
8248        "lists_tables.virtual_list.title",
8249        "Virtualized list",
8250        text(12.0, color(166, 176, 190)),
8251        LayoutStyle::new().with_width_percent(1.0),
8252    );
8253    let virtual_list = widgets::virtual_list(
8254        ui,
8255        virtual_list_column,
8256        "lists_tables.virtual_list",
8257        widgets::VirtualListSpec {
8258            row_count: 24,
8259            row_height: 28.0,
8260            viewport_height: 104.0,
8261            scroll_offset: state.virtual_scroll,
8262            overscan: 1,
8263        },
8264        |ui, row_parent, row| {
8265            widgets::label(
8266                ui,
8267                row_parent,
8268                format!("lists_tables.virtual_list.row.{row}"),
8269                format!("Virtual row {}", row + 1),
8270                text(12.0, color(214, 224, 238)),
8271                LayoutStyle::new()
8272                    .with_width_percent(1.0)
8273                    .with_height(28.0)
8274                    .with_flex_shrink(0.0),
8275            );
8276        },
8277    );
8278    ui.node_mut(virtual_list)
8279        .set_action("lists_tables.virtual_list.scroll");
8280
8281    widgets::separator(
8282        ui,
8283        body,
8284        "lists_tables.virtualized_table.separator",
8285        widgets::SeparatorOptions::default(),
8286    );
8287    widgets::label(
8288        ui,
8289        body,
8290        "lists_tables.data_table.title",
8291        "Virtualized selectable table",
8292        text(12.0, color(166, 176, 190)),
8293        LayoutStyle::new().with_width_percent(1.0),
8294    );
8295    let virtual_controls = wrapping_row(ui, body, "lists_tables.virtualized_table.controls", 8.0);
8296    button(
8297        ui,
8298        virtual_controls,
8299        "lists_tables.virtualized_table.sort.name",
8300        if state.virtual_table_descending {
8301            "Name desc"
8302        } else {
8303            "Name asc"
8304        },
8305        "lists_tables.virtualized_table.sort.name",
8306        button_visual(38, 52, 70),
8307    );
8308    button(
8309        ui,
8310        virtual_controls,
8311        "lists_tables.virtualized_table.filter.status",
8312        if state.virtual_table_ready_only {
8313            "Ready only"
8314        } else {
8315            "All status"
8316        },
8317        "lists_tables.virtualized_table.filter.status",
8318        button_visual(38, 52, 70),
8319    );
8320    button(
8321        ui,
8322        virtual_controls,
8323        "lists_tables.virtualized_table.resize.reset",
8324        "Reset width",
8325        "lists_tables.virtualized_table.resize.reset",
8326        button_visual(38, 52, 70),
8327    );
8328
8329    let columns = virtual_table_columns(state);
8330    let visible_rows = virtual_table_visible_rows(state);
8331    let mut table_options = ext_widgets::DataTableOptions::default()
8332        .with_row_action_prefix("lists_tables.virtualized_table")
8333        .with_cell_action_prefix("lists_tables.virtualized_table")
8334        .with_scroll_action("lists_tables.virtualized_table.scroll");
8335    table_options.layout = LayoutStyle::column()
8336        .with_width_percent(1.0)
8337        .with_flex_shrink(0.0);
8338    table_options.header_visual = UiVisual::panel(
8339        color(34, 41, 50),
8340        Some(StrokeStyle::new(color(67, 78, 95), 1.0)),
8341        0.0,
8342    );
8343    table_options.header_text_style = text(12.0, color(222, 230, 240));
8344    table_options.selection = state.table_selection.clone();
8345    ext_widgets::virtualized_data_table(
8346        ui,
8347        body,
8348        "lists_tables.virtualized_table",
8349        &columns,
8350        ext_widgets::VirtualDataTableSpec {
8351            row_count: visible_rows.len(),
8352            row_height: 28.0,
8353            viewport_width: 520.0,
8354            viewport_height: 156.0,
8355            scroll_offset: UiPoint::new(0.0, state.virtual_table_scroll),
8356            overscan_rows: 1,
8357        },
8358        table_options,
8359        |ui, cell_parent, cell| {
8360            let source_row = visible_rows.get(cell.row).copied().unwrap_or(cell.row);
8361            let value = virtual_table_cell_value(source_row, cell.column);
8362            widgets::label(
8363                ui,
8364                cell_parent,
8365                format!(
8366                    "lists_tables.virtualized_table.cell.{}.{}.label",
8367                    cell.row, cell.column
8368                ),
8369                value,
8370                text(12.0, color(220, 228, 238)),
8371                LayoutStyle::new().with_width_percent(1.0),
8372            );
8373        },
8374    );
8375}
8376
8377#[allow(clippy::field_reassign_with_default)]
8378fn property_inspector(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
8379    let body = section(ui, parent, "property_inspector", "Property inspector");
8380    widgets::label(
8381        ui,
8382        body,
8383        "property_inspector.target",
8384        "Inspecting: Styling preview",
8385        text(12.0, color(196, 210, 230)),
8386        LayoutStyle::new().with_width_percent(1.0),
8387    );
8388    let mut options = ext_widgets::PropertyInspectorOptions::default();
8389    options.selected_index = Some(0);
8390    options.label_width = 120.0;
8391    options.row_height = 30.0;
8392    ext_widgets::property_inspector_grid(
8393        ui,
8394        body,
8395        "property_inspector.grid",
8396        &[
8397            ext_widgets::PropertyGridRow::new("target", "Widget", "Button preview").read_only(),
8398            ext_widgets::PropertyGridRow::new(
8399                "inner",
8400                "Inner margin",
8401                format!("{:.0}px", state.styling.inner_margin),
8402            )
8403            .with_kind(ext_widgets::PropertyValueKind::Number),
8404            ext_widgets::PropertyGridRow::new(
8405                "outer",
8406                "Outer margin",
8407                format!("{:.0}px", state.styling.outer_margin),
8408            )
8409            .with_kind(ext_widgets::PropertyValueKind::Number),
8410            ext_widgets::PropertyGridRow::new(
8411                "radius",
8412                "Corner radius",
8413                format!("{:.0}px", state.styling.corner_radius),
8414            )
8415            .with_kind(ext_widgets::PropertyValueKind::Number),
8416            ext_widgets::PropertyGridRow::new(
8417                "stroke",
8418                "Stroke",
8419                format!("{:.1}px", state.styling.stroke_width),
8420            )
8421            .with_kind(ext_widgets::PropertyValueKind::Number)
8422            .changed(),
8423            ext_widgets::PropertyGridRow::new("state", "Source", "Styling widget").read_only(),
8424        ],
8425        options,
8426    );
8427}
8428
8429fn diagnostics_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
8430    let body = section(ui, parent, "diagnostics", "Diagnostics");
8431    let debug_snapshot = &state.diagnostics_snapshot;
8432
8433    diagnostics_selected_node_panel(ui, body, debug_snapshot);
8434    diagnostics_animation_panel(ui, body, state, debug_snapshot);
8435
8436    widgets::label(
8437        ui,
8438        body,
8439        "diagnostics.a11y.title",
8440        "Accessibility",
8441        text(14.0, color(222, 230, 240)),
8442        LayoutStyle::new().with_width_percent(1.0),
8443    );
8444    let mut overlay_preview_style = UiNodeStyle::from(
8445        LayoutStyle::new()
8446            .with_width(320.0)
8447            .with_height(140.0)
8448            .with_flex_shrink(0.0),
8449    );
8450    overlay_preview_style.set_clip(ClipBehavior::Clip);
8451    let overlay_preview = ui.add_child(
8452        body,
8453        UiNode::container("diagnostics.a11y.preview", overlay_preview_style).with_visual(
8454            UiVisual::panel(
8455                color(12, 17, 24),
8456                Some(StrokeStyle::new(color(47, 62, 82), 1.0)),
8457                4.0,
8458            ),
8459        ),
8460    );
8461    let mut overlay_options = ext_widgets::AccessibilityDebugOverlayOptions {
8462        action_prefix: Some("diagnostics.a11y.visual".to_owned()),
8463        ..Default::default()
8464    };
8465    overlay_options.show_labels = false;
8466    ext_widgets::accessibility_debug_overlay(
8467        ui,
8468        overlay_preview,
8469        "diagnostics.a11y.visual",
8470        &debug_snapshot,
8471        overlay_options,
8472    );
8473    diagnostics_accessibility_details(ui, body, debug_snapshot);
8474
8475    let diagnostic_columns = ui.add_child(
8476        body,
8477        UiNode::container(
8478            "diagnostics.columns",
8479            LayoutStyle::column()
8480                .with_width_percent(1.0)
8481                .with_flex_shrink(0.0)
8482                .gap(10.0),
8483        ),
8484    );
8485    let command_column = ui.add_child(
8486        diagnostic_columns,
8487        UiNode::container(
8488            "diagnostics.commands.column",
8489            LayoutStyle::column()
8490                .with_width_percent(1.0)
8491                .with_flex_shrink(0.0)
8492                .gap(8.0),
8493        ),
8494    );
8495    let theme_column = ui.add_child(
8496        diagnostic_columns,
8497        UiNode::container(
8498            "diagnostics.theme.column",
8499            LayoutStyle::column()
8500                .with_width_percent(1.0)
8501                .with_flex_shrink(0.0)
8502                .gap(8.0),
8503        ),
8504    );
8505
8506    let registry = diagnostics_command_registry();
8507    diagnostics_commands_panel(ui, command_column, &registry);
8508
8509    let theme_snapshot = DebugThemeSnapshot::from_theme(&Theme::dark());
8510    diagnostics_theme_panel(ui, theme_column, &theme_snapshot);
8511}
8512
8513fn diagnostics_selected_node_panel(
8514    ui: &mut UiDocument,
8515    parent: UiNodeId,
8516    snapshot: &DebugInspectorSnapshot,
8517) {
8518    let panel = diagnostics_panel(ui, parent, "diagnostics.inspector", "Selected node");
8519    let rows = snapshot
8520        .node("diagnostics.sample.preview")
8521        .map(|node| {
8522            vec![
8523                ext_widgets::PropertyGridRow::new("name", "Node", "Preview action").read_only(),
8524                ext_widgets::PropertyGridRow::new("role", "Role", "Button").read_only(),
8525                ext_widgets::PropertyGridRow::new(
8526                    "bounds",
8527                    "Bounds",
8528                    format!(
8529                        "{:.0}, {:.0}; {:.0} x {:.0}",
8530                        node.rect.x, node.rect.y, node.rect.width, node.rect.height
8531                    ),
8532                )
8533                .with_kind(ext_widgets::PropertyValueKind::Number)
8534                .read_only(),
8535                ext_widgets::PropertyGridRow::new(
8536                    "clip",
8537                    "Clip",
8538                    format!("{:.0} x {:.0}", node.clip_rect.width, node.clip_rect.height),
8539                )
8540                .with_kind(ext_widgets::PropertyValueKind::Number)
8541                .read_only(),
8542                ext_widgets::PropertyGridRow::new(
8543                    "input",
8544                    "Input",
8545                    if node.input.pointer {
8546                        "Receives pointer input"
8547                    } else {
8548                        "Passive"
8549                    },
8550                )
8551                .read_only(),
8552            ]
8553        })
8554        .unwrap_or_else(|| {
8555            vec![
8556                ext_widgets::PropertyGridRow::new("missing", "Selected node", "No node selected")
8557                    .read_only(),
8558            ]
8559        });
8560    ext_widgets::property_inspector_grid(
8561        ui,
8562        panel,
8563        "diagnostics.inspector.rows",
8564        &rows,
8565        diagnostics_grid_options("Selected node details"),
8566    );
8567}
8568
8569fn diagnostics_animation_panel(
8570    ui: &mut UiDocument,
8571    parent: UiNodeId,
8572    state: &ShowcaseState,
8573    snapshot: &DebugInspectorSnapshot,
8574) {
8575    let graph_panel =
8576        diagnostics_panel(ui, parent, "diagnostics.animation.graph", "Animation state");
8577    if let Some(animation) = snapshot.animation("diagnostics.sample.preview") {
8578        let state_row = row(ui, graph_panel, "diagnostics.animation.graph.states", 8.0);
8579        for state_name in ["idle", "hot"] {
8580            diagnostic_chip(
8581                ui,
8582                state_row,
8583                format!("diagnostics.animation.graph.state.{state_name}"),
8584                state_name,
8585                animation.current_state == state_name,
8586            );
8587        }
8588
8589        let graph = animation.state_graph();
8590        for (index, edge) in graph.edges.iter().take(2).enumerate() {
8591            let value = if edge.kind == DebugAnimationGraphEdgeKind::Blend {
8592                "Input blend"
8593            } else {
8594                "State change"
8595            };
8596            let detail = if edge.label.is_empty() {
8597                if edge.active { "Active" } else { "Inactive" }.to_owned()
8598            } else if edge.active {
8599                format!("{}; active", edge.label)
8600            } else {
8601                edge.label.clone()
8602            };
8603            diagnostic_value_row(
8604                ui,
8605                graph_panel,
8606                format!("diagnostics.animation.graph.edge.{index}"),
8607                value,
8608                format!("{} -> {}", edge.from, edge.to),
8609            );
8610            diagnostic_muted_label(
8611                ui,
8612                graph_panel,
8613                format!("diagnostics.animation.graph.edge.{index}.detail"),
8614                detail,
8615            );
8616        }
8617    } else {
8618        diagnostic_muted_label(
8619            ui,
8620            graph_panel,
8621            "diagnostics.animation.graph.empty",
8622            "No animation state machine",
8623        );
8624    }
8625
8626    let controls_panel = diagnostics_panel(
8627        ui,
8628        parent,
8629        "diagnostics.animation.controls",
8630        "Animation controls",
8631    );
8632    let transport = row(
8633        ui,
8634        controls_panel,
8635        "diagnostics.animation.controls.transport",
8636        8.0,
8637    );
8638    diagnostic_button(
8639        ui,
8640        transport,
8641        "diagnostics.animation.controls.transport.pause_toggle",
8642        if state.diagnostics_animation_paused {
8643            "Resume"
8644        } else {
8645            "Pause"
8646        },
8647        state.diagnostics_animation_paused,
8648    );
8649    diagnostic_button(
8650        ui,
8651        transport,
8652        "diagnostics.animation.controls.transport.step",
8653        "Step",
8654        false,
8655    );
8656    diagnostic_slider_row(
8657        ui,
8658        controls_panel,
8659        "diagnostics.animation.controls.transport.scrub",
8660        "Scrub progress",
8661        state.diagnostics_animation_scrub,
8662        "diagnostics.animation.controls.transport.scrub",
8663    );
8664    diagnostic_button(
8665        ui,
8666        controls_panel,
8667        "diagnostics.animation.controls.input.active.toggle",
8668        if state.diagnostics_animation_active {
8669            "Active input: true"
8670        } else {
8671            "Active input: false"
8672        },
8673        state.diagnostics_animation_active,
8674    );
8675    diagnostic_slider_row(
8676        ui,
8677        controls_panel,
8678        "diagnostics.animation.controls.input.hover.set",
8679        "Hover blend",
8680        state.diagnostics_animation_hover,
8681        "diagnostics.animation.controls.input.hover.set",
8682    );
8683    diagnostic_button(
8684        ui,
8685        controls_panel,
8686        "diagnostics.animation.controls.input.pulse.fire",
8687        "Fire pulse",
8688        false,
8689    );
8690    widgets::label(
8691        ui,
8692        controls_panel,
8693        "diagnostics.animation.controls.status",
8694        format!(
8695            "Scrub {:.0}%   Hover {:.0}%   Pulses {}",
8696            state.diagnostics_animation_scrub * 100.0,
8697            state.diagnostics_animation_hover * 100.0,
8698            state.diagnostics_animation_pulse_count
8699        ),
8700        text(12.0, color(166, 180, 198)),
8701        LayoutStyle::new().with_width_percent(1.0),
8702    );
8703}
8704
8705fn diagnostics_accessibility_details(
8706    ui: &mut UiDocument,
8707    parent: UiNodeId,
8708    snapshot: &DebugInspectorSnapshot,
8709) {
8710    let rows = snapshot
8711        .accessibility_overlay
8712        .iter()
8713        .find(|node| node.name == "diagnostics.sample.preview")
8714        .map(|node| {
8715            let accessibility = node.accessibility.as_ref();
8716            vec![
8717                ext_widgets::PropertyGridRow::new("role", "Role", "Button").read_only(),
8718                ext_widgets::PropertyGridRow::new(
8719                    "label",
8720                    "Label",
8721                    accessibility
8722                        .and_then(|meta| meta.label.clone())
8723                        .unwrap_or_else(|| "Preview action".to_owned()),
8724                )
8725                .read_only(),
8726                ext_widgets::PropertyGridRow::new(
8727                    "focus",
8728                    "Focus order",
8729                    node.focus_index
8730                        .map(|index| format!("#{}", index + 1))
8731                        .unwrap_or_else(|| "Not focusable".to_owned()),
8732                )
8733                .read_only(),
8734                ext_widgets::PropertyGridRow::new(
8735                    "warnings",
8736                    "Warnings",
8737                    if node.warnings.is_empty() {
8738                        "None"
8739                    } else {
8740                        "Needs review"
8741                    },
8742                )
8743                .read_only(),
8744            ]
8745        })
8746        .unwrap_or_else(|| {
8747            vec![
8748                ext_widgets::PropertyGridRow::new("missing", "Accessibility", "No metadata")
8749                    .read_only(),
8750            ]
8751        });
8752    ext_widgets::property_inspector_grid(
8753        ui,
8754        parent,
8755        "diagnostics.a11y",
8756        &rows,
8757        diagnostics_grid_options("Accessibility metadata"),
8758    );
8759}
8760
8761fn diagnostics_commands_panel(ui: &mut UiDocument, parent: UiNodeId, registry: &CommandRegistry) {
8762    let panel = diagnostics_panel(ui, parent, "diagnostics.commands", "Commands");
8763    let formatter = ShortcutFormatter::default();
8764    for command_id in [
8765        "diagnostics.palette",
8766        "diagnostics.inspect",
8767        "diagnostics.record",
8768        "diagnostics.export_theme",
8769    ] {
8770        if let Some(command) = registry.command(command_id) {
8771            let shortcut = registry
8772                .command_bindings(command.meta.id.clone())
8773                .first()
8774                .map(|binding| formatter.format(binding.shortcut))
8775                .unwrap_or_else(|| "Unbound".to_owned());
8776            let status = if command.enabled {
8777                command
8778                    .meta
8779                    .category
8780                    .clone()
8781                    .unwrap_or_else(|| "General".to_owned())
8782            } else {
8783                command
8784                    .disabled_reason
8785                    .clone()
8786                    .unwrap_or_else(|| "Disabled".to_owned())
8787            };
8788            diagnostic_command_row(
8789                ui,
8790                panel,
8791                format!(
8792                    "diagnostics.commands.row.{}",
8793                    command.meta.id.as_str().replace('.', "_")
8794                ),
8795                &command.meta.label,
8796                &shortcut,
8797                &status,
8798            );
8799        }
8800    }
8801    diagnostic_value_row(
8802        ui,
8803        panel,
8804        "diagnostics.commands.conflicts",
8805        "Shortcut conflicts",
8806        if registry.conflicts().is_empty() {
8807            "None"
8808        } else {
8809            "Needs review"
8810        },
8811    );
8812}
8813
8814fn diagnostics_theme_panel(ui: &mut UiDocument, parent: UiNodeId, snapshot: &DebugThemeSnapshot) {
8815    let panel = diagnostics_panel(ui, parent, "diagnostics.theme", "Theme tokens");
8816    diagnostic_value_row(
8817        ui,
8818        panel,
8819        "diagnostics.theme.name",
8820        "Theme",
8821        snapshot.name.as_str(),
8822    );
8823    for token_path in ["colors.accent", "colors.surface", "typography.body"] {
8824        if let Some(token) = snapshot.token(token_path) {
8825            diagnostic_value_row(
8826                ui,
8827                panel,
8828                format!("diagnostics.theme.token.{}", token_path.replace('.', "_")),
8829                token_path,
8830                token.value.as_str(),
8831            );
8832        }
8833    }
8834    if let Some(component) = snapshot.component_states.first() {
8835        diagnostic_value_row(
8836            ui,
8837            panel,
8838            "diagnostics.theme.component.button",
8839            "Button normal",
8840            format!(
8841                "{:.0} x {:.0}, padding {:.0}",
8842                component.min_width, component.min_height, component.padding_x
8843            ),
8844        );
8845    }
8846}
8847
8848fn diagnostics_panel(
8849    ui: &mut UiDocument,
8850    parent: UiNodeId,
8851    name: impl Into<String>,
8852    title: impl Into<String>,
8853) -> UiNodeId {
8854    let name = name.into();
8855    let title = title.into();
8856    let panel = ui.add_child(
8857        parent,
8858        UiNode::container(
8859            name.clone(),
8860            LayoutStyle::column()
8861                .with_width_percent(1.0)
8862                .with_padding(10.0)
8863                .with_gap(8.0)
8864                .with_flex_shrink(0.0),
8865        )
8866        .with_visual(UiVisual::panel(
8867            color(15, 20, 28),
8868            Some(StrokeStyle::new(color(52, 65, 84), 1.0)),
8869            4.0,
8870        ))
8871        .with_accessibility(AccessibilityMeta::new(AccessibilityRole::Group).label(title.clone())),
8872    );
8873    widgets::label(
8874        ui,
8875        panel,
8876        format!("{name}.title"),
8877        title,
8878        text(13.0, color(222, 230, 240)),
8879        LayoutStyle::new().with_width_percent(1.0),
8880    );
8881    panel
8882}
8883
8884fn diagnostics_grid_options(label: impl Into<String>) -> ext_widgets::PropertyInspectorOptions {
8885    ext_widgets::PropertyInspectorOptions {
8886        label_width: 112.0,
8887        row_height: 28.0,
8888        accessibility_label: Some(label.into()),
8889        ..Default::default()
8890    }
8891}
8892
8893fn diagnostic_value_row(
8894    ui: &mut UiDocument,
8895    parent: UiNodeId,
8896    name: impl Into<String>,
8897    label: impl Into<String>,
8898    value: impl Into<String>,
8899) -> UiNodeId {
8900    let name = name.into();
8901    let row = row(ui, parent, name.clone(), 8.0);
8902    widgets::label(
8903        ui,
8904        row,
8905        format!("{name}.label"),
8906        label.into(),
8907        text(12.0, color(166, 180, 198)),
8908        LayoutStyle::new().with_width(136.0).with_flex_shrink(0.0),
8909    );
8910    widgets::label(
8911        ui,
8912        row,
8913        format!("{name}.value"),
8914        value.into(),
8915        text(12.0, color(226, 234, 244)),
8916        LayoutStyle::new().with_width_percent(1.0),
8917    );
8918    row
8919}
8920
8921fn diagnostic_muted_label(
8922    ui: &mut UiDocument,
8923    parent: UiNodeId,
8924    name: impl Into<String>,
8925    label: impl Into<String>,
8926) -> UiNodeId {
8927    let mut style = text(12.0, color(166, 180, 198));
8928    style.wrap = TextWrap::WordOrGlyph;
8929    widgets::label(
8930        ui,
8931        parent,
8932        name,
8933        label.into(),
8934        style,
8935        LayoutStyle::new().with_width_percent(1.0),
8936    )
8937}
8938
8939fn diagnostic_command_row(
8940    ui: &mut UiDocument,
8941    parent: UiNodeId,
8942    name: impl Into<String>,
8943    label: &str,
8944    shortcut: &str,
8945    status: &str,
8946) -> UiNodeId {
8947    let name = name.into();
8948    let row = row(ui, parent, name.clone(), 8.0);
8949    widgets::label(
8950        ui,
8951        row,
8952        format!("{name}.label"),
8953        label,
8954        text(12.0, color(226, 234, 244)),
8955        LayoutStyle::new()
8956            .with_width_percent(1.0)
8957            .with_flex_grow(1.0),
8958    );
8959    widgets::label(
8960        ui,
8961        row,
8962        format!("{name}.shortcut"),
8963        shortcut,
8964        text(12.0, color(166, 180, 198)),
8965        LayoutStyle::new().with_width(78.0).with_flex_shrink(0.0),
8966    );
8967    widgets::label(
8968        ui,
8969        row,
8970        format!("{name}.status"),
8971        status,
8972        text(12.0, color(166, 180, 198)),
8973        LayoutStyle::new().with_width(140.0).with_flex_shrink(0.0),
8974    );
8975    row
8976}
8977
8978fn diagnostic_slider_row(
8979    ui: &mut UiDocument,
8980    parent: UiNodeId,
8981    name: impl Into<String>,
8982    label: impl Into<String>,
8983    value: f32,
8984    action: impl Into<String>,
8985) -> UiNodeId {
8986    let name = name.into();
8987    let label = label.into();
8988    let row = row(ui, parent, format!("{name}.row"), 8.0);
8989    widgets::label(
8990        ui,
8991        row,
8992        format!("{name}.label"),
8993        label.clone(),
8994        text(12.0, color(166, 180, 198)),
8995        LayoutStyle::new().with_width(136.0).with_flex_shrink(0.0),
8996    );
8997    let slider_name = if name.ends_with(".set") {
8998        format!("{name}.slider")
8999    } else {
9000        name.clone()
9001    };
9002    let mut options = widgets::SliderOptions::default()
9003        .with_layout(LayoutStyle::new().with_width(160.0).with_height(24.0))
9004        .with_value_edit_action(action.into());
9005    options.accessibility_label = Some(label);
9006    widgets::slider(ui, row, slider_name, value, 0.0..1.0, options);
9007    widgets::label(
9008        ui,
9009        row,
9010        format!("{name}.percent"),
9011        format!("{:.0}%", value * 100.0),
9012        text(12.0, color(226, 234, 244)),
9013        LayoutStyle::new().with_width(46.0).with_flex_shrink(0.0),
9014    );
9015    row
9016}
9017
9018fn diagnostic_button(
9019    ui: &mut UiDocument,
9020    parent: UiNodeId,
9021    name: impl Into<String>,
9022    label: impl Into<String>,
9023    active: bool,
9024) -> UiNodeId {
9025    let name = name.into();
9026    let mut options = widgets::ButtonOptions::default()
9027        .with_layout(LayoutStyle::new().with_height(32.0))
9028        .with_action(name.clone())
9029        .pressed(active);
9030    if active {
9031        options.visual = UiVisual::panel(
9032            color(47, 94, 150),
9033            Some(StrokeStyle::new(color(103, 164, 224), 1.0)),
9034            4.0,
9035        );
9036    }
9037    widgets::button(ui, parent, name, label, options)
9038}
9039
9040fn diagnostic_chip(
9041    ui: &mut UiDocument,
9042    parent: UiNodeId,
9043    name: impl Into<String>,
9044    label: impl Into<String>,
9045    active: bool,
9046) -> UiNodeId {
9047    let name = name.into();
9048    let chip = ui.add_child(
9049        parent,
9050        UiNode::container(
9051            name.clone(),
9052            LayoutStyle::new()
9053                .with_width(82.0)
9054                .with_height(28.0)
9055                .with_padding(4.0)
9056                .with_flex_shrink(0.0),
9057        )
9058        .with_visual(if active {
9059            UiVisual::panel(
9060                color(47, 94, 150),
9061                Some(StrokeStyle::new(color(103, 164, 224), 1.0)),
9062                4.0,
9063            )
9064        } else {
9065            UiVisual::panel(
9066                color(31, 39, 50),
9067                Some(StrokeStyle::new(color(62, 76, 96), 1.0)),
9068                4.0,
9069            )
9070        }),
9071    );
9072    widgets::label(
9073        ui,
9074        chip,
9075        format!("{name}.label"),
9076        label.into(),
9077        text(12.0, color(226, 234, 244)),
9078        LayoutStyle::new().with_width_percent(1.0),
9079    );
9080    chip
9081}
9082
9083fn diagnostics_sample_snapshot(state: &ShowcaseState) -> DebugInspectorSnapshot {
9084    diagnostics_sample_snapshot_for(
9085        state.diagnostics_animation_hover,
9086        state.diagnostics_animation_active,
9087    )
9088}
9089
9090fn diagnostics_sample_snapshot_for(hover: f32, active: bool) -> DebugInspectorSnapshot {
9091    let mut sample = UiDocument::new(root_style(320.0, 180.0));
9092    let card = sample.add_child(
9093        sample.root(),
9094        UiNode::container(
9095            "diagnostics.sample.card",
9096            LayoutStyle::column()
9097                .with_width_percent(1.0)
9098                .with_height(120.0)
9099                .padding(12.0)
9100                .gap(8.0),
9101        )
9102        .with_visual(UiVisual::panel(
9103            color(16, 22, 30),
9104            Some(StrokeStyle::new(color(62, 77, 98), 1.0)),
9105            6.0,
9106        ))
9107        .with_accessibility(
9108            AccessibilityMeta::new(AccessibilityRole::Group).label("Diagnostics sample"),
9109        ),
9110    );
9111    sample.add_child(
9112        card,
9113        UiNode::container(
9114            "diagnostics.sample.preview",
9115            LayoutStyle::new().with_width(160.0).with_height(38.0),
9116        )
9117        .with_input(InputBehavior::BUTTON)
9118        .with_visual(UiVisual::panel(
9119            color(52, 112, 180),
9120            Some(StrokeStyle::new(color(116, 183, 255), 1.0)),
9121            5.0,
9122        ))
9123        .with_accessibility(
9124            AccessibilityMeta::new(AccessibilityRole::Button)
9125                .label("Preview action")
9126                .focusable(),
9127        )
9128        .with_animation(
9129            AnimationMachine::new(
9130                vec![
9131                    AnimationState::new(
9132                        "idle",
9133                        AnimatedValues::new(1.0, UiPoint::new(0.0, 0.0), 1.0),
9134                    ),
9135                    AnimationState::new(
9136                        "hot",
9137                        AnimatedValues::new(0.92, UiPoint::new(18.0, 0.0), 1.08),
9138                    ),
9139                ],
9140                vec![AnimationTransition::when(
9141                    "idle",
9142                    "hot",
9143                    AnimationCondition::bool("active", true),
9144                    0.18,
9145                )],
9146                "idle",
9147            )
9148            .expect("sample animation")
9149            .with_number_input("hover", hover)
9150            .with_blend_binding(AnimationBlendBinding::new("hover", "idle", "hot"))
9151            .with_bool_input("active", active)
9152            .with_trigger_input("pulse"),
9153        ),
9154    );
9155    widgets::label(
9156        &mut sample,
9157        card,
9158        "diagnostics.sample.label",
9159        "Sample node",
9160        text(12.0, color(198, 210, 226)),
9161        LayoutStyle::new().with_width_percent(1.0),
9162    );
9163    sample
9164        .compute_layout(UiSize::new(320.0, 180.0), &mut ApproxTextMeasurer)
9165        .expect("sample layout");
9166    DebugInspectorSnapshot::from_document(&sample, &mut ApproxTextMeasurer)
9167}
More examples
Hide additional examples
examples/animation_state_machine.rs (line 155)
128fn shape_machine(open: bool) -> AnimationMachine {
129    let closed = AnimatedValues::new(0.82, UiPoint::new(0.0, 0.0), 1.0).with_morph(0.0);
130    let open_values = AnimatedValues::new(1.0, UiPoint::new(160.0, 0.0), 1.08).with_morph(1.0);
131    AnimationMachine::new(
132        vec![
133            AnimationState::new("closed", closed),
134            AnimationState::new("open", open_values),
135        ],
136        vec![
137            AnimationTransition::when(
138                "closed",
139                "open",
140                AnimationCondition::bool(INPUT_OPEN, true),
141                0.24,
142            ),
143            AnimationTransition::when(
144                "open",
145                "closed",
146                AnimationCondition::bool(INPUT_OPEN, false),
147                0.18,
148            ),
149        ],
150        "closed",
151    )
152    .unwrap_or_else(|_| AnimationMachine::single_state("closed", closed))
153    .with_bool_input(INPUT_OPEN, open)
154    .with_number_input(INPUT_MORPH, if open { 1.0 } else { 0.0 })
155    .with_blend_binding(AnimationBlendBinding::new(INPUT_MORPH, "closed", "open"))
156}
Source

pub fn with_range(self, min: f32, max: f32) -> Self

Trait Implementations§

Source§

impl Clone for AnimationBlendBinding

Source§

fn clone(&self) -> AnimationBlendBinding

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AnimationBlendBinding

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for AnimationBlendBinding

Source§

fn eq(&self, other: &AnimationBlendBinding) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl StructuralPartialEq for AnimationBlendBinding

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,