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: f32Implementations§
Source§impl AnimationBlendBinding
impl AnimationBlendBinding
Sourcepub fn new(
input: impl Into<String>,
from: impl Into<String>,
to: impl Into<String>,
) -> Self
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, ®istry);
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
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}pub fn with_range(self, min: f32, max: f32) -> Self
Trait Implementations§
Source§impl Clone for AnimationBlendBinding
impl Clone for AnimationBlendBinding
Source§fn clone(&self) -> AnimationBlendBinding
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)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for AnimationBlendBinding
impl Debug for AnimationBlendBinding
Source§impl PartialEq for AnimationBlendBinding
impl PartialEq for AnimationBlendBinding
Source§fn eq(&self, other: &AnimationBlendBinding) -> bool
fn eq(&self, other: &AnimationBlendBinding) -> bool
Tests for
self and other values to be equal, and is used by ==.impl StructuralPartialEq for AnimationBlendBinding
Auto Trait Implementations§
impl Freeze for AnimationBlendBinding
impl RefUnwindSafe for AnimationBlendBinding
impl Send for AnimationBlendBinding
impl Sync for AnimationBlendBinding
impl Unpin for AnimationBlendBinding
impl UnsafeUnpin for AnimationBlendBinding
impl UnwindSafe for AnimationBlendBinding
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
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>
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)
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)
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.