mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 03:33:48 +00:00
ifcviewer: preset-driven nav mouse bindings + a "Web" preset (desktop + web)
Make orbit/pan/select mouse bindings pure data owned by ViewportCore so both
hosts and every preset share one source of truth, and add a "Web" preset. This
rounds out the matrix: the desktop gains a web-style scheme and the web inherits
all presets, with no per-platform hardcoding.
- Core: NavBindings { orbit, pan, select button + modifier } + setNavPreset
("blender" default | "rhino" | "revit" | "web") + navBindings(). Select is
preset-driven too (was hardcoded LMB) so "web" moves it to RMB. web = orbit
LMB, pan MMB, select RMB (LMB drag orbits with no click/drag ambiguity; RMB
click-selects / drag-marquees). NavMod uses "Plain" not "None" (X11 #defines
None to 0L).
- Desktop ViewportWindow: applyNavPreset sources the core table (mapped to Qt);
marquee-arm / single-pick dispatch keys off select_button_. Default stays
blender → no behaviour change.
- Desktop config: AppSettings::NavPreset gains Web + navPresetName(); the
Settings dialog lists it. This also FIXES a pre-existing gap — the preset combo
was persisted but never applied (only WGPU_NAV_PRESET env worked). MainWindow
now applies the persisted preset at startup (env override still wins) and live
on navPresetChanged, so all four presets actually work from the dialog.
- Web main_web: classifyPress routes the pressed button through navBindings()
(orbit/pan/select), defaulting to the "web" preset; context menu already
suppressed so RMB is free.
Tests: setNavPreset table (Catch2, 123 total); web smoke select tests use RMB.
BonsaiViewer builds; 9/9 web smoke.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,44 @@ TEST_CASE("toggleXray flips the active state", "[camera][xray]") {
|
||||
REQUIRE_FALSE(core.xrayActive());
|
||||
}
|
||||
|
||||
TEST_CASE("setNavPreset maps names to the shared button bindings", "[camera][nav]") {
|
||||
MockHost host; ViewportCore core(&host);
|
||||
using B = ViewportCore::MouseBtn; using M = ViewportCore::NavMod;
|
||||
|
||||
// Default is blender: orbit MMB, pan Shift+MMB, select LMB.
|
||||
{
|
||||
const auto& b = core.navBindings();
|
||||
REQUIRE(b.orbit == B::Middle); REQUIRE(b.orbit_mod == M::Plain);
|
||||
REQUIRE(b.pan == B::Middle); REQUIRE(b.pan_mod == M::Shift);
|
||||
REQUIRE(b.select == B::Left); REQUIRE(b.select_mod == M::Plain);
|
||||
}
|
||||
SECTION("web: orbit LMB, pan MMB, select RMB") {
|
||||
core.setNavPreset("web");
|
||||
const auto& b = core.navBindings();
|
||||
REQUIRE(b.orbit == B::Left); REQUIRE(b.orbit_mod == M::Plain);
|
||||
REQUIRE(b.pan == B::Middle); REQUIRE(b.pan_mod == M::Plain);
|
||||
REQUIRE(b.select == B::Right); REQUIRE(b.select_mod == M::Plain);
|
||||
}
|
||||
SECTION("rhino: orbit RMB, pan Shift+RMB") {
|
||||
core.setNavPreset("rhino");
|
||||
const auto& b = core.navBindings();
|
||||
REQUIRE(b.orbit == B::Right); REQUIRE(b.pan == B::Right);
|
||||
REQUIRE(b.pan_mod == M::Shift); REQUIRE(b.select == B::Left);
|
||||
}
|
||||
SECTION("revit: orbit Shift+MMB, pan MMB") {
|
||||
core.setNavPreset("revit");
|
||||
const auto& b = core.navBindings();
|
||||
REQUIRE(b.orbit == B::Middle); REQUIRE(b.orbit_mod == M::Shift);
|
||||
REQUIRE(b.pan == B::Middle); REQUIRE(b.pan_mod == M::Plain);
|
||||
}
|
||||
SECTION("unknown name falls back to blender") {
|
||||
core.setNavPreset("web");
|
||||
core.setNavPreset("nonsense");
|
||||
const auto& b = core.navBindings();
|
||||
REQUIRE(b.orbit == B::Middle); REQUIRE(b.select == B::Left);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("hideSelected hides the selection; showAll restores", "[camera][visibility]") {
|
||||
MockHost host; ViewportCore core(&host);
|
||||
REQUIRE(core.hiddenCount() == 0);
|
||||
|
||||
Reference in New Issue
Block a user