feat(topola-egui): Allow using Shift+{arrow keys} to scroll viewport using keyboard

Fixes #188.
This commit is contained in:
Ellen Emilia Anna Zscheile 2025-05-03 21:05:20 +02:00
parent 7d59ffd5be
commit b5b14e6329
5 changed files with 83 additions and 36 deletions

View File

@ -81,12 +81,16 @@ impl Trigger {
} }
impl Switch { impl Switch {
pub fn toggle_widget(&self, _ctx: &egui::Context, ui: &mut egui::Ui, selected: &mut bool) { pub fn toggle_widget(&self, ui: &mut egui::Ui, selected: &mut bool) {
ui.toggle_value(selected, self.action.widget_text()); ui.toggle_value(selected, self.action.widget_text());
} }
pub fn checkbox(&self, ui: &mut egui::Ui, selected: &mut bool) {
ui.checkbox(selected, self.action.widget_text());
}
pub fn consume_key_enabled( pub fn consume_key_enabled(
&mut self, &self,
ctx: &egui::Context, ctx: &egui::Context,
_ui: &mut egui::Ui, _ui: &mut egui::Ui,
selected: &mut bool, selected: &mut bool,

View File

@ -166,6 +166,7 @@ pub struct ViewActions {
pub zoom_to_fit: Switch, pub zoom_to_fit: Switch,
pub show_ratsnest: Switch, pub show_ratsnest: Switch,
pub show_navmesh: Switch, pub show_navmesh: Switch,
pub show_topo_navmesh: Switch,
pub show_bboxes: Switch, pub show_bboxes: Switch,
pub show_origin_destination: Switch, pub show_origin_destination: Switch,
pub show_appearance_panel: Switch, pub show_appearance_panel: Switch,
@ -177,6 +178,8 @@ impl ViewActions {
zoom_to_fit: Action::new_keyless(tr.text("tr-menu-view-zoom-to-fit")).into_switch(), zoom_to_fit: Action::new_keyless(tr.text("tr-menu-view-zoom-to-fit")).into_switch(),
show_ratsnest: Action::new_keyless(tr.text("tr-menu-view-show-ratsnest")).into_switch(), show_ratsnest: Action::new_keyless(tr.text("tr-menu-view-show-ratsnest")).into_switch(),
show_navmesh: Action::new_keyless(tr.text("tr-menu-view-show-navmesh")).into_switch(), show_navmesh: Action::new_keyless(tr.text("tr-menu-view-show-navmesh")).into_switch(),
show_topo_navmesh: Action::new_keyless(tr.text("tr-menu-view-show-topo-navmesh"))
.into_switch(),
show_bboxes: Action::new_keyless(tr.text("tr-menu-view-show-bboxes")).into_switch(), show_bboxes: Action::new_keyless(tr.text("tr-menu-view-show-bboxes")).into_switch(),
show_origin_destination: Action::new_keyless( show_origin_destination: Action::new_keyless(
tr.text("tr-menu-view-show-origin-destination"), tr.text("tr-menu-view-show-origin-destination"),
@ -196,42 +199,30 @@ impl ViewActions {
viewport: &mut Viewport, viewport: &mut Viewport,
have_workspace: bool, have_workspace: bool,
) { ) {
ui.toggle_value( self.zoom_to_fit
&mut viewport.scheduled_zoom_to_fit, .toggle_widget(ui, &mut viewport.scheduled_zoom_to_fit);
tr.text("tr-menu-view-zoom-to-fit"),
);
ui.separator(); ui.separator();
ui.add_enabled_ui(have_workspace, |ui| { ui.add_enabled_ui(have_workspace, |ui| {
ui.checkbox( self.show_ratsnest.checkbox(ui, &mut menu_bar.show_ratsnest);
&mut menu_bar.show_ratsnest, self.show_navmesh.checkbox(ui, &mut menu_bar.show_navmesh);
tr.text("tr-menu-view-show-ratsnest"), self.show_topo_navmesh
); .checkbox(ui, &mut menu_bar.show_topo_navmesh);
ui.checkbox( self.show_bboxes.checkbox(ui, &mut menu_bar.show_bboxes);
&mut menu_bar.show_navmesh, self.show_origin_destination
tr.text("tr-menu-view-show-navmesh"), .checkbox(ui, &mut menu_bar.show_origin_destination);
);
ui.checkbox(
&mut menu_bar.show_topo_navmesh,
tr.text("tr-menu-view-show-topo-navmesh"),
);
ui.checkbox(
&mut menu_bar.show_bboxes,
tr.text("tr-menu-view-show-bboxes"),
);
ui.checkbox(
&mut menu_bar.show_origin_destination,
tr.text("tr-menu-view-show-origin-destination"),
);
}); });
ui.separator(); ui.separator();
self.show_appearance_panel
.checkbox(ui, &mut menu_bar.show_appearance_panel);
ui.checkbox( ui.separator();
&mut menu_bar.show_appearance_panel, ui.label(tr.text("tr-menu-view-kdb-scroll-delta-factor"));
tr.text("tr-menu-view-show-layer-manager"), ui.add(egui::widgets::Slider::new(
); &mut viewport.kbd_scroll_delta_factor,
1.0..=100.0,
));
} }
} }
@ -259,7 +250,7 @@ impl PlaceActions {
is_placing_via: &mut bool, is_placing_via: &mut bool,
) -> egui::InnerResponse<()> { ) -> egui::InnerResponse<()> {
ui.add_enabled_ui(have_workspace, |ui| { ui.add_enabled_ui(have_workspace, |ui| {
self.place_via.toggle_widget(ctx, ui, is_placing_via); self.place_via.toggle_widget(ui, is_placing_via);
}) })
} }
} }

View File

@ -18,8 +18,8 @@ use topola::{
}; };
use crate::{ use crate::{
config::Config, error_dialog::ErrorDialog, menu_bar::MenuBar, status_bar::StatusBar, actions::Actions, config::Config, error_dialog::ErrorDialog, menu_bar::MenuBar,
translator::Translator, viewport::Viewport, workspace::Workspace, status_bar::StatusBar, translator::Translator, viewport::Viewport, workspace::Workspace,
}; };
pub struct App { pub struct App {

View File

@ -27,10 +27,14 @@ use topola::{
router::navmesh::NavvertexIndex, router::navmesh::NavvertexIndex,
}; };
use crate::{config::Config, menu_bar::MenuBar, painter::Painter, workspace::Workspace}; use crate::{
actions::Actions, config::Config, menu_bar::MenuBar, painter::Painter, workspace::Workspace,
};
pub struct Viewport { pub struct Viewport {
pub transform: egui::emath::TSTransform, pub transform: egui::emath::TSTransform,
/// how much should a single arrow key press scroll
pub kbd_scroll_delta_factor: f32,
pub scheduled_zoom_to_fit: bool, pub scheduled_zoom_to_fit: bool,
} }
@ -38,6 +42,7 @@ impl Viewport {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01), transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01),
kbd_scroll_delta_factor: 5.0,
scheduled_zoom_to_fit: false, scheduled_zoom_to_fit: false,
} }
} }
@ -52,6 +57,7 @@ impl Viewport {
egui::CentralPanel::default() egui::CentralPanel::default()
.show(ctx, |ui| { .show(ctx, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| {
// TODO: only request re-render if anything changed
ui.ctx().request_repaint(); ui.ctx().request_repaint();
let (id, viewport_rect) = ui.allocate_space(ui.available_size()); let (id, viewport_rect) = ui.allocate_space(ui.available_size());
@ -68,7 +74,52 @@ impl Viewport {
self.transform.translation += self.transform.translation +=
latest_pos.to_vec2() * (old_scaling - self.transform.scaling); latest_pos.to_vec2() * (old_scaling - self.transform.scaling);
self.transform.translation += ctx.input(|i| i.smooth_scroll_delta);
// disable built-in behavior of arrow keys
if response.has_focus() {
response.ctx.memory_mut(|m| {
// we are only allowed to modify the focus lock filter if we have focus
m.set_focus_lock_filter(
id,
egui::EventFilter {
horizontal_arrows: true,
vertical_arrows: true,
..Default::default()
},
);
});
}
self.transform.translation += ctx.input_mut(|i| {
// handle scrolling
let mut scroll_delta = core::mem::take(&mut i.smooth_scroll_delta);
// arrow keys
let kbd_sdf = self.kbd_scroll_delta_factor;
let mut pressed = |key| {
i.consume_shortcut(&egui::KeyboardShortcut::new(
egui::Modifiers::SHIFT,
key,
))
};
use egui::Key;
scroll_delta.y += if pressed(Key::ArrowDown) {
kbd_sdf
} else if pressed(Key::ArrowUp) {
-kbd_sdf
} else {
0.0
};
scroll_delta.x += if pressed(Key::ArrowRight) {
kbd_sdf
} else if pressed(Key::ArrowLeft) {
-kbd_sdf
} else {
0.0
};
scroll_delta
});
let mut painter = Painter::new(ui, self.transform, menu_bar.show_bboxes); let mut painter = Painter::new(ui, self.transform, menu_bar.show_bboxes);

View File

@ -26,6 +26,7 @@ tr-menu-view-show-topo-navmesh = Show Topological Navmesh
tr-menu-view-show-bboxes = Show BBoxes tr-menu-view-show-bboxes = Show BBoxes
tr-menu-view-show-origin-destination = Show OriginDestination tr-menu-view-show-origin-destination = Show OriginDestination
tr-menu-view-show-layer-manager = Show Layer Manager tr-menu-view-show-layer-manager = Show Layer Manager
tr-menu-view-kdb-scroll-delta-factor = Keyboard scroll delta factor
tr-menu-view-frame-timestep = Frame Timestep tr-menu-view-frame-timestep = Frame Timestep
tr-menu-place = Place tr-menu-place = Place