Another project
0

Configure Feed

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

app: more hotkeys

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

author
Lewis
date (Jun 13, 2026, 11:16 PM +0300) commit 25369b1f parent 9d5cf11a change-id ptryoool
+190 -19
+155 -3
crates/bone-app/src/hotkeys.rs
··· 1 1 use core::num::NonZeroU32; 2 2 use std::collections::BTreeMap; 3 3 4 + use bone_types::StandardView; 4 5 use bone_ui::hotkey::{ 5 6 ActionId, HotkeyBinding, HotkeyScope, HotkeyTable, HotkeyTableError, KeyChord, 6 7 }; ··· 34 35 pub const QUIT_ACTION: ActionId = action_id(17); 35 36 pub const IMPORT_STEP_ACTION: ActionId = action_id(18); 36 37 pub const EXPORT_STEP_ACTION: ActionId = action_id(19); 38 + pub const VIEW_FRONT_ACTION: ActionId = action_id(20); 39 + pub const VIEW_BACK_ACTION: ActionId = action_id(21); 40 + pub const VIEW_LEFT_ACTION: ActionId = action_id(22); 41 + pub const VIEW_RIGHT_ACTION: ActionId = action_id(23); 42 + pub const VIEW_TOP_ACTION: ActionId = action_id(24); 43 + pub const VIEW_BOTTOM_ACTION: ActionId = action_id(25); 44 + pub const VIEW_ISOMETRIC_ACTION: ActionId = action_id(26); 45 + pub const VIEW_NORMAL_TO_ACTION: ActionId = action_id(27); 46 + pub const VIEW_SELECTOR_ACTION: ActionId = action_id(28); 47 + pub const VIEW_CUBE_ACTION: ActionId = action_id(29); 37 48 38 49 const fn ch(c: char) -> KeyCode { 39 50 KeyCode::Char(KeyChar::from_ascii(c)) ··· 55 66 const CTRL_Q: KeyChord = KeyChord::new(ch('q'), ModifierMask::CTRL); 56 67 const CTRL_I: KeyChord = KeyChord::new(ch('i'), ModifierMask::CTRL); 57 68 const CTRL_E: KeyChord = KeyChord::new(ch('e'), ModifierMask::CTRL); 69 + const CTRL_1: KeyChord = KeyChord::new(ch('1'), ModifierMask::CTRL); 70 + const CTRL_2: KeyChord = KeyChord::new(ch('2'), ModifierMask::CTRL); 71 + const CTRL_3: KeyChord = KeyChord::new(ch('3'), ModifierMask::CTRL); 72 + const CTRL_4: KeyChord = KeyChord::new(ch('4'), ModifierMask::CTRL); 73 + const CTRL_5: KeyChord = KeyChord::new(ch('5'), ModifierMask::CTRL); 74 + const CTRL_6: KeyChord = KeyChord::new(ch('6'), ModifierMask::CTRL); 75 + const CTRL_7: KeyChord = KeyChord::new(ch('7'), ModifierMask::CTRL); 76 + const CTRL_8: KeyChord = KeyChord::new(ch('8'), ModifierMask::CTRL); 77 + const SPACE: KeyChord = KeyChord::new(named(NamedKey::Space), ModifierMask::NONE); 78 + const CTRL_SPACE: KeyChord = KeyChord::new(named(NamedKey::Space), ModifierMask::CTRL); 58 79 const DELETE: KeyChord = KeyChord::new(named(NamedKey::Delete), ModifierMask::NONE); 59 80 const F_KEY: KeyChord = KeyChord::new(ch('f'), ModifierMask::NONE); 60 81 const S_KEY: KeyChord = KeyChord::new(ch('s'), ModifierMask::NONE); ··· 79 100 Extend, 80 101 Mirror, 81 102 ToggleConstruction, 103 + StandardView(StandardView), 104 + ToggleViewSelector, 105 + ToggleViewCube, 82 106 } 83 107 84 108 #[derive(Copy, Clone, Debug)] ··· 88 112 pub scope: HotkeyScope, 89 113 pub label: StringKey, 90 114 pub defaults: &'static [KeyChord], 115 + } 116 + 117 + const fn view_command( 118 + action: ActionId, 119 + view: StandardView, 120 + label: StringKey, 121 + defaults: &'static [KeyChord], 122 + ) -> Command { 123 + Command { 124 + action, 125 + kind: Some(HotkeyCommand::StandardView(view)), 126 + scope: HotkeyScope::Global, 127 + label, 128 + defaults, 129 + } 91 130 } 92 131 93 132 pub const COMMANDS: &[Command] = &[ ··· 231 270 label: s::HOTKEY_LABEL_CONSTRUCTION_TOGGLE, 232 271 defaults: &[], 233 272 }, 273 + view_command( 274 + VIEW_FRONT_ACTION, 275 + StandardView::Front, 276 + s::VIEW_FRONT, 277 + &[CTRL_1], 278 + ), 279 + view_command( 280 + VIEW_BACK_ACTION, 281 + StandardView::Back, 282 + s::VIEW_BACK, 283 + &[CTRL_2], 284 + ), 285 + view_command( 286 + VIEW_LEFT_ACTION, 287 + StandardView::Left, 288 + s::VIEW_LEFT, 289 + &[CTRL_3], 290 + ), 291 + view_command( 292 + VIEW_RIGHT_ACTION, 293 + StandardView::Right, 294 + s::VIEW_RIGHT, 295 + &[CTRL_4], 296 + ), 297 + view_command(VIEW_TOP_ACTION, StandardView::Top, s::VIEW_TOP, &[CTRL_5]), 298 + view_command( 299 + VIEW_BOTTOM_ACTION, 300 + StandardView::Bottom, 301 + s::VIEW_BOTTOM, 302 + &[CTRL_6], 303 + ), 304 + view_command( 305 + VIEW_ISOMETRIC_ACTION, 306 + StandardView::Isometric, 307 + s::VIEW_ISOMETRIC, 308 + &[CTRL_7], 309 + ), 310 + view_command( 311 + VIEW_NORMAL_TO_ACTION, 312 + StandardView::NormalTo, 313 + s::VIEW_NORMAL_TO, 314 + &[CTRL_8], 315 + ), 316 + Command { 317 + action: VIEW_SELECTOR_ACTION, 318 + kind: Some(HotkeyCommand::ToggleViewSelector), 319 + scope: HotkeyScope::Global, 320 + label: s::VIEW_SELECTOR, 321 + defaults: &[SPACE], 322 + }, 323 + Command { 324 + action: VIEW_CUBE_ACTION, 325 + kind: Some(HotkeyCommand::ToggleViewCube), 326 + scope: HotkeyScope::Global, 327 + label: s::VIEW_CUBE, 328 + defaults: &[CTRL_SPACE], 329 + }, 234 330 ]; 235 331 236 332 #[must_use] ··· 360 456 EXTEND_ACTION, HotkeyOverrides, IMPORT_STEP_ACTION, MIRROR_ACTION, NEW_DOCUMENT_ACTION, 361 457 OPEN_DOCUMENT_ACTION, OPEN_SHORTCUT_BAR_ACTION, QUIT_ACTION, REDO_ACTION, 362 458 SAVE_DOCUMENT_ACTION, SELECT_ALL_ACTION, SMART_DIMENSION_ACTION, 363 - TOGGLE_CONSTRUCTION_ACTION, TRIM_ACTION, UNDO_ACTION, ZOOM_FIT_ACTION, compose_table, 364 - default_bindings, remap_entries, 459 + TOGGLE_CONSTRUCTION_ACTION, TRIM_ACTION, UNDO_ACTION, VIEW_BACK_ACTION, VIEW_BOTTOM_ACTION, 460 + VIEW_CUBE_ACTION, VIEW_FRONT_ACTION, VIEW_ISOMETRIC_ACTION, VIEW_LEFT_ACTION, 461 + VIEW_NORMAL_TO_ACTION, VIEW_RIGHT_ACTION, VIEW_SELECTOR_ACTION, VIEW_TOP_ACTION, 462 + ZOOM_FIT_ACTION, compose_table, default_bindings, remap_entries, 365 463 }; 366 - use bone_ui::hotkey::{HotkeyScope, HotkeyScopes, KeyChord}; 464 + use bone_ui::hotkey::{ActionId, HotkeyScope, HotkeyScopes, KeyChord}; 367 465 use bone_ui::input::{KeyChar, KeyCode, ModifierMask, NamedKey}; 368 466 369 467 fn ch(c: char) -> KeyCode { ··· 449 547 QUIT_ACTION, 450 548 IMPORT_STEP_ACTION, 451 549 EXPORT_STEP_ACTION, 550 + VIEW_FRONT_ACTION, 551 + VIEW_BACK_ACTION, 552 + VIEW_LEFT_ACTION, 553 + VIEW_RIGHT_ACTION, 554 + VIEW_TOP_ACTION, 555 + VIEW_BOTTOM_ACTION, 556 + VIEW_ISOMETRIC_ACTION, 557 + VIEW_NORMAL_TO_ACTION, 558 + VIEW_SELECTOR_ACTION, 559 + VIEW_CUBE_ACTION, 452 560 ] 453 561 .iter() 454 562 .for_each(|a| assert!(actions.contains(a), "missing remappable: {a:?}")); 563 + } 564 + 565 + #[test] 566 + fn ctrl_digits_dispatch_standard_views() { 567 + let Ok(table) = compose_table(&HotkeyOverrides::default()) else { 568 + panic!("defaults must compose"); 569 + }; 570 + let expected: [(char, ActionId); 8] = [ 571 + ('1', VIEW_FRONT_ACTION), 572 + ('2', VIEW_BACK_ACTION), 573 + ('3', VIEW_LEFT_ACTION), 574 + ('4', VIEW_RIGHT_ACTION), 575 + ('5', VIEW_TOP_ACTION), 576 + ('6', VIEW_BOTTOM_ACTION), 577 + ('7', VIEW_ISOMETRIC_ACTION), 578 + ('8', VIEW_NORMAL_TO_ACTION), 579 + ]; 580 + expected.iter().for_each(|(digit, action)| { 581 + let chord = KeyChord::new(ch(*digit), ModifierMask::CTRL); 582 + assert_eq!(table.dispatch(chord, &scopes()), Some(*action)); 583 + }); 584 + } 585 + 586 + #[test] 587 + fn space_chords_dispatch_view_surfaces() { 588 + let Ok(table) = compose_table(&HotkeyOverrides::default()) else { 589 + panic!("defaults must compose"); 590 + }; 591 + let space = KeyChord::new(KeyCode::Named(NamedKey::Space), ModifierMask::NONE); 592 + let ctrl_space = KeyChord::new(KeyCode::Named(NamedKey::Space), ModifierMask::CTRL); 593 + assert_eq!(table.dispatch(space, &scopes()), Some(VIEW_SELECTOR_ACTION)); 594 + assert_eq!( 595 + table.dispatch(ctrl_space, &scopes()), 596 + Some(VIEW_CUBE_ACTION) 597 + ); 598 + } 599 + 600 + #[test] 601 + fn ctrl_e_dispatches_export_inside_sketch_scope() { 602 + let Ok(table) = compose_table(&HotkeyOverrides::default()) else { 603 + panic!("defaults must compose"); 604 + }; 605 + let ctrl_e = KeyChord::new(ch('e'), ModifierMask::CTRL); 606 + assert_eq!(table.dispatch(ctrl_e, &scopes()), Some(EXPORT_STEP_ACTION)); 455 607 } 456 608 457 609 #[test]
+25 -16
crates/bone-app/src/main.rs
··· 1701 1701 let physical_named = physical_code.and_then(keycode_to_named); 1702 1702 let named = logical_named.or(physical_named); 1703 1703 let mods = self.input.modifier_mask(); 1704 - if named == Some(NamedKey::Space) && view_nav_enabled(state) { 1705 - if repeat { 1706 - return; 1707 - } 1708 - if self.input.modifiers.control_key() || self.input.modifiers.super_key() { 1709 - state.view.toggle_cube(); 1710 - } else { 1711 - state.view.toggle_selector(); 1712 - } 1713 - return; 1714 - } 1704 + let repeat_space = repeat && named == Some(NamedKey::Space); 1715 1705 if let Some(named) = named { 1716 - self.input 1717 - .pending_keys 1718 - .push(UiKeyEvent::new(UiKeyCode::Named(named), mods)); 1706 + if !repeat_space { 1707 + self.input 1708 + .pending_keys 1709 + .push(UiKeyEvent::new(UiKeyCode::Named(named), mods)); 1710 + } 1719 1711 } else if let Some(c) = physical_code.and_then(keycode_to_char) { 1720 1712 self.input.pending_keys.push(UiKeyEvent::new( 1721 1713 UiKeyCode::Char(KeyChar::from_char(c)), ··· 1911 1903 let solid_region = solid_viewport_region(state.viewport_rect, extent); 1912 1904 sync_solid_camera(state, solid_region); 1913 1905 let cursor_layout = input_state.cursor_px.map(physical_to_layout_pos); 1914 - apply_hotkey_actions(state, &hotkey_actions, cursor_layout); 1906 + apply_hotkey_actions(state, &hotkey_actions, cursor_layout, input.frame); 1915 1907 apply_view_pick(state, frame.view_pick, input.frame); 1916 1908 apply_view_menu(state, frame.view_menu, input.frame); 1917 1909 apply_menu_action(state, frame.menu_action); ··· 2125 2117 state: &mut RenderState, 2126 2118 actions: &[ActionId], 2127 2119 cursor_layout: Option<LayoutPos>, 2120 + now: FrameInstant, 2128 2121 ) { 2129 2122 actions 2130 2123 .iter() 2131 2124 .filter_map(|a| hotkeys::command_for_action(*a)) 2132 - .for_each(|cmd| dispatch_hotkey_command(state, cmd, cursor_layout)); 2125 + .for_each(|cmd| dispatch_hotkey_command(state, cmd, cursor_layout, now)); 2133 2126 } 2134 2127 2135 2128 fn dispatch_hotkey_command( 2136 2129 state: &mut RenderState, 2137 2130 cmd: hotkeys::HotkeyCommand, 2138 2131 cursor_layout: Option<LayoutPos>, 2132 + now: FrameInstant, 2139 2133 ) { 2140 2134 use hotkeys::HotkeyCommand as C; 2141 2135 match cmd { ··· 2166 2160 } 2167 2161 C::ToggleConstruction => apply_construction_toggle(state), 2168 2162 C::Mirror => apply_mirror(state), 2163 + C::StandardView(view) => { 2164 + if view_nav_enabled(state) { 2165 + apply_view_pick(state, Some(view_cube::ViewPick::Standard(view)), now); 2166 + } 2167 + } 2168 + C::ToggleViewSelector => { 2169 + if view_nav_enabled(state) { 2170 + state.view.toggle_selector(); 2171 + } 2172 + } 2173 + C::ToggleViewCube => { 2174 + if view_nav_enabled(state) { 2175 + state.view.toggle_cube(); 2176 + } 2177 + } 2169 2178 C::SelectAll 2170 2179 | C::DeleteSelection 2171 2180 | C::EnterSketch
+10
crates/bone-app/src/snapshots/bone_app__hotkeys__tests__default_hotkey_table.snap
··· 15 15 action=18 chord=Ctrl+I scope=Global 16 16 action=19 chord=Ctrl+E scope=Global 17 17 action=2 chord=Ctrl+Z scope=Global 18 + action=20 chord=Ctrl+1 scope=Global 19 + action=21 chord=Ctrl+2 scope=Global 20 + action=22 chord=Ctrl+3 scope=Global 21 + action=23 chord=Ctrl+4 scope=Global 22 + action=24 chord=Ctrl+5 scope=Global 23 + action=25 chord=Ctrl+6 scope=Global 24 + action=26 chord=Ctrl+7 scope=Global 25 + action=27 chord=Ctrl+8 scope=Global 26 + action=28 chord=Space scope=Global 27 + action=29 chord=Ctrl+Space scope=Global 18 28 action=3 chord=Ctrl+Shift+Z scope=Global 19 29 action=3 chord=Ctrl+Y scope=Global