Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,22 @@ jobs:
components: rustfmt
- name: Run cargo fmt
run: cargo fmt --all -- --check

# Run wasm compile checks to catch web-only type mismatches.
wasm_check:
name: Wasm Compile
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
- name: Check wasm target
run: rustup target add wasm32-unknown-unknown
- name: Run wasm compile checks
run: |
cargo check -p vizmat-core --target wasm32-unknown-unknown --no-default-features
cargo check -p vizmat-app --target wasm32-unknown-unknown --no-default-features
5 changes: 5 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ jobs:
run: |
curl https://drager.github.io/wasm-pack/installer/init.sh -sSf | bash

- name: Run wasm compile checks
run: |
cargo check -p vizmat-core --target wasm32-unknown-unknown --no-default-features
cargo check -p vizmat-app --target wasm32-unknown-unknown --no-default-features

- name: Build WASM
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
Expand Down
5 changes: 5 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ watch:
bench:
cargo bench -p vizmat-core --bench bond_cache

wasm-check:
rustup target add wasm32-unknown-unknown
cargo check -p vizmat-core --target wasm32-unknown-unknown --no-default-features
cargo check -p vizmat-app --target wasm32-unknown-unknown --no-default-features

wasm:
rustup target add wasm32-unknown-unknown --toolchain nightly-aarch64-apple-darwin
cd vizmat-app && PATH="$HOME/.cargo/bin:$PATH" NO_COLOR=false trunk serve --port 8082
Expand Down
11 changes: 11 additions & 0 deletions vizmat-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,16 @@ <h1 class="site-title">vizmat <span>molecule / crystal</span> visual lab</h1>
<div class="canvas-shell">
<canvas id="bevy-canvas"></canvas>
</div>
<input
id="picker-keyboard-input"
type="text"
inputmode="search"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
enterkeyhint="search"
aria-label="Structure search"
>
</body>
</html>
2 changes: 1 addition & 1 deletion vizmat-app/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function () {
const canvas = document.getElementById("bevy-canvas");
const loader = document.getElementById("app-loader");
const status = document.getElementById("loader-status");
const canvas = document.getElementById("bevy-canvas");
if (!loader) return;

if (canvas) {
Expand Down
40 changes: 40 additions & 0 deletions vizmat-app/mobile-gesture.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
(function () {
const canvas = document.getElementById("bevy-canvas");
const pickerInput = document.getElementById("picker-keyboard-input");

const emitPickerQuery = (action) => {
if (!pickerInput) return;
const query = pickerInput.value || "";
window.dispatchEvent(
new CustomEvent("vizmat-picker-query", {
detail: {
query,
action,
},
}),
);
};

if (pickerInput) {
window.addEventListener("vizmat-structure-picker-open", () => {
pickerInput.value = "";
try {
pickerInput.focus({ preventScroll: true });
} catch (error) {
pickerInput.focus();
}
emitPickerQuery("change");
});

window.addEventListener("vizmat-structure-picker-close", () => {
pickerInput.blur();
pickerInput.value = "";
});

pickerInput.addEventListener("input", () => emitPickerQuery("change"));
pickerInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
emitPickerQuery("submit");
event.stopPropagation();
}
});
}

const emitTouchGesture = (gesture) => {
window.dispatchEvent(
Expand Down
17 changes: 17 additions & 0 deletions vizmat-app/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,23 @@ canvas {
user-select: none;
-webkit-user-select: none;
}
#picker-keyboard-input {
position: fixed;
left: 0;
top: 0;
width: 1px;
height: 1px;
opacity: 0;
padding: 0;
margin: 0;
border: 0;
outline: 0;
background: transparent;
color: transparent;
caret-color: transparent;
z-index: 9999;
pointer-events: none;
}
.loader {
position: fixed;
inset: 0;
Expand Down
107 changes: 95 additions & 12 deletions vizmat-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,22 @@ use crate::structure::{
Crystal, UpdateStructure,
};
use crate::ui::{
apply_bond_tolerance_debounce, apply_theme_to_atom_hover_panel, apply_theme_to_hud,
apply_theme_to_startup_screen, auto_reset_view_on_crystal_change, bond_tolerance_controls,
camera_controls, cleanup_startup_screen, color_mode_button, handle_catalog_load_results,
handle_load_default_button, handle_open_file_button, hide_non_startup_controls,
refresh_structure_picker_panel, reset_camera_button_interaction, setup_cameras, setup_file_ui,
setup_light, setup_startup_screen, show_non_startup_controls, structure_picker_keyboard_search,
structure_picker_result_buttons, structure_picker_toggle_button, sync_atom_selection_highlight,
sync_color_mode_label, sync_gizmo_axis_rotation, toggle_light_attachment, toggle_theme_button,
transition_to_running_on_structure_loaded, update_atom_hover_cache, update_atom_hover_label,
update_bond_order_legend, update_color_mode_availability, update_file_ui,
update_gizmo_viewport, update_scene, update_selected_atom_from_click,
update_structure_loading_overlay, AppUiState, CatalogLoadChannel, TouchGestureState,
apply_bond_tolerance_debounce, apply_structure_picker_query_text,
apply_theme_to_atom_hover_panel, apply_theme_to_hud, apply_theme_to_startup_screen,
auto_reset_view_on_crystal_change, blink_structure_picker_query_caret, bond_tolerance_controls,
camera_controls, cleanup_startup_screen, color_mode_button, filtered_structure_entries,
handle_catalog_load_results, handle_load_default_button, handle_open_file_button,
hide_non_startup_controls, refresh_structure_picker_panel, reset_camera_button_interaction,
set_structure_picker_keyboard_active, setup_cameras, setup_file_ui, setup_light,
setup_startup_screen, show_non_startup_controls, structure_picker_keyboard_search,
structure_picker_result_buttons, structure_picker_scroll, structure_picker_toggle_button,
sync_atom_selection_highlight, sync_color_mode_label, sync_gizmo_axis_rotation,
toggle_light_attachment, toggle_theme_button, transition_to_running_on_structure_loaded,
update_atom_hover_cache, update_atom_hover_label, update_bond_order_legend,
update_color_mode_availability, update_file_ui, update_gizmo_viewport, update_scene,
update_selected_atom_from_click, update_structure_loading_overlay,
update_structure_picker_scroll_indicator, AppUiState, CatalogLoadChannel,
StructurePickerCaretState, StructurePickerState, TouchGestureState,
};
use crate::ui::{setup_buttons, spawn_axis};

Expand Down Expand Up @@ -89,6 +93,10 @@ pub enum WebEvent {
dy: f32,
scale_delta: f32,
},
StructurePickerQuery {
query: String,
submit: bool,
},
}

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -133,6 +141,13 @@ struct TouchGesturePayload {
scale_delta: f32,
}

#[allow(dead_code)]
#[derive(serde::Deserialize)]
struct StructurePickerQueryPayload {
query: String,
action: String,
}

pub struct WebPlugin {
#[cfg_attr(not(target_family = "wasm"), allow(dead_code))]
pub dom_drop_element_id: String,
Expand Down Expand Up @@ -174,6 +189,7 @@ impl Plugin for WebPlugin {
set_sender(sender);
register_drop(&self.dom_drop_element_id).unwrap();
register_touch_gesture_listener().unwrap();
register_structure_picker_query_listener().unwrap();
}
}
}
Expand Down Expand Up @@ -378,6 +394,44 @@ fn register_touch_gesture_listener() -> Option<()> {
Some(())
}

#[cfg(target_arch = "wasm32")]
fn register_structure_picker_query_listener() -> Option<()> {
let document = gloo::utils::document();
let window = document.default_view()?;

EventListener::new_with_options(
&window,
"vizmat-picker-query",
EventListenerOptions::enable_prevent_default(),
move |event| {
let event: CustomEvent = match event.clone().dyn_into() {
Ok(event) => event,
Err(err) => {
warn!("Ignoring invalid structure-picker-query event: {err:?}");
return;
}
};

let payload: StructurePickerQueryPayload = match event.detail().into_serde() {
Ok(payload) => payload,
Err(err) => {
warn!("Ignoring structure-picker-query payload parse failure: {err}");
return;
}
};

let submit = payload.action.eq_ignore_ascii_case("submit");
send_event(WebEvent::StructurePickerQuery {
query: payload.query,
submit,
});
},
)
.forget();

Some(())
}

/// Shared function for Bevy app setup
pub fn run_app() {
App::new()
Expand Down Expand Up @@ -457,10 +511,13 @@ pub fn run_app() {
)
.add_systems(Update, structure_picker_toggle_button)
.add_systems(Update, structure_picker_keyboard_search)
.add_systems(Update, blink_structure_picker_query_caret)
.add_systems(
Update,
refresh_structure_picker_panel.after(structure_picker_keyboard_search),
)
.add_systems(Update, update_structure_picker_scroll_indicator)
.add_systems(Update, structure_picker_scroll)
.add_systems(Update, structure_picker_result_buttons)
.add_systems(
Update,
Expand Down Expand Up @@ -529,12 +586,16 @@ pub fn run_app() {
.run();
}

#[allow(clippy::too_many_arguments)]
fn web_event_observer(
trigger: Trigger<WebEvent>,
mut file_drag_drop: ResMut<FileDragDrop>,
mut next_ui_state: ResMut<NextState<AppUiState>>,
mut commands: Commands,
mut touch_gesture_state: ResMut<TouchGestureState>,
mut picker: ResMut<StructurePickerState>,
mut picker_caret_state: ResMut<StructurePickerCaretState>,
catalog_channel: Option<Res<CatalogLoadChannel>>,
) {
match trigger.event() {
WebEvent::Drop {
Expand Down Expand Up @@ -591,6 +652,28 @@ fn web_event_observer(
touch_gesture_state.zoom += *scale_delta;
}
},
WebEvent::StructurePickerQuery { query, submit } => {
apply_structure_picker_query_text(&mut picker, &mut picker_caret_state, query.clone());

if !picker.visible {
picker.visible = true;
set_structure_picker_keyboard_active(true);
}

if !submit {
return;
}

if let Some(first) = filtered_structure_entries(&picker).first().cloned() {
crate::ui::load_structure_from_catalog_path(
&first,
&mut file_drag_drop,
catalog_channel.as_deref(),
);
picker.visible = false;
set_structure_picker_keyboard_active(false);
}
}
}
}

Expand Down
Loading