Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

app: view cube & selector in shell, reduce-motion setting

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jun 13, 2026, 11:04 PM +0300) commit 3ffb7e21 parent abfa35f4 change-id pwpquwzz
+565 -141
+70 -3
crates/bone-app/src/chrome.rs
··· 1 1 use std::borrow::Cow; 2 2 3 - use bone_render::{ChromeInstance, SdfGlyphInstance}; 3 + use bone_render::{ChromeInstance, ConvexInstance, SdfGlyphInstance, StrokeInstance}; 4 4 use bone_text::{FontFace, FontWeight, ShapeRequest, ShapedLine, ShapedText, Shaper}; 5 5 use bone_ui::layout::LayoutRect; 6 6 use bone_ui::strings::StringTable; 7 7 use bone_ui::text::{MaskAtlas, MaskAtlasKey}; 8 - use bone_ui::theme::{Color, Theme}; 9 - use bone_ui::widgets::{HorizontalAlign, PaintPrim, WidgetPaint, lower_paint}; 8 + use bone_ui::theme::{Border, Color, StrokeWidth, Theme}; 9 + use bone_ui::widgets::{ 10 + ConvexPoly, HorizontalAlign, PaintPrim, PolyPath, WidgetPaint, lower_paint, 11 + }; 10 12 use swash::FontRef; 11 13 12 14 const MARK_FONT_SCALE: f32 = 0.7; 15 + 16 + const _: () = assert!(bone_ui::widgets::MAX_CONVEX_VERTS == bone_render::MAX_PLANES); 17 + const _: () = assert!(bone_ui::widgets::MAX_PATH_POINTS < bone_render::MAX_STROKE_POINTS); 13 18 14 19 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 15 20 pub enum SpanVertical { ··· 39 44 WidgetPaint::Label { .. } 40 45 | WidgetPaint::AlignedLabel { .. } 41 46 | WidgetPaint::Mark { .. } 47 + | WidgetPaint::ConvexFill { .. } 48 + | WidgetPaint::Stroke { .. } 42 49 ) 43 50 }) 44 51 .map(|p| prim_to_instance(&lower_paint(theme, p))) 45 52 .collect() 53 + } 54 + 55 + #[must_use] 56 + pub fn paint_to_convex_instances(paints: &[WidgetPaint]) -> Vec<ConvexInstance> { 57 + paints 58 + .iter() 59 + .filter_map(|p| match p { 60 + WidgetPaint::ConvexFill { poly, fill, border } => { 61 + convex_to_instance(poly, *fill, *border) 62 + } 63 + _ => None, 64 + }) 65 + .collect() 66 + } 67 + 68 + #[must_use] 69 + pub fn paint_to_stroke_instances(paints: &[WidgetPaint]) -> Vec<StrokeInstance> { 70 + paints 71 + .iter() 72 + .filter_map(|p| match p { 73 + WidgetPaint::Stroke { path, width, color } => stroke_to_instance(path, *width, *color), 74 + _ => None, 75 + }) 76 + .collect() 77 + } 78 + 79 + fn convex_to_instance( 80 + poly: &ConvexPoly, 81 + fill: Color, 82 + border: Option<Border>, 83 + ) -> Option<ConvexInstance> { 84 + let bounds = poly.bounds(); 85 + let rect = [ 86 + bounds.origin.x.value() - 1.0, 87 + bounds.origin.y.value() - 1.0, 88 + bounds.size.width.value() + 2.0, 89 + bounds.size.height.value() + 2.0, 90 + ]; 91 + let (border_premul, border_width) = border.map_or(([0.0; 4], 0.0), |b| { 92 + (b.color.linear_rgba_premul(), b.width.value_px()) 93 + }); 94 + ConvexInstance::new( 95 + rect, 96 + fill.linear_rgba_premul(), 97 + border_premul, 98 + border_width, 99 + &poly.edge_planes(), 100 + ) 101 + } 102 + 103 + fn stroke_to_instance(path: &PolyPath, width: StrokeWidth, color: Color) -> Option<StrokeInstance> { 104 + let half = width.value_px().max(1.0) * 0.5; 105 + let open: Vec<[f32; 2]> = path 106 + .points() 107 + .iter() 108 + .map(|p| [p.x.value(), p.y.value()]) 109 + .collect(); 110 + let closing = path.is_closed().then(|| open.first().copied()).flatten(); 111 + let points: Vec<[f32; 2]> = open.into_iter().chain(closing).collect(); 112 + StrokeInstance::new(&points, color.linear_rgba_premul(), half) 46 113 } 47 114 48 115 #[must_use]
+3
crates/bone-app/src/event.rs
··· 27 27 physical_key: PhysicalKey, 28 28 logical_key: Key, 29 29 text: Option<SmolStr>, 30 + repeat: bool, 30 31 }, 31 32 } 32 33 ··· 54 55 physical_key, 55 56 logical_key, 56 57 text, 58 + repeat, 57 59 .. 58 60 }, 59 61 .. ··· 61 63 physical_key, 62 64 logical_key, 63 65 text, 66 + repeat, 64 67 }), 65 68 WindowEvent::KeyboardInput { .. } 66 69 | WindowEvent::ActivationTokenDone { .. }
+278 -85
crates/bone-app/src/main.rs
··· 9 9 SketchEntity, SketchRelation, SketchVersion, SolverError, UndoStack, 10 10 }; 11 11 use bone_render::{ 12 - Camera2, ChromeInstance, ChromePipeline, ChromeTextPipeline, DragModifiers, EdgeScene, 13 - NavGesture, PickQuery, PickedItem, PixelsPerMm, RenderTargets, SdfGlyphInstance, SketchPreview, 14 - SketchRenderer, SketchScene, SolidFrameView, SolidRenderer, SolidScene, Style, SurfaceContext, 12 + Camera2, CameraTween, ChromeInstance, ChromePipeline, ChromeTextPipeline, ConvexInstance, 13 + ConvexPolyPipeline, DragModifiers, EdgeScene, NavGesture, PickQuery, PickedItem, PixelsPerMm, 14 + RenderTargets, SdfGlyphInstance, SketchPreview, SketchRenderer, SketchScene, SolidFrameView, 15 + SolidRenderer, SolidScene, StrokeInstance, StrokePipeline, Style, SurfaceContext, 15 16 ViewportExtent, ViewportNavigator, ViewportPoint, ViewportPx, ViewportRegion, frame_current, 16 - frame_standard_view, orbit_pitch, orbit_yaw, pan_pixels, roll_by, zoom_about_pixel, 17 + frame_standard_view, frame_view_direction, orbit_pitch, orbit_yaw, pan_pixels, roll_by, 18 + zoom_about_pixel, 17 19 }; 18 20 use bone_types::{ 19 - Aabb3, Angle, AngleTolerance, BudgetCeiling, Camera3, ChordHeightTolerance, DisplayMode, 20 - DocumentId, ExtrudeId, FeatureId, GeometryGeneration, Length, Point2, SketchId, SketchItemId, 21 - StandardView, Vec2, ZoomFactor, 21 + Aabb3, Angle, AngleTolerance, BudgetCeiling, Camera3, ChordHeightTolerance, CubicEasing, 22 + DisplayMode, DocumentId, ExtrudeId, FeatureId, GeometryGeneration, Length, Plane3, Point2, 23 + SketchId, SketchItemId, StandardView, Vec2, ZoomFactor, 22 24 }; 23 25 use bone_ui::a11y::AccessTreeBuilder; 24 26 use bone_ui::focus::FocusManager; ··· 66 68 mod status_badge; 67 69 mod strings; 68 70 mod tools; 71 + mod view_cube; 69 72 70 73 use dimension_editor::{DimensionEditorAction, DimensionEditorOutcome, DimensionEditorState}; 71 74 use selection::Selection; ··· 123 126 surface: SurfaceContext, 124 127 renderer: SketchRenderer, 125 128 chrome_pipeline: ChromePipeline, 129 + convex_pipeline: ConvexPolyPipeline, 130 + stroke_pipeline: StrokePipeline, 126 131 text_pipeline: ChromeTextPipeline, 127 132 sdf_atlas: MaskAtlas, 128 133 chrome_shaper: Shaper, ··· 143 148 camera3: Option<Camera3>, 144 149 framed_extrude: Option<ExtrudeId>, 145 150 navigator: ViewportNavigator, 151 + view: view_cube::ViewUi, 146 152 focus: FocusManager, 147 153 hit_state: HitState, 148 154 hotkeys: HotkeyTable, ··· 716 722 state.extrude_preview = None; 717 723 state.solid_view = None; 718 724 state.camera3 = None; 725 + state.view.home = None; 726 + state.view.tween = None; 719 727 return; 720 728 }; 721 729 let Some(sketch_version) = state.document.sketch(feature.sketch).map(Sketch::version) else { 722 730 state.extrude_preview = None; 723 731 state.solid_view = None; 724 732 state.camera3 = None; 733 + state.view.home = None; 734 + state.view.tween = None; 725 735 return; 726 736 }; 727 737 if extrude_preview_is_current(state.extrude_preview.as_ref(), &feature, sketch_version) { ··· 812 822 && let Some(view) = state.solid_view.as_ref() 813 823 && state.camera3.is_none() 814 824 { 815 - state.camera3 = 825 + let framed = 816 826 frame_standard_view(view.aabb, region.extent(), StandardView::Isometric, None).ok(); 827 + state.camera3 = framed; 828 + if state.view.home.is_none() { 829 + state.view.home = framed; 830 + } 831 + } 832 + } 833 + 834 + const VIEW_TWEEN_MS: u64 = 220; 835 + 836 + fn step_view_tween(state: &mut RenderState, now: FrameInstant) { 837 + let Some(active) = state.view.tween else { 838 + return; 839 + }; 840 + let elapsed = now.since(active.started); 841 + if let Ok(camera) = active.tween.sample(elapsed) { 842 + state.camera3 = Some(camera); 843 + } 844 + if active.tween.is_done(elapsed) { 845 + state.camera3 = Some(active.tween.to()); 846 + state.view.tween = None; 847 + } 848 + } 849 + 850 + fn start_view_tween(state: &mut RenderState, target: Camera3, now: FrameInstant) { 851 + let tween = if state.settings.reduce_motion { 852 + CameraTween::immediate(target) 853 + } else { 854 + CameraTween::eased( 855 + state.camera3.unwrap_or(target), 856 + target, 857 + std::time::Duration::from_millis(VIEW_TWEEN_MS), 858 + CubicEasing::STANDARD, 859 + ) 860 + }; 861 + state.view.tween = Some(view_cube::ActiveTween { 862 + tween, 863 + started: now, 864 + }); 865 + } 866 + 867 + fn apply_nav_camera(state: &mut RenderState, camera: Camera3) { 868 + state.camera3 = Some(camera); 869 + state.view.tween = None; 870 + } 871 + 872 + fn solid_aabb_and_extent(state: &RenderState) -> Option<(Aabb3, ViewportExtent)> { 873 + let region = solid_viewport_region(state.viewport_rect, state.surface.extent())?; 874 + let view = state.solid_view.as_ref()?; 875 + Some((view.aabb, region.extent())) 876 + } 877 + 878 + fn normal_to_plane(state: &RenderState) -> Option<Plane3> { 879 + let sketch_id = active_sketch_id(&state.mode, &state.plane_sketches)?; 880 + let plane = state 881 + .plane_sketches 882 + .iter() 883 + .find_map(|(plane, id)| (*id == sketch_id).then_some(*plane))?; 884 + Some(Plane3::from(plane.basis())) 885 + } 886 + 887 + fn frame_target_camera(state: &RenderState, pick: view_cube::ViewPick) -> Option<Camera3> { 888 + match pick { 889 + view_cube::ViewPick::Home => state.view.home, 890 + view_cube::ViewPick::Standard(view) => { 891 + let (aabb, extent) = solid_aabb_and_extent(state)?; 892 + frame_standard_view(aabb, extent, view, normal_to_plane(state)).ok() 893 + } 894 + view_cube::ViewPick::Direction(direction) => { 895 + let (aabb, extent) = solid_aabb_and_extent(state)?; 896 + frame_view_direction(aabb, extent, direction).ok() 897 + } 898 + } 899 + } 900 + 901 + fn apply_view_pick(state: &mut RenderState, pick: Option<view_cube::ViewPick>, now: FrameInstant) { 902 + let Some(pick) = pick else { 903 + return; 904 + }; 905 + if let Some(target) = frame_target_camera(state, pick) { 906 + start_view_tween(state, target, now); 907 + } 908 + } 909 + 910 + fn view_nav_enabled(state: &RenderState) -> bool { 911 + state.solid_view.is_some() 912 + && !modal_active(state) 913 + && state.focus.focused().is_none() 914 + && !dim_flow_active(&state.mode) 915 + } 916 + 917 + fn apply_view_menu( 918 + state: &mut RenderState, 919 + action: Option<view_cube::ViewCubeMenuAction>, 920 + now: FrameInstant, 921 + ) { 922 + match action { 923 + Some(view_cube::ViewCubeMenuAction::SetAsHome) => { 924 + state.view.home = state.camera3; 925 + } 926 + Some(view_cube::ViewCubeMenuAction::FitToWindow) => { 927 + if let (Some(camera), Some((aabb, extent))) = 928 + (state.camera3, solid_aabb_and_extent(state)) 929 + && let Ok(target) = frame_current(camera, aabb, extent) 930 + { 931 + start_view_tween(state, target, now); 932 + } 933 + } 934 + Some(view_cube::ViewCubeMenuAction::ViewNormalTo) => { 935 + apply_view_pick( 936 + state, 937 + Some(view_cube::ViewPick::Standard(StandardView::NormalTo)), 938 + now, 939 + ); 940 + } 941 + None => {} 817 942 } 818 943 } 819 944 ··· 1202 1327 let renderer = SketchRenderer::new(surface.gpu(), surface.color_format()); 1203 1328 let solid_renderer = SolidRenderer::new(surface.gpu(), surface.color_format()); 1204 1329 let chrome_pipeline = ChromePipeline::new(surface.gpu(), surface.color_format()); 1330 + let convex_pipeline = ConvexPolyPipeline::new(surface.gpu(), surface.color_format()); 1331 + let stroke_pipeline = StrokePipeline::new(surface.gpu(), surface.color_format()); 1205 1332 let sdf_atlas = MaskAtlas::new(MaskAtlasParams::STANDARD); 1206 1333 let text_pipeline = 1207 1334 ChromeTextPipeline::new(surface.gpu(), surface.color_format(), sdf_atlas.extent()); ··· 1243 1370 surface, 1244 1371 renderer, 1245 1372 chrome_pipeline, 1373 + convex_pipeline, 1374 + stroke_pipeline, 1246 1375 text_pipeline, 1247 1376 sdf_atlas, 1248 1377 chrome_shaper, ··· 1263 1392 camera3: None, 1264 1393 framed_extrude: None, 1265 1394 navigator: ViewportNavigator::new(), 1395 + view: view_cube::ViewUi::default(), 1266 1396 focus: FocusManager::new(), 1267 1397 hit_state: HitState::new(), 1268 1398 hotkeys: initial_hotkeys, ··· 1348 1478 && let Some(cursor) = viewport_local_point(position, region) 1349 1479 && let Ok(next) = state.navigator.drag_to(cursor, camera, region.extent()) 1350 1480 { 1351 - state.camera3 = Some(next); 1481 + apply_nav_camera(state, next); 1352 1482 } else if !modal 1353 1483 && self.input.panning() 1354 1484 && let Some(p) = prev ··· 1372 1502 self.dispatch_pointer_button(button, btn_state); 1373 1503 } 1374 1504 event::InputEvent::Wheel(delta) => { 1375 - if !modal_active(state) && self.input.cursor_in(state.viewport_rect) { 1376 - if state.solid_view.is_some() { 1377 - if let Some(camera) = state.camera3 1378 - && let Some(region) = 1379 - solid_viewport_region(state.viewport_rect, state.surface.extent()) 1380 - { 1381 - match delta { 1382 - MouseScrollDelta::PixelDelta(p) => { 1383 - if let Ok(next) = state 1384 - .navigator 1385 - .orbit_pixels(camera, region.extent(), p.x, p.y) 1386 - { 1387 - state.camera3 = Some(next); 1388 - } 1389 - } 1390 - MouseScrollDelta::LineDelta(..) => { 1391 - if let Some(cursor) = self 1392 - .input 1393 - .cursor_px 1394 - .and_then(|p| viewport_local_point(p, region)) 1395 - && let Ok(factor) = ZoomFactor::new(zoom_factor(delta)) 1396 - && let Ok(next) = 1397 - zoom_about_pixel(camera, region.extent(), cursor, factor) 1398 - { 1399 - state.camera3 = Some(next); 1400 - } 1401 - } 1402 - } 1403 - } 1404 - } else { 1405 - state.camera = 1406 - zoom_about(state.camera, self.input.cursor_px, zoom_factor(delta)); 1407 - } 1408 - } 1505 + self.dispatch_wheel(delta); 1409 1506 } 1410 1507 event::InputEvent::KeyDown { 1411 1508 physical_key, 1412 1509 logical_key, 1413 1510 text, 1511 + repeat, 1414 1512 } => { 1415 - self.dispatch_keydown(event_loop, physical_key, &logical_key, text.as_ref()); 1513 + self.dispatch_keydown( 1514 + event_loop, 1515 + physical_key, 1516 + &logical_key, 1517 + text.as_ref(), 1518 + repeat, 1519 + ); 1416 1520 } 1417 1521 } 1418 1522 ack 1419 1523 } 1420 1524 1525 + fn dispatch_wheel(&mut self, delta: MouseScrollDelta) { 1526 + let Some(state) = self.render.as_mut() else { 1527 + return; 1528 + }; 1529 + if modal_active(state) || !self.input.cursor_in(state.viewport_rect) { 1530 + return; 1531 + } 1532 + if state.solid_view.is_none() { 1533 + state.camera = zoom_about(state.camera, self.input.cursor_px, zoom_factor(delta)); 1534 + return; 1535 + } 1536 + let (Some(camera), Some(region)) = ( 1537 + state.camera3, 1538 + solid_viewport_region(state.viewport_rect, state.surface.extent()), 1539 + ) else { 1540 + return; 1541 + }; 1542 + match delta { 1543 + MouseScrollDelta::PixelDelta(p) => { 1544 + if let Ok(next) = state 1545 + .navigator 1546 + .orbit_pixels(camera, region.extent(), p.x, p.y) 1547 + { 1548 + apply_nav_camera(state, next); 1549 + } 1550 + } 1551 + MouseScrollDelta::LineDelta(..) => { 1552 + if let Some(cursor) = self 1553 + .input 1554 + .cursor_px 1555 + .and_then(|p| viewport_local_point(p, region)) 1556 + && let Ok(factor) = ZoomFactor::new(zoom_factor(delta)) 1557 + && let Ok(next) = zoom_about_pixel(camera, region.extent(), cursor, factor) 1558 + { 1559 + apply_nav_camera(state, next); 1560 + } 1561 + } 1562 + } 1563 + } 1564 + 1421 1565 fn dispatch_pointer_button(&mut self, button: MouseButton, btn_state: ElementState) { 1422 1566 let Some(state) = self.render.as_mut() else { 1423 1567 return; ··· 1507 1651 physical_key: PhysicalKey, 1508 1652 logical_key: &Key, 1509 1653 text: Option<&winit::keyboard::SmolStr>, 1654 + repeat: bool, 1510 1655 ) { 1511 1656 let Some(state) = self.render.as_mut() else { 1512 1657 return; ··· 1522 1667 let physical_named = physical_code.and_then(keycode_to_named); 1523 1668 let named = logical_named.or(physical_named); 1524 1669 let mods = self.input.modifier_mask(); 1670 + if named == Some(NamedKey::Space) && view_nav_enabled(state) { 1671 + if repeat { 1672 + return; 1673 + } 1674 + if self.input.modifiers.control_key() || self.input.modifiers.super_key() { 1675 + state.view.toggle_cube(); 1676 + } else { 1677 + state.view.toggle_selector(); 1678 + } 1679 + return; 1680 + } 1525 1681 if let Some(named) = named { 1526 1682 self.input 1527 1683 .pending_keys ··· 1547 1703 { 1548 1704 if state.solid_view.is_some() { 1549 1705 if let Some(next) = keyboard_camera3(code, &self.input, state) { 1550 - state.camera3 = Some(next); 1706 + apply_nav_camera(state, next); 1551 1707 } 1552 1708 } else if let Some(next) = keyboard_camera(code, &self.input, state) { 1553 1709 state.camera = next; ··· 1589 1745 let slack = std::time::Duration::from_millis(8); 1590 1746 input.start + pending.at.duration() + window + slack 1591 1747 }); 1592 - [native_poll, rename_deadline].into_iter().flatten().min() 1748 + let tween_tick = state 1749 + .view 1750 + .tween 1751 + .is_some() 1752 + .then(|| now + std::time::Duration::from_millis(8)); 1753 + [native_poll, rename_deadline, tween_tick] 1754 + .into_iter() 1755 + .flatten() 1756 + .min() 1593 1757 } 1594 1758 1595 1759 #[allow( ··· 1610 1774 let layout_size = layout_size_from_extent(extent); 1611 1775 let theme = Arc::clone(&state.theme); 1612 1776 let mut input = input_state.drain_snapshot(); 1777 + step_view_tween(state, input.frame); 1613 1778 let mut hits = HitFrame::new(); 1614 1779 let mut a11y = AccessTreeBuilder::new(); 1615 1780 let scopes = scopes_for_mode(&state.mode); ··· 1704 1869 sync_solid_camera(state, solid_region); 1705 1870 let cursor_layout = input_state.cursor_px.map(physical_to_layout_pos); 1706 1871 apply_hotkey_actions(state, &hotkey_actions, cursor_layout); 1872 + apply_view_pick(state, frame.view_pick, input.frame); 1873 + apply_view_menu(state, frame.view_menu, input.frame); 1707 1874 apply_menu_action(state, frame.menu_action); 1708 1875 apply_settings_change(state, frame.settings_change); 1709 1876 apply_relation_action(state, frame.activated_relation); ··· 1726 1893 let renderer = &mut state.renderer; 1727 1894 let mut chrome_stage = ChromeStage { 1728 1895 chrome: &mut state.chrome_pipeline, 1896 + convex: &mut state.convex_pipeline, 1897 + stroke: &mut state.stroke_pipeline, 1729 1898 text: &mut state.text_pipeline, 1730 1899 atlas_pixels, 1731 1900 atlas_version, ··· 1802 1971 1803 1972 struct ChromeStage<'a> { 1804 1973 chrome: &'a mut ChromePipeline, 1974 + convex: &'a mut ConvexPolyPipeline, 1975 + stroke: &'a mut StrokePipeline, 1805 1976 text: &'a mut ChromeTextPipeline, 1806 1977 atlas_pixels: &'a [u8], 1807 1978 atlas_version: u64, 1808 1979 viewport_px: [f32; 2], 1809 1980 } 1810 1981 1982 + fn merge_layers<T: Copy>(main: &[T], overlay: &[T]) -> Vec<T> { 1983 + main.iter().chain(overlay.iter()).copied().collect() 1984 + } 1985 + 1986 + fn count_u32(len: usize) -> u32 { 1987 + let Ok(count) = u32::try_from(len) else { 1988 + unreachable!("instance counts fit in u32"); 1989 + }; 1990 + count 1991 + } 1992 + 1811 1993 impl ChromeStage<'_> { 1812 1994 fn encode_layered( 1813 1995 &mut self, ··· 1816 1998 main: &ChromeLayer, 1817 1999 overlay: &ChromeLayer, 1818 2000 ) { 1819 - let combined_chrome: Vec<ChromeInstance> = main 1820 - .chrome 1821 - .iter() 1822 - .chain(overlay.chrome.iter()) 1823 - .copied() 1824 - .collect(); 1825 - let combined_glyphs: Vec<SdfGlyphInstance> = main 1826 - .glyphs 1827 - .iter() 1828 - .chain(overlay.glyphs.iter()) 1829 - .copied() 1830 - .collect(); 1831 - let Ok(main_chrome) = u32::try_from(main.chrome.len()) else { 1832 - unreachable!("chrome instance count fits in u32") 1833 - }; 1834 - let Ok(total_chrome) = u32::try_from(combined_chrome.len()) else { 1835 - unreachable!("chrome instance count fits in u32") 1836 - }; 1837 - let Ok(main_glyphs) = u32::try_from(main.glyphs.len()) else { 1838 - unreachable!("glyph instance count fits in u32") 1839 - }; 1840 - let Ok(total_glyphs) = u32::try_from(combined_glyphs.len()) else { 1841 - unreachable!("glyph instance count fits in u32") 1842 - }; 1843 - self.chrome.upload(self.viewport_px, &combined_chrome); 2001 + let chrome = merge_layers(&main.chrome, &overlay.chrome); 2002 + let convex = merge_layers(&main.convex, &overlay.convex); 2003 + let stroke = merge_layers(&main.stroke, &overlay.stroke); 2004 + let glyphs = merge_layers(&main.glyphs, &overlay.glyphs); 2005 + self.chrome.upload(self.viewport_px, &chrome); 2006 + self.convex.upload(self.viewport_px, &convex); 2007 + self.stroke.upload(self.viewport_px, &stroke); 1844 2008 self.text.upload( 1845 2009 self.viewport_px, 1846 2010 self.atlas_pixels, 1847 2011 self.atlas_version, 1848 - &combined_glyphs, 2012 + &glyphs, 1849 2013 ); 1850 - self.chrome.draw_range(encoder, color, 0..main_chrome); 1851 - self.text.draw_range(encoder, color, 0..main_glyphs); 1852 - self.chrome 1853 - .draw_range(encoder, color, main_chrome..total_chrome); 1854 - self.text 1855 - .draw_range(encoder, color, main_glyphs..total_glyphs); 2014 + let (mc, tc) = (count_u32(main.chrome.len()), count_u32(chrome.len())); 2015 + let (mv, tv) = (count_u32(main.convex.len()), count_u32(convex.len())); 2016 + let (ms, ts) = (count_u32(main.stroke.len()), count_u32(stroke.len())); 2017 + let (mg, tg) = (count_u32(main.glyphs.len()), count_u32(glyphs.len())); 2018 + self.chrome.draw_range(encoder, color, 0..mc); 2019 + self.convex.draw_range(encoder, color, 0..mv); 2020 + self.stroke.draw_range(encoder, color, 0..ms); 2021 + self.text.draw_range(encoder, color, 0..mg); 2022 + self.chrome.draw_range(encoder, color, mc..tc); 2023 + self.convex.draw_range(encoder, color, mv..tv); 2024 + self.stroke.draw_range(encoder, color, ms..ts); 2025 + self.text.draw_range(encoder, color, mg..tg); 1856 2026 } 1857 2027 } 1858 2028 ··· 1880 2050 1881 2051 struct ChromeLayer { 1882 2052 chrome: Vec<ChromeInstance>, 2053 + convex: Vec<ConvexInstance>, 2054 + stroke: Vec<StrokeInstance>, 1883 2055 glyphs: Vec<SdfGlyphInstance>, 1884 2056 } 1885 2057 ··· 1888 2060 paints: &[bone_ui::widgets::WidgetPaint], 1889 2061 ) -> ChromeLayer { 1890 2062 let chrome = chrome::paint_to_instances(&state.theme, paints); 2063 + let convex = chrome::paint_to_convex_instances(paints); 2064 + let stroke = chrome::paint_to_stroke_instances(paints); 1891 2065 let spans = chrome::paint_to_text_spans(paints, &state.strings); 1892 2066 let glyphs = chrome::build_glyph_instances( 1893 2067 &spans, ··· 1896 2070 &state.sans_font, 1897 2071 &state.mono_font, 1898 2072 ); 1899 - ChromeLayer { chrome, glyphs } 2073 + ChromeLayer { 2074 + chrome, 2075 + convex, 2076 + stroke, 2077 + glyphs, 2078 + } 1900 2079 } 1901 2080 1902 2081 fn apply_hotkey_actions( ··· 2466 2645 confirm_action: None, 2467 2646 menu_action: None, 2468 2647 settings_change: None, 2648 + view_pick: None, 2649 + view_menu: None, 2469 2650 } 2470 2651 } 2471 2652 ··· 2605 2786 &state.settings, 2606 2787 layout_size, 2607 2788 cursor_world, 2789 + state.camera3.filter(|_| state.solid_view.is_some()), 2790 + &mut state.view, 2608 2791 ); 2609 2792 let dim_outcome = pending_dim(&state.mode).map(|pending| { 2610 2793 let live_anchor = state ··· 3171 3354 refresh_active_scene(state); 3172 3355 } 3173 3356 Some(shell::MenuAction::ZoomFit) => { 3174 - let solid_fit = state.solid_view.as_ref().map(|view| view.aabb).and_then(|aabb| { 3175 - let region = solid_viewport_region(state.viewport_rect, state.surface.extent())?; 3176 - frame_current(state.camera3?, aabb, region.extent()).ok() 3177 - }); 3357 + let solid_fit = state 3358 + .solid_view 3359 + .as_ref() 3360 + .map(|view| view.aabb) 3361 + .and_then(|aabb| { 3362 + let region = 3363 + solid_viewport_region(state.viewport_rect, state.surface.extent())?; 3364 + frame_current(state.camera3?, aabb, region.extent()).ok() 3365 + }); 3178 3366 if let Some(next) = solid_fit { 3179 - state.camera3 = Some(next); 3367 + apply_nav_camera(state, next); 3180 3368 } else { 3181 3369 state.camera = zoom_fit(state.camera, &state.scene, state.viewport_rect); 3182 3370 } ··· 3838 4026 confirm_action: None, 3839 4027 menu_action: None, 3840 4028 settings_change: None, 4029 + view_pick: None, 4030 + view_menu: None, 3841 4031 } 3842 4032 } 3843 4033 ··· 4073 4263 super::drag_gesture(ModifiersState::empty()), 4074 4264 NavGesture::Orbit 4075 4265 ); 4076 - assert_eq!(super::drag_gesture(ModifiersState::CONTROL), NavGesture::Pan); 4266 + assert_eq!( 4267 + super::drag_gesture(ModifiersState::CONTROL), 4268 + NavGesture::Pan 4269 + ); 4077 4270 assert_eq!(super::drag_gesture(ModifiersState::SHIFT), NavGesture::Zoom); 4078 4271 assert_eq!(super::drag_gesture(ModifiersState::ALT), NavGesture::Roll); 4079 4272 assert_eq!(
+6
crates/bone-app/src/settings.rs
··· 9 9 pub struct Settings { 10 10 pub pick_aperture: PickAperture, 11 11 pub hotkey_overrides: HotkeyOverrides, 12 + pub reduce_motion: bool, 12 13 } 13 14 14 15 impl Default for Settings { ··· 16 17 Self { 17 18 pick_aperture: PickAperture::DEFAULT, 18 19 hotkey_overrides: HotkeyOverrides::default(), 20 + reduce_motion: false, 19 21 } 20 22 } 21 23 } ··· 24 26 struct SettingsOnDisk { 25 27 aperture_px: u32, 26 28 hotkey_overrides: HotkeyOverrides, 29 + #[serde(default)] 30 + reduce_motion: bool, 27 31 } 28 32 29 33 impl From<&Settings> for SettingsOnDisk { ··· 31 35 Self { 32 36 aperture_px: value.pick_aperture.radius_px(), 33 37 hotkey_overrides: value.hotkey_overrides.clone(), 38 + reduce_motion: value.reduce_motion, 34 39 } 35 40 } 36 41 } ··· 40 45 Settings { 41 46 pick_aperture: PickAperture::new(self.aperture_px), 42 47 hotkey_overrides: self.hotkey_overrides, 48 + reduce_motion: self.reduce_motion, 43 49 } 44 50 } 45 51 }
+156 -53
crates/bone-app/src/shell.rs
··· 8 8 SketchVersion, 9 9 }; 10 10 use bone_types::{ 11 - Angle, ExtrudeId, Length, Point2, PositiveLength, SketchDimensionId, SketchEntityId, SketchId, 11 + Angle, Camera3, ExtrudeId, Length, Point2, PositiveLength, SketchDimensionId, SketchEntityId, 12 + SketchId, 12 13 }; 13 14 use bone_ui::a11y::{AccessNode, Role}; 14 15 use bone_ui::frame::{FrameCtx, InteractDeclaration}; ··· 22 23 use bone_ui::theme::{Border, ElevationLevel, Radius, Spacing, Step12, StrokeWidth, Theme}; 23 24 use bone_ui::widgets::GlyphMark; 24 25 use bone_ui::widgets::{ 25 - AngleEditor, BoolEditor, Clipboard, Dialog, DialogButton, HotkeyCapture, HotkeyCaptureState, 26 - LabelText, LengthEditor, MemoryClipboard, MenuBar, MenuBarEntry, MenuBarState, MenuItem, 27 - PanelState, PropertyCell, PropertyEditor, PropertyGrid, PropertyOption, PropertyRow, 28 - RenameCommit, Ribbon, RibbonGroup, RibbonIconSize, RibbonTab, SelectionEditor, Slider, 29 - SliderRange, SliderStep, StatusAlign, StatusBar, StatusItem, Tab, Tabs, TabsOrientation, 30 - ToolbarItem, TreeNode, TreeView, TreeViewState, WidgetPaint, show_dialog, show_hotkey_capture, 31 - show_menu_bar, show_property_grid, show_ribbon, show_slider, show_status_bar, show_tabs, 32 - show_tree_view, 26 + AngleEditor, BoolEditor, Checkbox, CheckboxState, Clipboard, Dialog, DialogButton, 27 + HotkeyCapture, HotkeyCaptureState, LabelText, LengthEditor, MemoryClipboard, MenuBar, 28 + MenuBarEntry, MenuBarState, MenuItem, PanelState, PropertyCell, PropertyEditor, PropertyGrid, 29 + PropertyOption, PropertyRow, RenameCommit, Ribbon, RibbonGroup, RibbonIconSize, RibbonTab, 30 + SelectionEditor, Slider, SliderRange, SliderStep, StatusAlign, StatusBar, StatusItem, Tab, 31 + Tabs, TabsOrientation, ToolbarItem, TreeNode, TreeView, TreeViewState, WidgetPaint, 32 + show_checkbox, show_dialog, show_hotkey_capture, show_menu_bar, show_property_grid, 33 + show_ribbon, show_slider, show_status_bar, show_tabs, show_tree_view, 33 34 }; 34 35 use bone_ui::{WidgetId, WidgetKey}; 35 36 use uom::si::angle::degree; ··· 51 52 status_panel_widget_id, 52 53 }; 53 54 use crate::strings; 55 + use crate::view_cube::{ 56 + ViewCubeInputs, ViewCubeMenuAction, ViewPick, ViewUi, render_view_cube, render_view_selector, 57 + }; 54 58 55 59 const RIBBON_GROUP_PADDING_PX: f32 = 8.0; 56 60 const RIBBON_TOOLBAR_GAP_PX: f32 = 4.0; ··· 92 96 viewport: WidgetId, 93 97 confirm_accept: WidgetId, 94 98 confirm_cancel: WidgetId, 99 + view_cube: WidgetId, 100 + view_cube_menu: WidgetId, 101 + view_selector: WidgetId, 95 102 status_bar: WidgetId, 96 103 doc_tabs: WidgetId, 97 104 doc_tab_model: WidgetId, ··· 121 128 menu_sketch_exit: WidgetId, 122 129 settings_dialog: WidgetId, 123 130 settings_aperture_slider: WidgetId, 131 + settings_reduce_motion: WidgetId, 124 132 settings_reset: WidgetId, 125 133 settings_close: WidgetId, 126 134 keyboard_dialog: WidgetId, ··· 159 167 viewport, 160 168 confirm_accept: viewport.child(WidgetKey::new("confirm.accept")), 161 169 confirm_cancel: viewport.child(WidgetKey::new("confirm.cancel")), 170 + view_cube: viewport.child(WidgetKey::new("view_cube")), 171 + view_cube_menu: viewport.child(WidgetKey::new("view_cube.menu")), 172 + view_selector: viewport.child(WidgetKey::new("view_selector")), 162 173 status_bar: root.child(WidgetKey::new("status")), 163 174 doc_tabs: root.child(WidgetKey::new("doc_tabs")), 164 175 doc_tab_model: root.child(WidgetKey::new("doc_tabs.model")), ··· 188 199 menu_sketch_exit: menu_sketch.child(WidgetKey::new("exit")), 189 200 settings_dialog, 190 201 settings_aperture_slider: settings_dialog.child(WidgetKey::new("aperture.slider")), 202 + settings_reduce_motion: settings_dialog.child(WidgetKey::new("reduce_motion.checkbox")), 191 203 settings_reset: settings_dialog.child(WidgetKey::new("button.reset")), 192 204 settings_close: settings_dialog.child(WidgetKey::new("button.close")), 193 205 keyboard_dialog, ··· 437 449 pub confirm_action: Option<ConfirmAction>, 438 450 pub menu_action: Option<MenuAction>, 439 451 pub settings_change: Option<crate::settings::Settings>, 452 + pub view_pick: Option<ViewPick>, 453 + pub view_menu: Option<ViewCubeMenuAction>, 440 454 } 441 455 442 456 #[derive(Copy, Clone, Debug, PartialEq)] ··· 466 480 confirm_action: None, 467 481 menu_action: None, 468 482 settings_change: None, 483 + view_pick: None, 484 + view_menu: None, 469 485 } 470 486 } 471 487 } ··· 532 548 settings: &Settings, 533 549 viewport_size: LayoutSize, 534 550 cursor_world: Option<Point2>, 551 + camera3: Option<Camera3>, 552 + view: &mut ViewUi, 535 553 ) -> ShellFrame { 536 554 let theme = ctx.theme(); 537 555 let direction = ctx.direction(); ··· 684 702 let confirm = 685 703 render_confirm_corner(ctx, viewport_rect, &self.ids, confirm_visible, &mut paints); 686 704 let confirm_action = confirm; 705 + let normal_to_available = active_sketch.is_some(); 706 + let (view_pick, view_menu) = render_view_controls( 707 + ctx, 708 + ViewControlInputs { 709 + viewport: viewport_rect, 710 + camera3, 711 + ids: &self.ids, 712 + view, 713 + normal_to_available, 714 + confirm_visible, 715 + }, 716 + &mut paints, 717 + &mut popover_paints, 718 + ); 687 719 let exit_sketch = confirm_action.is_some() || menu_action == Some(MenuAction::ExitSketch); 688 720 let activated_tool = activated_widget.and_then(|id| self.tool_index.get(&id).copied()); 689 721 let activated_feature_tool = ··· 747 779 confirm_action, 748 780 menu_action, 749 781 settings_change, 782 + view_pick, 783 + view_menu, 750 784 } 751 785 } 752 786 } 753 787 788 + struct ViewControlInputs<'a> { 789 + viewport: LayoutRect, 790 + camera3: Option<Camera3>, 791 + ids: &'a ShellIds, 792 + view: &'a mut ViewUi, 793 + normal_to_available: bool, 794 + confirm_visible: bool, 795 + } 796 + 797 + fn render_view_controls( 798 + ctx: &mut FrameCtx<'_>, 799 + inputs: ViewControlInputs<'_>, 800 + paints: &mut Vec<WidgetPaint>, 801 + popover_paints: &mut Vec<WidgetPaint>, 802 + ) -> (Option<ViewPick>, Option<ViewCubeMenuAction>) { 803 + let ViewControlInputs { 804 + viewport, 805 + camera3, 806 + ids, 807 + view, 808 + normal_to_available, 809 + confirm_visible, 810 + } = inputs; 811 + let Some(camera) = camera3 else { 812 + return (None, None); 813 + }; 814 + let outcome = render_view_cube( 815 + ctx, 816 + ViewCubeInputs { 817 + viewport, 818 + camera, 819 + base: ids.view_cube, 820 + menu_id: ids.view_cube_menu, 821 + view, 822 + normal_to_available, 823 + confirm_visible, 824 + }, 825 + paints, 826 + popover_paints, 827 + ); 828 + let selected = render_view_selector( 829 + ctx, 830 + viewport, 831 + ids.view_selector, 832 + view, 833 + normal_to_available, 834 + popover_paints, 835 + ); 836 + let pick = outcome.pick.or_else(|| selected.map(ViewPick::Standard)); 837 + (pick, outcome.menu) 838 + } 839 + 754 840 const SETTINGS_DIALOG_WIDTH: f32 = 420.0; 755 - const SETTINGS_DIALOG_HEIGHT: f32 = 220.0; 841 + const SETTINGS_DIALOG_HEIGHT: f32 = 256.0; 756 842 const SETTINGS_DIALOG_GUTTER: f32 = 16.0; 757 843 const SETTINGS_LABEL_HEIGHT: f32 = 20.0; 758 844 const SETTINGS_HINT_HEIGHT: f32 = 36.0; 759 845 const SETTINGS_SLIDER_HEIGHT: f32 = 28.0; 846 + const SETTINGS_CHECKBOX_HEIGHT: f32 = 24.0; 760 847 const SETTINGS_LABEL_TO_HINT_GAP: f32 = 6.0; 761 848 const SETTINGS_HINT_TO_SLIDER_GAP: f32 = 12.0; 849 + const SETTINGS_SLIDER_TO_CHECKBOX_GAP: f32 = 12.0; 762 850 const PICK_APERTURE_MIN_PX: i32 = 1; 763 851 const PICK_APERTURE_MAX_PX: i32 = 30; 764 852 ··· 791 879 settings.pick_aperture.radius_px(), 792 880 ); 793 881 let aperture_slider_id = ids.settings_aperture_slider; 794 - let (response, slider_change) = show_dialog( 882 + let reduce_motion_id = ids.settings_reduce_motion; 883 + let (response, body_change) = show_dialog( 795 884 ctx, 796 885 Dialog::new( 797 886 ids.settings_dialog, ··· 805 894 ctx, 806 895 body_rect, 807 896 aperture_slider_id, 897 + reduce_motion_id, 808 898 settings, 809 899 aperture_label_text, 810 900 paint, ··· 817 907 } 818 908 if response.activated == Some(ids.settings_reset) { 819 909 return Some(Settings { 820 - pick_aperture: PickAperture::DEFAULT, 821 910 hotkey_overrides: settings.hotkey_overrides.clone(), 911 + ..Settings::default() 822 912 }); 823 913 } 824 - slider_change 914 + body_change 825 915 } 826 916 827 917 fn settings_dialog_body( 828 918 ctx: &mut FrameCtx<'_>, 829 919 body_rect: LayoutRect, 830 920 aperture_slider_id: WidgetId, 921 + reduce_motion_id: WidgetId, 831 922 settings: &Settings, 832 923 aperture_label_text: String, 833 924 paint: &mut Vec<WidgetPaint>, ··· 865 956 ), 866 957 ); 867 958 paint.extend(response.paint); 868 - response.changed.then(|| { 959 + let slider_change = response.changed.then(|| { 869 960 let clamped = response 870 961 .value 871 962 .clamp(PICK_APERTURE_MIN_PX, PICK_APERTURE_MAX_PX); ··· 876 967 let radius = clamped as u32; 877 968 Settings { 878 969 pick_aperture: PickAperture::new(radius), 879 - hotkey_overrides: settings.hotkey_overrides.clone(), 970 + ..settings.clone() 880 971 } 881 - }) 972 + }); 973 + let checkbox_state = if settings.reduce_motion { 974 + CheckboxState::Checked 975 + } else { 976 + CheckboxState::Unchecked 977 + }; 978 + let checkbox = show_checkbox( 979 + ctx, 980 + Checkbox::new( 981 + reduce_motion_id, 982 + settings_checkbox_rect(body_rect), 983 + strings::SETTINGS_REDUCE_MOTION_LABEL, 984 + checkbox_state, 985 + ), 986 + ); 987 + paint.extend(checkbox.paint); 988 + let checkbox_change = checkbox.toggled.then(|| Settings { 989 + reduce_motion: !settings.reduce_motion, 990 + ..settings.clone() 991 + }); 992 + slider_change.or(checkbox_change) 882 993 } 883 994 884 - fn settings_label_rect(body: LayoutRect) -> LayoutRect { 995 + fn settings_row_rect(body: LayoutRect, top_offset: f32, height: f32) -> LayoutRect { 885 996 LayoutRect::new( 886 997 LayoutPos::new( 887 998 LayoutPx::new(body.origin.x.value() + SETTINGS_DIALOG_GUTTER), 888 - LayoutPx::new(body.origin.y.value() + SETTINGS_DIALOG_GUTTER), 999 + LayoutPx::new(body.origin.y.value() + SETTINGS_DIALOG_GUTTER + top_offset), 889 1000 ), 890 1001 LayoutSize::new( 891 1002 LayoutPx::saturating_nonneg(body.size.width.value() - 2.0 * SETTINGS_DIALOG_GUTTER), 892 - LayoutPx::new(SETTINGS_LABEL_HEIGHT), 1003 + LayoutPx::new(height), 893 1004 ), 894 1005 ) 895 1006 } 896 1007 1008 + const SETTINGS_HINT_TOP: f32 = SETTINGS_LABEL_HEIGHT + SETTINGS_LABEL_TO_HINT_GAP; 1009 + const SETTINGS_SLIDER_TOP: f32 = 1010 + SETTINGS_HINT_TOP + SETTINGS_HINT_HEIGHT + SETTINGS_HINT_TO_SLIDER_GAP; 1011 + const SETTINGS_CHECKBOX_TOP: f32 = 1012 + SETTINGS_SLIDER_TOP + SETTINGS_SLIDER_HEIGHT + SETTINGS_SLIDER_TO_CHECKBOX_GAP; 1013 + 1014 + fn settings_label_rect(body: LayoutRect) -> LayoutRect { 1015 + settings_row_rect(body, 0.0, SETTINGS_LABEL_HEIGHT) 1016 + } 1017 + 897 1018 fn settings_hint_rect(body: LayoutRect) -> LayoutRect { 898 - LayoutRect::new( 899 - LayoutPos::new( 900 - LayoutPx::new(body.origin.x.value() + SETTINGS_DIALOG_GUTTER), 901 - LayoutPx::new( 902 - body.origin.y.value() 903 - + SETTINGS_DIALOG_GUTTER 904 - + SETTINGS_LABEL_HEIGHT 905 - + SETTINGS_LABEL_TO_HINT_GAP, 906 - ), 907 - ), 908 - LayoutSize::new( 909 - LayoutPx::saturating_nonneg(body.size.width.value() - 2.0 * SETTINGS_DIALOG_GUTTER), 910 - LayoutPx::new(SETTINGS_HINT_HEIGHT), 911 - ), 912 - ) 1019 + settings_row_rect(body, SETTINGS_HINT_TOP, SETTINGS_HINT_HEIGHT) 913 1020 } 914 1021 915 1022 fn settings_slider_rect(body: LayoutRect) -> LayoutRect { 916 - LayoutRect::new( 917 - LayoutPos::new( 918 - LayoutPx::new(body.origin.x.value() + SETTINGS_DIALOG_GUTTER), 919 - LayoutPx::new( 920 - body.origin.y.value() 921 - + SETTINGS_DIALOG_GUTTER 922 - + SETTINGS_LABEL_HEIGHT 923 - + SETTINGS_LABEL_TO_HINT_GAP 924 - + SETTINGS_HINT_HEIGHT 925 - + SETTINGS_HINT_TO_SLIDER_GAP, 926 - ), 927 - ), 928 - LayoutSize::new( 929 - LayoutPx::saturating_nonneg(body.size.width.value() - 2.0 * SETTINGS_DIALOG_GUTTER), 930 - LayoutPx::new(SETTINGS_SLIDER_HEIGHT), 931 - ), 932 - ) 1023 + settings_row_rect(body, SETTINGS_SLIDER_TOP, SETTINGS_SLIDER_HEIGHT) 1024 + } 1025 + 1026 + fn settings_checkbox_rect(body: LayoutRect) -> LayoutRect { 1027 + settings_row_rect(body, SETTINGS_CHECKBOX_TOP, SETTINGS_CHECKBOX_HEIGHT) 933 1028 } 934 1029 935 1030 const KEYBOARD_DIALOG_WIDTH: f32 = 460.0; ··· 992 1087 } 993 1088 if response.activated == Some(ids.keyboard_dialog_reset) { 994 1089 return Some(Settings { 995 - pick_aperture: settings.pick_aperture, 996 1090 hotkey_overrides: crate::hotkeys::HotkeyOverrides::default(), 1091 + ..settings.clone() 997 1092 }); 998 1093 } 999 1094 next_overrides.map(|overrides| Settings { 1000 - pick_aperture: settings.pick_aperture, 1001 1095 hotkey_overrides: overrides, 1096 + ..settings.clone() 1002 1097 }) 1003 1098 } 1004 1099 ··· 3253 3348 &Settings::default(), 3254 3349 size, 3255 3350 None, 3351 + None, 3352 + &mut ViewUi::default(), 3256 3353 ) 3257 3354 } 3258 3355 ··· 3912 4009 &Settings::default(), 3913 4010 layout_size(1600.0, 900.0), 3914 4011 None, 4012 + None, 4013 + &mut ViewUi::default(), 3915 4014 ); 3916 4015 let any_smart_dim_label = frame.paints.iter().any(|p| { 3917 4016 matches!( ··· 4348 4447 &Settings::default(), 4349 4448 layout_size(1280.0, 800.0), 4350 4449 None, 4450 + None, 4451 + &mut ViewUi::default(), 4351 4452 ) 4352 4453 }; 4353 4454 *prev = bone_ui::hit_test::resolve(prev, &hits, snap, focus.focused()); ··· 4959 5060 &Settings::default(), 4960 5061 canvas, 4961 5062 None, 5063 + None, 5064 + &mut ViewUi::default(), 4962 5065 ); 4963 5066 } 4964 5067 (shell, a11y, focus)
+52
crates/bone-app/src/strings.rs
··· 14 14 pub const CONFIRM_ACCEPT: StringKey = StringKey::new("confirm.accept"); 15 15 pub const CONFIRM_CANCEL: StringKey = StringKey::new("confirm.cancel"); 16 16 17 + pub const VIEW_FRONT: StringKey = StringKey::new("view.front"); 18 + pub const VIEW_BACK: StringKey = StringKey::new("view.back"); 19 + pub const VIEW_LEFT: StringKey = StringKey::new("view.left"); 20 + pub const VIEW_RIGHT: StringKey = StringKey::new("view.right"); 21 + pub const VIEW_TOP: StringKey = StringKey::new("view.top"); 22 + pub const VIEW_BOTTOM: StringKey = StringKey::new("view.bottom"); 23 + pub const VIEW_ISOMETRIC: StringKey = StringKey::new("view.isometric"); 24 + pub const VIEW_NORMAL_TO: StringKey = StringKey::new("view.normal_to"); 25 + pub const VIEW_CUBE: StringKey = StringKey::new("view.cube"); 26 + pub const VIEW_HOME: StringKey = StringKey::new("view.home"); 27 + pub const VIEW_CUBE_EDGE: StringKey = StringKey::new("view.cube.edge"); 28 + pub const VIEW_CUBE_CORNER: StringKey = StringKey::new("view.cube.corner"); 29 + pub const VIEW_SELECTOR: StringKey = StringKey::new("view.selector"); 30 + pub const VIEW_CUBE_SET_HOME: StringKey = StringKey::new("view.cube.set_home"); 31 + pub const VIEW_CUBE_FIT: StringKey = StringKey::new("view.cube.fit"); 32 + pub const VIEW_CUBE_NORMAL_TO: StringKey = StringKey::new("view.cube.normal_to"); 33 + 17 34 pub const TOOL_POINT: StringKey = StringKey::new("tool.point"); 18 35 pub const TOOL_LINE: StringKey = StringKey::new("tool.line"); 19 36 pub const TOOL_CENTERPOINT_ARC: StringKey = StringKey::new("tool.centerpoint_arc"); ··· 162 179 pub const SETTINGS_DIALOG_TITLE: StringKey = StringKey::new("settings.dialog.title"); 163 180 pub const SETTINGS_PICK_APERTURE_LABEL: StringKey = StringKey::new("settings.pick_aperture.label"); 164 181 pub const SETTINGS_PICK_APERTURE_HINT: StringKey = StringKey::new("settings.pick_aperture.hint"); 182 + pub const SETTINGS_REDUCE_MOTION_LABEL: StringKey = StringKey::new("settings.reduce_motion.label"); 165 183 pub const SETTINGS_RESET: StringKey = StringKey::new("settings.reset"); 166 184 pub const SETTINGS_CLOSE: StringKey = StringKey::new("settings.close"); 167 185 pub const HOTKEY_SECTION_HEADING: StringKey = StringKey::new("hotkey.section.heading"); ··· 269 287 (RIBBON_GROUP_EXTRUDE, "Extrude"), 270 288 (CONFIRM_ACCEPT, "Accept"), 271 289 (CONFIRM_CANCEL, "Cancel"), 290 + (VIEW_FRONT, "Front"), 291 + (VIEW_BACK, "Back"), 292 + (VIEW_LEFT, "Left"), 293 + (VIEW_RIGHT, "Right"), 294 + (VIEW_TOP, "Top"), 295 + (VIEW_BOTTOM, "Bottom"), 296 + (VIEW_ISOMETRIC, "Isometric"), 297 + (VIEW_NORMAL_TO, "Normal To"), 298 + (VIEW_CUBE, "View Cube"), 299 + (VIEW_HOME, "Home"), 300 + (VIEW_CUBE_EDGE, "Edge View"), 301 + (VIEW_CUBE_CORNER, "Corner View"), 302 + (VIEW_SELECTOR, "View Orientation"), 303 + (VIEW_CUBE_SET_HOME, "Set as Home"), 304 + (VIEW_CUBE_FIT, "Fit to Window"), 305 + (VIEW_CUBE_NORMAL_TO, "View Normal To"), 272 306 (TOOL_POINT, "Point"), 273 307 (TOOL_LINE, "Line"), 274 308 (TOOL_CENTERPOINT_ARC, "Centerpoint Arc"), ··· 423 457 SETTINGS_PICK_APERTURE_HINT, 424 458 "How many pixels of slack the picker allows when clicking thin lines.", 425 459 ), 460 + (SETTINGS_REDUCE_MOTION_LABEL, "Reduce motion"), 426 461 (SETTINGS_RESET, "Reset"), 427 462 (SETTINGS_CLOSE, "Close"), 428 463 (HOTKEY_SECTION_HEADING, "Keyboard shortcuts"), ··· 518 553 (RIBBON_GROUP_EXTRUDE, "[!! Êxtrude !!]"), 519 554 (CONFIRM_ACCEPT, "[!! Accêpt !!]"), 520 555 (CONFIRM_CANCEL, "[!! Cancêl !!]"), 556 + (VIEW_FRONT, "[!! Frônt !!]"), 557 + (VIEW_BACK, "[!! Bâck !!]"), 558 + (VIEW_LEFT, "[!! Lêft !!]"), 559 + (VIEW_RIGHT, "[!! Rîght !!]"), 560 + (VIEW_TOP, "[!! Tôp !!]"), 561 + (VIEW_BOTTOM, "[!! Bôttom !!]"), 562 + (VIEW_ISOMETRIC, "[!! Isômetric !!]"), 563 + (VIEW_NORMAL_TO, "[!! Nôrmal Tô !!]"), 564 + (VIEW_CUBE, "[!! Vîew Cûbe !!]"), 565 + (VIEW_HOME, "[!! Hôme !!]"), 566 + (VIEW_CUBE_EDGE, "[!! Êdge Vîew !!]"), 567 + (VIEW_CUBE_CORNER, "[!! Côrner Vîew !!]"), 568 + (VIEW_SELECTOR, "[!! Vîew Orientâtion !!]"), 569 + (VIEW_CUBE_SET_HOME, "[!! Sêt as Hôme !!]"), 570 + (VIEW_CUBE_FIT, "[!! Fît to Wîndow !!]"), 571 + (VIEW_CUBE_NORMAL_TO, "[!! Vîew Nôrmal Tô !!]"), 521 572 (TOOL_POINT, "[!! Pôint !!]"), 522 573 (TOOL_LINE, "[!! Lîne !!]"), 523 574 (TOOL_CENTERPOINT_ARC, "[!! Cêntrepoint Arc !!]"), ··· 681 732 SETTINGS_PICK_APERTURE_HINT, 682 733 "[!! Hôw mâny pîxels ôf slâck thê pîcker âllows whên clîcking thîn lînes. !!]", 683 734 ), 735 + (SETTINGS_REDUCE_MOTION_LABEL, "[!! Redûce môtion !!]"), 684 736 (SETTINGS_RESET, "[!! Resêt !!]"), 685 737 (SETTINGS_CLOSE, "[!! Clôse !!]"), 686 738 (HOTKEY_SECTION_HEADING, "[!! Kêyboard shortcûts !!]"),