Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance I/O feedback on GUI #31

Merged
merged 5 commits into from
Aug 2, 2023
Merged
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
991 changes: 599 additions & 392 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ itertools = "0.10.5"
fastrand = "1.8.0"
serde = "1.0.147"
serde_json = "1.0.88"
proc-macro2 = "1.0.43"

# dependencies exclusive for wasm32
[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
15 changes: 13 additions & 2 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::aesthetics;
use crate::escher::EscherMap;
use crate::geom;
use crate::geom::{AesFilter, GeomHist, HistPlot};
use crate::info::Info;
use bevy::asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy::ecs::query::ReadOnlyWorldQuery;
use bevy::prelude::*;
Expand Down Expand Up @@ -152,22 +153,31 @@ struct GgPair<'a, Aes, Geom> {
fn load_data(
mut commands: Commands,
mut state: ResMut<ReactionState>,
mut info_state: ResMut<Info>,
mut custom_assets: ResMut<Assets<Data>>,
asset_server: Res<AssetServer>,
current_sizes: Query<Entity, (With<aesthetics::Gsize>, With<geom::GeomArrow>)>,
current_colors: Query<Entity, (With<aesthetics::Gcolor>, With<geom::GeomArrow>)>,
current_hist: Query<(Entity, &AesFilter), Or<(With<GeomHist>, With<geom::HistTag>)>>,
current_met_sizes: Query<Entity, (With<aesthetics::Gsize>, With<geom::GeomMetabolite>)>,
current_met_colors: Query<Entity, (With<aesthetics::Gcolor>, With<geom::GeomMetabolite>)>,
) {
let custom_asset = if let Some(reac_handle) = &mut state.reaction_data {
if asset_server.get_load_state(&*reac_handle) == bevy::asset::LoadState::Failed {
info_state
.notify("Failed loading data! Check if your metabolism.json is in correct format.");
state.reaction_data = None;
return;
}
custom_assets.get_mut(reac_handle)
} else {
return;
};
if state.reac_loaded || custom_asset.is_none() {
return;
}
info!("Loading Reaction data!");
info_state.notify("Loading data...");

let data = custom_asset.unwrap();
let conditions = data
.conditions
Expand Down Expand Up @@ -300,7 +310,7 @@ fn load_data(
}
}

info!("Loading Metabolite data!");
info_state.notify("Loading Metabolite data!");
let conditions = data
.met_conditions
.clone()
Expand Down Expand Up @@ -386,6 +396,7 @@ fn load_data(

state.met_loaded = true;
state.reac_loaded = true;
info_state.close()
}

fn insert_geom_map<F, Aes: Component, Geom: Component>(
Expand Down
13 changes: 11 additions & 2 deletions src/escher.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Data model of escher JSON maps
//! TODO: borrow strings
use crate::geom::{GeomHist, HistTag, Side, Xaxis};
use crate::info::Info;
use crate::scale::DefaultFontSize;
use bevy::{prelude::*, reflect::TypeUuid};
use bevy_prototype_lyon::prelude::*;
Expand Down Expand Up @@ -303,20 +304,28 @@ pub struct Hover {
pub fn load_map(
mut commands: Commands,
mut state: ResMut<MapState>,
mut info_state: ResMut<Info>,
mut node_to_text: ResMut<NodeToText>,
asset_server: Res<AssetServer>,
mut custom_assets: ResMut<Assets<EscherMap>>,
existing_map: Query<Entity, Or<(With<CircleTag>, With<ArrowTag>, With<HistTag>, With<Xaxis>)>>,
mut existing_geom_hist: Query<&mut GeomHist>,
) {
let custom_asset = custom_assets.get_mut(&state.escher_map);
if (asset_server.get_load_state(&state.escher_map) == bevy::asset::LoadState::Failed)
& !state.loaded
{
info_state.notify("Failed loading map! Check that you JSON is correct.");
state.loaded = true;
return;
}
if state.loaded || custom_asset.is_none() {
return;
}
let node_to_text = &mut node_to_text.inner;

// previous arrows and circles are despawned.
// HistTags has to be despawned too because they are spawned when painted,
// HistTags has to be despawned too because they are spawned when painted
// but they will be repainted at the end of loading the amp
for e in existing_map.iter() {
commands.entity(e).despawn_recursive();
Expand Down Expand Up @@ -455,6 +464,6 @@ pub fn load_map(
geom.rendered = false;
geom.in_axis = false;
}
info!("Map loaded!");
info_state.close();
state.loaded = true;
}
22 changes: 18 additions & 4 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::data::{Data, ReactionState};
use crate::escher::{ArrowTag, EscherMap, Hover, MapState, NodeToText, ARROW_COLOR};
use crate::geom::{AnyTag, Drag, HistTag, VisCondition, Xaxis};
use crate::info::Info;
use bevy::prelude::*;
use bevy_egui::egui::color_picker::{color_edit_button_rgba, Alpha};
use bevy_egui::egui::epaint::color::Rgba;
Expand Down Expand Up @@ -38,7 +39,8 @@ impl Plugin for GuiPlugin {
#[cfg(target_arch = "wasm32")]
building
.add_system(listen_js_escher)
.add_system(listen_js_data);
.add_system(listen_js_data)
.add_system(listen_js_info);
}
}
const HIGH_COLOR: Color = Color::rgb(183. / 255., 210. / 255., 255.);
Expand Down Expand Up @@ -267,6 +269,7 @@ fn ui_settings(
/// Open `.metabolism.json` and `.reactions.json` files when dropped on the window.
pub fn file_drop(
mut dnd_evr: EventReader<FileDragAndDrop>,
mut info_state: ResMut<Info>,
asset_server: Res<AssetServer>,
mut reaction_resource: ResMut<ReactionState>,
mut escher_resource: ResMut<MapState>,
Expand All @@ -280,13 +283,14 @@ pub fn file_drop(
reaction_resource.reaction_data = Some(reaction_handle);
reaction_resource.reac_loaded = false;
reaction_resource.met_loaded = false;
info! {"Reactions dropped!"};
info_state.notify("Loading data...");
} else {
//an escher map
let escher_handle: Handle<EscherMap> =
asset_server.load(path_buf.to_str().unwrap());
escher_resource.escher_map = escher_handle;
escher_resource.loaded = false;
info_state.notify("Loading map...");
}
}
}
Expand Down Expand Up @@ -569,6 +573,7 @@ fn show_axes(
/// Save map to arbitrary place, including (non-hover) hist transforms.
fn save_file(
mut assets: ResMut<Assets<EscherMap>>,
mut info_state: ResMut<Info>,
state: ResMut<MapState>,
mut save_events: EventReader<SaveEvent>,
hist_query: Query<(&Transform, &Xaxis), Without<AnyTag>>,
Expand All @@ -586,8 +591,10 @@ fn save_file(
.insert(axis.side.clone(), (*trans).into());
}
}
safe_json_write(&save_event.0, escher_map)
.unwrap_or_else(|e| warn!("Could not write the file: {}.", e));
safe_json_write(&save_event.0, escher_map).unwrap_or_else(|e| {
warn!("Could not write the file: {}.", e);
info_state.notify("File could not be written!\nCheck that path exists.");
});
}
}

Expand Down Expand Up @@ -631,3 +638,10 @@ fn listen_js_data(
data_resource.met_loaded = false;
}
}

#[cfg(target_arch = "wasm32")]
fn listen_js_info(receiver: Res<ReceiverResource<&'static str>>, mut info_box: ResMut<Info>) {
if let Ok(msg) = receiver.rx.try_recv() {
info_box.notify(msg);
}
}
146 changes: 146 additions & 0 deletions src/info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! Information to show in the UI.
use crate::funcplot::lerp;
use std::time::Duration;

use bevy::prelude::*;

pub struct InfoPlugin;
impl Plugin for InfoPlugin {
fn build(&self, app: &mut App) {
let app = app
.insert_resource(Info {
msg: None,
timer: Timer::new(Duration::from_secs(3), TimerMode::Once),
})
.add_system(pop_infobox)
.add_system(display_information);

// display the info messages in different positions for native and WASM
#[cfg(not(target_arch = "wasm32"))]
app.add_startup_system(|commands: Commands| spawn_info_box(commands, 2.0, 1.0));

#[cfg(target_arch = "wasm32")]
app.add_startup_system(|commands: Commands| spawn_info_box(commands, 6.5, 0.5));
}
}

#[derive(Resource)]
/// Information about IO.
pub struct Info {
msg: Option<&'static str>,
timer: Timer,
}

impl Info {
/// Sends a message to be logged in the CLI and displayed in the GUI.
pub fn notify(&mut self, msg: &'static str) {
info!(msg);
self.msg = Some(msg);
self.timer.reset();
}
pub fn close(&mut self) {
self.msg = None;
}
pub fn displaying(&self) -> bool {
self.msg.is_some()
}
}

#[derive(Component)]
pub struct InfoBox;

/// Spawn the UI components to show I/O feedback to the user.
/// The top argument is the top of the screen in percent to allow for different
/// positioning on WASM (would collide with the buttons otherwise).
fn spawn_info_box(mut commands: Commands, top: f32, right: f32) {
commands
.spawn(NodeBundle {
style: Style {
position_type: PositionType::Absolute,
position: UiRect {
right: Val::Percent(right),
top: Val::Percent(top),
..Default::default()
},
padding: UiRect {
right: Val::Px(8.),
left: Val::Px(8.),
top: Val::Px(3.),
bottom: Val::Px(3.),
},
..Default::default()
},
focus_policy: bevy::ui::FocusPolicy::Block,
z_index: ZIndex::Global(10),
background_color: BackgroundColor(Color::DARK_GRAY),
..Default::default()
})
.insert(InfoBox)
.insert(Interaction::default())
.with_children(|p| {
p.spawn(TextBundle {
focus_policy: bevy::ui::FocusPolicy::Block,
z_index: ZIndex::Global(12),
..Default::default()
});
});
}

/// Show information about I/O in a popup.
fn display_information(
info_state: Res<Info>,
asset_server: Res<AssetServer>,
mut info_query: Query<&Children, With<InfoBox>>,
mut text_query: Query<&mut Text>,
) {
for child in info_query.single_mut().iter() {
if let Ok(mut info_box) = text_query.get_mut(*child) {
let font = asset_server.load("fonts/Assistant-Regular.ttf");
let msg = info_state.msg.unwrap_or_default();
*info_box = Text::from_section(
msg.to_string(),
TextStyle {
font: font.clone(),
font_size: 20.,
color: Color::hex("F49596").unwrap(),
},
);
}
}
}

/// Popup-like mouse interactions for the infobox.
fn pop_infobox(
time: Res<Time>,
mut info_state: ResMut<Info>,
mut hover_query: Query<(&mut Style, &Interaction, &mut BackgroundColor), With<InfoBox>>,
) {
if info_state.timer.tick(time.delta()).just_finished() {
info_state.close();
}

for (mut style, interaction, mut color) in hover_query.iter_mut() {
if !info_state.displaying() {
style.display = Display::None;
return;
}
style.display = Display::Flex;
match *interaction {
Interaction::Hovered => {
info_state.timer.reset();
info_state.timer.pause();
}
_ => {
info_state.timer.unpause();
}
}
// fade out
color.0.set_a(lerp(
info_state.timer.elapsed_secs(),
0.,
info_state.timer.duration().as_secs_f32(),
1.,
0.,
));
}
}
Loading
Loading