mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 10:05:06 +03:00
Fix CB discarding changes if pressed Ctrl + Enter too quickly (#6875)
Now, quick typing in component browser and pressing "enter" should not cut off the last part typed. Fixes #6733 https://github.com/enso-org/enso/assets/3919101/3979ed5a-ba4e-4e25-93e6-672e731b7bd8 On this occasion, also fixed "go-to-dashboard" button and "Unsupported engine version" being over the full-screen visualization. Fixes #6722 # Important Notes I did a significant refactoring of Project View: 1. The huge `frp::extend` block was split into multiple `init` methods. 2. Remaining of the "Old searcher" were removed. 3. The "Edited" event from node's input is emitted only when in edit mode (it's consistent with other API terminology, and makes FRP for showing CB much simpler. The code was _mostly_ moved around, but the check is advised anyway, as there were small changes here and there.
This commit is contained in:
parent
ed3f9b306e
commit
2e39d7595f
@ -135,8 +135,8 @@ pub enum MatchKind {
|
||||
/// The entry's label to be displayed in the component browser was matched.
|
||||
#[default]
|
||||
Label,
|
||||
/// The code to be generated by the entry was matched.
|
||||
Code,
|
||||
/// The entry's name from the code was matched.
|
||||
Name,
|
||||
/// An alias of the entry was matched, contains the specific alias that was matched.
|
||||
Alias(ImString),
|
||||
}
|
||||
@ -204,7 +204,7 @@ impl ListEntry {
|
||||
fuzzly::find_best_subsequence(self.action.to_string(), pattern, metric)
|
||||
});
|
||||
self.match_info = match subsequence {
|
||||
Some(subsequence) => MatchInfo::Matches { subsequence, kind: MatchKind::Code },
|
||||
Some(subsequence) => MatchInfo::Matches { subsequence, kind: MatchKind::Label },
|
||||
None => MatchInfo::DoesNotMatch,
|
||||
};
|
||||
}
|
||||
@ -442,7 +442,7 @@ impl<'a> CategoryBuilder<'a> {
|
||||
let category = self.category_id;
|
||||
built_list.entries.borrow_mut().extend(iter.into_iter().map(|action| {
|
||||
let match_info =
|
||||
MatchInfo::Matches { subsequence: default(), kind: MatchKind::Code };
|
||||
MatchInfo::Matches { subsequence: default(), kind: MatchKind::Label };
|
||||
ListEntry { category, match_info, action }
|
||||
}));
|
||||
}
|
||||
@ -497,7 +497,7 @@ impl ListWithSearchResultBuilder {
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let match_info =
|
||||
MatchInfo::Matches { subsequence: default(), kind: MatchKind::Code };
|
||||
MatchInfo::Matches { subsequence: default(), kind: MatchKind::Label };
|
||||
let action = entry.action.clone_ref();
|
||||
let category = self.search_result_category;
|
||||
ListEntry { category, match_info, action }
|
||||
|
@ -176,36 +176,32 @@ impl Component {
|
||||
pub fn update_matching_info(&self, filter: Filter) {
|
||||
// Match the input pattern to the component label.
|
||||
let label = self.to_string();
|
||||
let label_matches = fuzzly::matches(&label, filter.pattern.clone_ref());
|
||||
let label_matches = fuzzly::matches(&label, filter.pattern.as_str());
|
||||
let label_subsequence = label_matches.and_option_from(|| {
|
||||
let metric = fuzzly::metric::default();
|
||||
fuzzly::find_best_subsequence(label, filter.pattern.clone_ref(), metric)
|
||||
fuzzly::find_best_subsequence(label, filter.pattern.as_str(), metric)
|
||||
});
|
||||
let label_match_info = label_subsequence
|
||||
.map(|subsequence| MatchInfo::Matches { subsequence, kind: MatchKind::Label });
|
||||
|
||||
// Match the input pattern to the code to be inserted.
|
||||
let in_module = QualifiedName::as_ref(&filter.module_name);
|
||||
let code = match &self.data {
|
||||
Data::FromDatabase { entry, .. } => entry.code_to_insert(true, in_module).to_string(),
|
||||
Data::Virtual { snippet } => snippet.code.to_string(),
|
||||
};
|
||||
let code_matches = fuzzly::matches(&code, filter.pattern.clone_ref());
|
||||
let code_subsequence = code_matches.and_option_from(|| {
|
||||
// Match the input pattern to the component name.
|
||||
let name = self.name();
|
||||
let name_matches = fuzzly::matches(name, filter.pattern.as_str());
|
||||
let name_subsequence = name_matches.and_option_from(|| {
|
||||
let metric = fuzzly::metric::default();
|
||||
fuzzly::find_best_subsequence(code, filter.pattern.clone_ref(), metric)
|
||||
fuzzly::find_best_subsequence(name, filter.pattern.as_str(), metric)
|
||||
});
|
||||
let code_match_info = code_subsequence.map(|subsequence| {
|
||||
let name_match_info = name_subsequence.map(|subsequence| {
|
||||
let subsequence = fuzzly::Subsequence { indices: Vec::new(), ..subsequence };
|
||||
MatchInfo::Matches { subsequence, kind: MatchKind::Code }
|
||||
MatchInfo::Matches { subsequence, kind: MatchKind::Name }
|
||||
});
|
||||
|
||||
// Match the input pattern to an entry's aliases and select the best alias match.
|
||||
let alias_matches = self.aliases().filter_map(|alias| {
|
||||
if fuzzly::matches(alias, filter.pattern.clone_ref()) {
|
||||
if fuzzly::matches(alias, filter.pattern.as_str()) {
|
||||
let metric = fuzzly::metric::default();
|
||||
let subsequence =
|
||||
fuzzly::find_best_subsequence(alias, filter.pattern.clone_ref(), metric);
|
||||
fuzzly::find_best_subsequence(alias, filter.pattern.as_str(), metric);
|
||||
subsequence.map(|subsequence| (subsequence, alias))
|
||||
} else {
|
||||
None
|
||||
@ -221,7 +217,7 @@ impl Component {
|
||||
});
|
||||
|
||||
// Select the best match of the available label-, code- and alias matches.
|
||||
let match_info_iter = [alias_match_info, code_match_info, label_match_info].into_iter();
|
||||
let match_info_iter = [alias_match_info, name_match_info, label_match_info].into_iter();
|
||||
let best_match_info = match_info_iter.flatten().max_by(|lhs, rhs| lhs.cmp(rhs));
|
||||
*self.match_info.borrow_mut() = best_match_info.unwrap_or(MatchInfo::DoesNotMatch);
|
||||
|
||||
|
@ -357,6 +357,7 @@ ensogl::define_endpoints! {
|
||||
Output {
|
||||
pointer_style (cursor::Style),
|
||||
width (f32),
|
||||
/// Changes done when nodes is in edit mode.
|
||||
expression_edit (ImString, Vec<Selection<Byte>>),
|
||||
|
||||
editing (bool),
|
||||
@ -477,9 +478,10 @@ impl Area {
|
||||
legit_edit <- frp.input.edit_expression.gate(&set_editing);
|
||||
model.edit_mode_label.select <+ legit_edit.map(|(range, _)| (range.start.into(), range.end.into()));
|
||||
model.edit_mode_label.insert <+ legit_edit._1();
|
||||
expression_changed_by_user <- model.edit_mode_label.content.gate(&set_editing);
|
||||
frp.output.source.expression_edit <+ model.edit_mode_label.selections.map2(
|
||||
&expression_changed_by_user,
|
||||
expression_edited <- model.edit_mode_label.content.gate(&set_editing);
|
||||
selections_edited <- model.edit_mode_label.selections.gate(&set_editing);
|
||||
frp.output.source.expression_edit <+ selections_edited.gate(&set_editing).map2(
|
||||
&model.edit_mode_label.content,
|
||||
f!([model](selection, full_content) {
|
||||
let full_content = full_content.into();
|
||||
let to_byte = |loc| text::Byte::from_in_context_snapped(&model.edit_mode_label, loc);
|
||||
@ -487,7 +489,7 @@ impl Area {
|
||||
(full_content, selections)
|
||||
})
|
||||
);
|
||||
frp.output.source.on_port_code_update <+ expression_changed_by_user.map(|e| {
|
||||
frp.output.source.on_port_code_update <+ expression_edited.map(|e| {
|
||||
// Treat edit mode update as a code modification at the span tree root.
|
||||
(default(), e.into())
|
||||
});
|
||||
|
@ -15,7 +15,6 @@ use crate::graph_editor::GraphEditor;
|
||||
use crate::graph_editor::NodeId;
|
||||
use crate::popup;
|
||||
use crate::project_list::ProjectList;
|
||||
use crate::searcher;
|
||||
|
||||
use enso_config::ARGS;
|
||||
use enso_frp as frp;
|
||||
@ -23,8 +22,9 @@ use ensogl::application;
|
||||
use ensogl::application::shortcut;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display;
|
||||
use ensogl::display::Scene;
|
||||
use ensogl::system::web;
|
||||
use ensogl::DEPRECATED_Animation;
|
||||
use ensogl::Animation;
|
||||
use ensogl_component::text;
|
||||
use ensogl_component::text::selection::Selection;
|
||||
use ensogl_hardcoded_theme::Theme;
|
||||
@ -85,10 +85,6 @@ ensogl::define_endpoints! {
|
||||
hide_project_list(),
|
||||
/// Close the searcher without taking any actions
|
||||
close_searcher(),
|
||||
/// Show the graph editor.
|
||||
show_graph_editor(),
|
||||
/// Hide the graph editor.
|
||||
hide_graph_editor(),
|
||||
/// Simulates a style toggle press event.
|
||||
toggle_style(),
|
||||
/// Toggles the visibility of private components in the component browser.
|
||||
@ -126,7 +122,6 @@ ensogl::define_endpoints! {
|
||||
adding_new_node (bool),
|
||||
old_expression_of_edited_node (Expression),
|
||||
editing_aborted (NodeId),
|
||||
editing_committed_old_searcher (NodeId, Option<searcher::entry::Id>),
|
||||
editing_committed (NodeId, Option<component_list_panel::grid::GroupEntryId>),
|
||||
project_list_shown (bool),
|
||||
code_editor_shown (bool),
|
||||
@ -226,31 +221,13 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update Searcher View - its visibility and position - when edited node changed.
|
||||
fn update_searcher_view(
|
||||
&self,
|
||||
searcher_parameters: Option<SearcherParams>,
|
||||
is_searcher_empty: bool,
|
||||
searcher_left_top_position: &DEPRECATED_Animation<Vector2<f32>>,
|
||||
) {
|
||||
match searcher_parameters {
|
||||
Some(SearcherParams { input, .. }) if !is_searcher_empty => {
|
||||
self.searcher.show();
|
||||
let new_position = self.searcher_anchor_next_to_node(input);
|
||||
searcher_left_top_position.set_target_value(new_position);
|
||||
}
|
||||
_ => {
|
||||
self.searcher.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_fullscreen_visualization(&self, node_id: NodeId) {
|
||||
let node = self.graph_editor.nodes().get_cloned_ref(&node_id);
|
||||
if let Some(node) = node {
|
||||
let visualization =
|
||||
node.view.model().visualization.fullscreen_visualization().clone_ref();
|
||||
self.display_object.remove_child(&*self.graph_editor);
|
||||
self.display_object.remove_child(&self.project_view_top_bar);
|
||||
self.display_object.add_child(&visualization);
|
||||
*self.fullscreen_vis.borrow_mut() = Some(visualization);
|
||||
}
|
||||
@ -260,6 +237,7 @@ impl Model {
|
||||
if let Some(visualization) = std::mem::take(&mut *self.fullscreen_vis.borrow_mut()) {
|
||||
self.display_object.remove_child(&visualization);
|
||||
self.display_object.add_child(&*self.graph_editor);
|
||||
self.display_object.add_child(&self.project_view_top_bar);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,14 +270,6 @@ impl Model {
|
||||
fn hide_project_list(&self) {
|
||||
self.display_object.remove_child(&*self.project_list);
|
||||
}
|
||||
|
||||
fn show_graph_editor(&self) {
|
||||
self.display_object.add_child(&*self.graph_editor);
|
||||
}
|
||||
|
||||
fn hide_graph_editor(&self) {
|
||||
self.display_object.remove_child(&*self.graph_editor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -368,32 +338,36 @@ impl View {
|
||||
_ => Theme::Light,
|
||||
};
|
||||
|
||||
let scene = app.display.default_scene.clone_ref();
|
||||
let scene = &app.display.default_scene;
|
||||
scene.begin_shader_initialization();
|
||||
let model = Model::new(app);
|
||||
let frp = Frp::new();
|
||||
let network = &frp.network;
|
||||
let searcher = &model.searcher.frp();
|
||||
let project_view_top_bar = &model.project_view_top_bar;
|
||||
let graph = &model.graph_editor.frp;
|
||||
let code_editor = &model.code_editor;
|
||||
let project_list = &model.project_list;
|
||||
let searcher_anchor = DEPRECATED_Animation::<Vector2<f32>>::new(network);
|
||||
|
||||
// FIXME[WD]: Think how to refactor it, as it needs to be done before model, as we do not
|
||||
// want shader recompilation. Model uses styles already.
|
||||
model.set_style(theme);
|
||||
let input_change_delay = frp::io::timer::Timeout::new(network);
|
||||
|
||||
Self { model, frp }
|
||||
.init_top_bar_frp(scene)
|
||||
.init_graph_editor_frp()
|
||||
.init_code_editor_frp()
|
||||
.init_searcher_position_frp(scene)
|
||||
.init_searcher_input_changes_frp()
|
||||
.init_opening_searcher_frp()
|
||||
.init_closing_searcher_frp()
|
||||
.init_open_projects_dialog_frp(scene)
|
||||
.init_style_toggle_frp()
|
||||
.init_fullscreen_visualization_frp()
|
||||
.init_debug_mode_frp()
|
||||
}
|
||||
|
||||
fn init_top_bar_frp(self, scene: &Scene) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let model = &self.model;
|
||||
let project_view_top_bar = &model.project_view_top_bar;
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
|
||||
eval_ frp.show_graph_editor(model.show_graph_editor());
|
||||
eval_ frp.hide_graph_editor(model.hide_graph_editor());
|
||||
|
||||
|
||||
// === Project View Top Bar ===
|
||||
|
||||
let window_control_buttons = &project_view_top_bar.window_control_buttons;
|
||||
eval_ window_control_buttons.close (model.on_close_clicked());
|
||||
eval_ window_control_buttons.fullscreen (model.on_fullscreen_clicked());
|
||||
@ -412,20 +386,56 @@ impl View {
|
||||
);
|
||||
project_view_top_bar_width <-
|
||||
project_view_top_bar_display_object.on_resized.map(|new_size| new_size.x);
|
||||
graph.graph_editor_top_bar_offset_x <+ project_view_top_bar_width;
|
||||
self.model.graph_editor.graph_editor_top_bar_offset_x <+ project_view_top_bar_width;
|
||||
}
|
||||
init.emit(());
|
||||
self
|
||||
}
|
||||
|
||||
fn init_graph_editor_frp(self) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let model = &self.model;
|
||||
let graph = &model.graph_editor;
|
||||
let searcher = &model.searcher;
|
||||
let documentation = &searcher.model().documentation;
|
||||
|
||||
// === Read-only mode ===
|
||||
frp::extend! { network
|
||||
// We block graph navigator if it interferes with other panels (searcher, documentation,
|
||||
// etc.)
|
||||
searcher_active <- searcher.is_hovered || documentation.frp.is_selected;
|
||||
disable_navigation <- searcher_active || frp.project_list_shown;
|
||||
graph.set_navigator_disabled <+ disable_navigation;
|
||||
|
||||
model.popup.set_label <+ graph.model.breadcrumbs.project_name_error;
|
||||
graph.set_read_only <+ frp.set_read_only;
|
||||
code_editor.set_read_only <+ frp.set_read_only;
|
||||
graph.set_debug_mode <+ frp.source.debug_mode;
|
||||
|
||||
frp.source.fullscreen_visualization_shown <+
|
||||
graph.output.visualization_fullscreen.is_some();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// === Searcher Position and Size ===
|
||||
fn init_code_editor_frp(self) -> Self {
|
||||
let _network = &self.frp.network;
|
||||
frp::extend! { _network
|
||||
self.model.code_editor.set_read_only <+ self.frp.set_read_only;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
let main_cam = app.display.default_scene.layers.main.camera();
|
||||
let searcher_cam = app.display.default_scene.layers.node_searcher.camera();
|
||||
fn init_searcher_position_frp(self, scene: &Scene) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let model = &self.model;
|
||||
let main_cam = scene.layers.main.camera();
|
||||
let searcher_cam = scene.layers.node_searcher.camera();
|
||||
let main_cam_frp = &main_cam.frp();
|
||||
let searcher = &self.model.searcher;
|
||||
let anchor = Animation::<Vector2<f32>>::new(network);
|
||||
|
||||
frp::extend! { network
|
||||
// We want to:
|
||||
// 1. Preserve the zoom factor of the searcher.
|
||||
// 2. Keep it directly below edited node at all times.
|
||||
@ -442,8 +452,10 @@ impl View {
|
||||
// x = 100 * 0.1 = 10 in searcher_cam-space. To compensate for that, we need to move
|
||||
// searcher (or rather searcher_cam) by 90 units, so that the node is at x = 100 both
|
||||
// in searcher_cam- and in main_cam-space.
|
||||
searcher_cam_pos <- all_with3
|
||||
(&main_cam_frp.position, &main_cam_frp.zoom, &searcher_anchor.value,
|
||||
searcher_cam_pos <- all_with3(
|
||||
&main_cam_frp.position,
|
||||
&main_cam_frp.zoom,
|
||||
&anchor.value,
|
||||
|&main_cam_pos, &zoom, &searcher_pos| {
|
||||
let preserve_zoom = (main_cam_pos * zoom).xy();
|
||||
let move_to_edited_node = searcher_pos * (1.0 - zoom);
|
||||
@ -451,137 +463,133 @@ impl View {
|
||||
});
|
||||
eval searcher_cam_pos ((pos) searcher_cam.set_xy(*pos));
|
||||
|
||||
// Compute positions. It should be done _before_ showing searcher (or we display it at
|
||||
// wrong position).
|
||||
input <- frp.searcher.filter_map(|s| Some(s.as_ref()?.input));
|
||||
let node_position_set = model.graph_editor.output.node_position_set.clone_ref();
|
||||
is_input_position_update <-
|
||||
node_position_set.map2(&input, |&(node_id, _), &input_id| node_id == input_id);
|
||||
input_position_changed <- is_input_position_update.on_true();
|
||||
set_anchor_to_node <- all(input, input_position_changed)._0();
|
||||
anchor.target <+ set_anchor_to_node.map(f!((&input) model.searcher_anchor_next_to_node(input)));
|
||||
anchor.skip <+ set_anchor_to_node.gate_not(&searcher.is_visible).constant(());
|
||||
let searcher_offset = &model.searcher.expression_input_position;
|
||||
position <- all_with(&anchor.value, searcher_offset, |anchor, pos| anchor - pos);
|
||||
eval position ((pos) model.searcher.set_xy(*pos));
|
||||
|
||||
// Showing searcher.
|
||||
searcher.show <+ frp.searcher.is_some().on_true().constant(());
|
||||
searcher.hide <+ frp.searcher.is_none().on_true().constant(());
|
||||
eval searcher.is_visible ([model](is_visible) {
|
||||
let is_attached = model.searcher.has_parent();
|
||||
if !is_attached && *is_visible {
|
||||
model.display_object.add_child(&model.searcher);
|
||||
} else if is_attached && !is_visible {
|
||||
model.display_object.remove_child(&model.searcher);
|
||||
match (is_attached, is_visible) {
|
||||
(false, true) => model.display_object.add_child(&model.searcher),
|
||||
(true, false) => model.display_object.remove_child(&model.searcher),
|
||||
_ => ()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// === Closing Searcher
|
||||
|
||||
frp.source.is_searcher_opened <+ frp.searcher.map(|s| s.is_some());
|
||||
last_searcher <- frp.searcher.filter_map(|&s| s);
|
||||
|
||||
finished_with_searcher <- graph.node_editing_finished.gate(&frp.is_searcher_opened);
|
||||
frp.source.searcher <+ frp.close_searcher.constant(None);
|
||||
frp.source.searcher <+ searcher.editing_committed.constant(None);
|
||||
frp.source.searcher <+ finished_with_searcher.constant(None);
|
||||
|
||||
aborted_in_searcher <- frp.close_searcher.map2(&last_searcher, |(), &s| s.input);
|
||||
frp.source.editing_aborted <+ aborted_in_searcher;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
let grid = &model.searcher.model().list.model().grid;
|
||||
fn init_opening_searcher_frp(self) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let graph = &self.model.graph_editor;
|
||||
|
||||
frp::extend! { network
|
||||
committed_in_browser <- grid.expression_accepted.map2(&last_searcher, |&entry, &s| (s.input, entry));
|
||||
frp.source.editing_committed <+ committed_in_browser;
|
||||
frp.source.editing_committed <+ finished_with_searcher.map(|id| (*id,None));
|
||||
}
|
||||
|
||||
let anchor = &searcher_anchor.value;
|
||||
frp::extend! { network
|
||||
committed_in_searcher_event <- searcher.editing_committed.constant(());
|
||||
aborted_in_searcher_event <- aborted_in_searcher.constant(());
|
||||
graph.stop_editing <+ any(&committed_in_searcher_event, &aborted_in_searcher_event);
|
||||
|
||||
|
||||
// === Editing ===
|
||||
|
||||
node_edited_by_user <- graph.node_being_edited.gate_not(&frp.adding_new_node);
|
||||
existing_node_edited <- graph.node_expression_edited.gate_not(&frp.is_searcher_opened);
|
||||
open_searcher <- existing_node_edited.map2(&node_edited_by_user,
|
||||
|(id, _, _), edited| edited.map_or(false, |edited| *id == edited)
|
||||
).on_true().debounce();
|
||||
cursor_position <- existing_node_edited.map2(
|
||||
&node_edited_by_user,
|
||||
|(node_id, _, selections), edited| {
|
||||
edited.map_or(None, |edited| {
|
||||
let position = || selections.last().map(|sel| sel.end).unwrap_or_default();
|
||||
(*node_id == edited).then(position)
|
||||
})
|
||||
}
|
||||
).filter_map(|pos| *pos);
|
||||
edited_node <- node_edited_by_user.filter_map(|node| *node);
|
||||
position_and_edited_node <- cursor_position.map2(&edited_node, |pos, id| (*pos, *id));
|
||||
prepare_params <- position_and_edited_node.sample(&open_searcher);
|
||||
frp.source.searcher <+ prepare_params.map(|(pos, node_id)| {
|
||||
Some(SearcherParams::new_for_edited_node(*node_id, *pos))
|
||||
});
|
||||
searcher_input_change_opt <- graph.node_expression_edited.map2(&frp.searcher,
|
||||
|(node_id, expr, selections), searcher| {
|
||||
let input_change = || (*node_id, expr.clone_ref(), selections.clone());
|
||||
(searcher.as_ref()?.input == *node_id).then(input_change)
|
||||
}
|
||||
);
|
||||
searcher_input_change <- searcher_input_change_opt.unwrap();
|
||||
input_change_delay.restart <+ searcher_input_change.constant(INPUT_CHANGE_DELAY_MS);
|
||||
update_searcher_input_on_commit <- frp.output.editing_committed.constant(());
|
||||
input_change_delay.cancel <+ update_searcher_input_on_commit;
|
||||
update_searcher_input <- any(&input_change_delay.on_expired, &update_searcher_input_on_commit);
|
||||
input_change_and_searcher <- all_with(&searcher_input_change, &frp.searcher,
|
||||
|c, s| (c.clone(), *s)
|
||||
);
|
||||
updated_input <- input_change_and_searcher.sample(&update_searcher_input);
|
||||
input_changed <- updated_input.filter_map(|((node_id, expr, selections), searcher)| {
|
||||
let input_change = || (expr.clone_ref(), selections.clone());
|
||||
(searcher.as_ref()?.input == *node_id).then(input_change)
|
||||
});
|
||||
frp.source.searcher_input_changed <+ input_changed;
|
||||
|
||||
// === Adding Node ===
|
||||
|
||||
node_added_by_user <- graph.node_added.filter(|(_, _, should_edit)| *should_edit);
|
||||
searcher_for_adding <- node_added_by_user.map(
|
||||
|&(node, src, _)| SearcherParams::new_for_new_node(node, src)
|
||||
);
|
||||
frp.source.adding_new_node <+ searcher_for_adding.to_true();
|
||||
new_node_edited <- graph.node_editing_started.gate(&frp.adding_new_node);
|
||||
frp.source.searcher <+ searcher_for_adding.sample(&new_node_edited).map(|&s| Some(s));
|
||||
frp.source.searcher <+ searcher_for_adding.sample(&new_node_edited).some();
|
||||
|
||||
adding_committed_new_searcher <- frp.editing_committed.map(|(id,_)| *id);
|
||||
adding_committed_old_searcher <- frp.editing_committed_old_searcher.map(|(id,_)| *id);
|
||||
adding_committed <- any(&adding_committed_new_searcher,&adding_committed_old_searcher).gate(&frp.adding_new_node);
|
||||
adding_aborted <- frp.editing_aborted.gate(&frp.adding_new_node);
|
||||
adding_finished <- any(adding_committed,adding_aborted);
|
||||
frp.source.adding_new_node <+ adding_finished.constant(false);
|
||||
frp.source.searcher <+ adding_finished.constant(None);
|
||||
|
||||
eval adding_committed ([graph](node) {
|
||||
graph.deselect_all_nodes();
|
||||
graph.select_node(node);
|
||||
edit_which_opens_searcher <-
|
||||
graph.node_expression_edited.gate_not(&frp.is_searcher_opened).debounce();
|
||||
frp.source.searcher <+ edit_which_opens_searcher.map(|(node_id, _, selections)| {
|
||||
let cursor_position = selections.last().map(|sel| sel.end).unwrap_or_default();
|
||||
Some(SearcherParams::new_for_edited_node(*node_id, cursor_position))
|
||||
});
|
||||
|
||||
|
||||
// === Searcher Position and Visibility ===
|
||||
|
||||
visibility_conditions <- all(&frp.searcher,&searcher.is_empty);
|
||||
_eval <- visibility_conditions.map2(&searcher.is_visible,
|
||||
f!([model,searcher_anchor]((searcher,is_searcher_empty),is_visible) {
|
||||
model.update_searcher_view(*searcher,*is_searcher_empty,&searcher_anchor);
|
||||
if !is_visible {
|
||||
// Do not animate
|
||||
searcher_anchor.skip();
|
||||
frp.source.is_searcher_opened <+ frp.searcher.map(|s| s.is_some());
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
_eval <- graph.output.node_position_set.map2(&frp.searcher,
|
||||
f!([searcher_anchor](&(node_id, position), &searcher) {
|
||||
if searcher.map_or(false, |s| s.input == node_id) {
|
||||
searcher_anchor.set_target_value(position);
|
||||
self
|
||||
}
|
||||
})
|
||||
|
||||
fn init_closing_searcher_frp(self) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let grid = &self.model.searcher.model().list.model().grid;
|
||||
let graph = &self.model.graph_editor;
|
||||
|
||||
frp::extend! { network
|
||||
last_searcher <- frp.searcher.filter_map(|&s| s);
|
||||
|
||||
node_editing_finished <- graph.node_editing_finished.gate(&frp.is_searcher_opened);
|
||||
committed_in_searcher <-
|
||||
grid.expression_accepted.map2(&last_searcher, |&entry, &s| (s.input, entry));
|
||||
aborted_in_searcher <- frp.close_searcher.map2(&last_searcher, |(), &s| s.input);
|
||||
frp.source.editing_committed <+ committed_in_searcher;
|
||||
frp.source.editing_committed <+ node_editing_finished.map(|id| (*id,None));
|
||||
frp.source.editing_aborted <+ aborted_in_searcher;
|
||||
|
||||
// Should be done before we update `searcher` and `adding_new_node` outputs.
|
||||
adding_committed <- committed_in_searcher.gate(&frp.adding_new_node);
|
||||
graph.deselect_all_nodes <+ adding_committed.constant(());
|
||||
graph.select_node <+ adding_committed._0();
|
||||
|
||||
node_editing_finished_event <- node_editing_finished.constant(());
|
||||
committed_in_searcher_event <- committed_in_searcher.constant(());
|
||||
aborted_in_searcher_event <- aborted_in_searcher.constant(());
|
||||
searcher_should_close <- any(
|
||||
node_editing_finished_event,
|
||||
committed_in_searcher_event,
|
||||
aborted_in_searcher_event
|
||||
);
|
||||
graph.stop_editing <+ any(&committed_in_searcher_event, &aborted_in_searcher_event);
|
||||
frp.source.searcher <+ searcher_should_close.constant(None);
|
||||
frp.source.adding_new_node <+ searcher_should_close.constant(false);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
cb_position <- all_with(anchor, &model.searcher.expression_input_position, |anchor, pos| anchor - pos);
|
||||
eval cb_position ((pos) model.searcher.set_xy(*pos));
|
||||
fn init_searcher_input_changes_frp(self) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let graph = &self.model.graph_editor;
|
||||
let input_change_delay = frp::io::timer::Timeout::new(network);
|
||||
|
||||
// === Project Dialog ===
|
||||
frp::extend! { network
|
||||
searcher_input_change_opt <- graph.node_expression_edited.map2(&frp.searcher,
|
||||
|(node_id, expr, selections), searcher| {
|
||||
let input_change = || (*node_id, expr.clone_ref(), selections.clone());
|
||||
(searcher.as_ref()?.input == *node_id).then(input_change)
|
||||
}
|
||||
);
|
||||
input_change <- searcher_input_change_opt.unwrap();
|
||||
input_change_delay.restart <+ input_change.constant(INPUT_CHANGE_DELAY_MS);
|
||||
update_input_on_commit <- frp.output.editing_committed.constant(());
|
||||
input_change_delay.cancel <+ update_input_on_commit;
|
||||
update_input <- any(&input_change_delay.on_expired, &update_input_on_commit);
|
||||
input_change_and_searcher <-
|
||||
all_with(&input_change, &frp.searcher, |c, s| (c.clone(), *s));
|
||||
updated_input <- input_change_and_searcher.sample(&update_input);
|
||||
input_changed <- updated_input.filter_map(|((node_id, expr, selections), searcher)| {
|
||||
let input_change = || (expr.clone_ref(), selections.clone());
|
||||
(searcher.as_ref()?.input == *node_id).then(input_change)
|
||||
});
|
||||
frp.source.searcher_input_changed <+ input_changed;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn init_open_projects_dialog_frp(self, scene: &Scene) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let model = &self.model;
|
||||
let project_list = &model.project_list;
|
||||
frp::extend! { network
|
||||
eval_ frp.show_project_list (model.show_project_list());
|
||||
project_chosen <- project_list.frp.selected_project.constant(());
|
||||
mouse_down <- scene.mouse.frp_deprecated.down.constant(());
|
||||
@ -589,10 +597,17 @@ impl View {
|
||||
should_be_closed <- any(frp.hide_project_list,project_chosen,clicked_on_bg);
|
||||
eval_ should_be_closed (model.hide_project_list());
|
||||
frp.source.project_list_shown <+ bool(&should_be_closed,&frp.show_project_list);
|
||||
frp.source.drop_files_enabled <+ frp.project_list_shown.map(|v| !v);
|
||||
}
|
||||
frp.source.drop_files_enabled.emit(true);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
// === Style toggle ===
|
||||
|
||||
fn init_style_toggle_frp(self) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let model = &self.model;
|
||||
frp::extend! {network
|
||||
let style_toggle_ev = frp.toggle_style.clone_ref();
|
||||
style_pressed <- style_toggle_ev.toggle() ;
|
||||
style_was_pressed <- style_pressed.previous();
|
||||
@ -603,10 +618,15 @@ impl View {
|
||||
});
|
||||
frp.source.style <+ style_press_on_off;
|
||||
eval frp.style ((style) model.set_style(style.clone()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
// === Fullscreen Visualization ===
|
||||
|
||||
fn init_fullscreen_visualization_frp(self) -> Self {
|
||||
let network = &self.frp.network;
|
||||
let model = &self.model;
|
||||
let graph = &self.model.graph_editor;
|
||||
frp::extend! { network
|
||||
// TODO[ao]: All DOM elements in visualizations are displayed below canvas, because
|
||||
// The mouse cursor must be displayed over them. But fullscreen visualization should
|
||||
// be displayed "above" nodes. The workaround is to hide whole graph editor except
|
||||
@ -621,35 +641,21 @@ impl View {
|
||||
model.hide_fullscreen_visualization()
|
||||
}
|
||||
});
|
||||
|
||||
// === Disabling Navigation ===
|
||||
|
||||
let documentation = &model.searcher.model().documentation;
|
||||
searcher_active <- searcher.is_hovered || documentation.frp.is_selected;
|
||||
disable_navigation <- searcher_active || frp.project_list_shown;
|
||||
graph.set_navigator_disabled <+ disable_navigation;
|
||||
|
||||
// === Disabling Dropping ===
|
||||
|
||||
frp.source.drop_files_enabled <+ init.constant(true);
|
||||
frp.source.drop_files_enabled <+ frp.project_list_shown.map(|v| !v);
|
||||
|
||||
// === Debug Mode ===
|
||||
|
||||
frp.source.debug_mode <+ bool(&frp.disable_debug_mode, &frp.enable_debug_mode);
|
||||
graph.set_debug_mode <+ frp.source.debug_mode;
|
||||
|
||||
model.debug_mode_popup.enabled <+ frp.enable_debug_mode;
|
||||
model.debug_mode_popup.disabled <+ frp.disable_debug_mode;
|
||||
|
||||
// === Error Pop-up ===
|
||||
|
||||
model.popup.set_label <+ model.graph_editor.model.breadcrumbs.project_name_error;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
init.emit(());
|
||||
fn init_debug_mode_frp(self) -> Self {
|
||||
let frp = &self.frp;
|
||||
let network = &frp.network;
|
||||
let popup = &self.model.debug_mode_popup;
|
||||
frp::extend! { network
|
||||
frp.source.debug_mode <+ bool(&frp.disable_debug_mode, &frp.enable_debug_mode);
|
||||
|
||||
Self { model, frp }
|
||||
popup.enabled <+ frp.enable_debug_mode;
|
||||
popup.disabled <+ frp.disable_debug_mode;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Graph Editor View.
|
||||
|
@ -84,9 +84,17 @@ impl Model {
|
||||
|
||||
fn init_project_view(&self) {
|
||||
if self.project_view.get().is_none() {
|
||||
let network = &self.frp.network;
|
||||
let view = self.app.new_view::<crate::project::View>();
|
||||
let project_list_frp = &view.project_list().frp;
|
||||
frp::extend! { network
|
||||
let status_bar = &self.status_bar;
|
||||
let display_object = &self.display_object;
|
||||
frp::new_bridge_network! { [network, view.network] project_bridge
|
||||
fs_vis_shown <- view.fullscreen_visualization_shown.on_true();
|
||||
fs_vis_hidden <- view.fullscreen_visualization_shown.on_false();
|
||||
eval fs_vis_shown ((_) status_bar.unset_parent());
|
||||
eval fs_vis_hidden ([display_object, status_bar](_) display_object.add_child(&status_bar));
|
||||
|
||||
self.frp.source.selected_project <+ project_list_frp.selected_project;
|
||||
}
|
||||
self.project_view.set(Some(view));
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! A vector with a cap for its size. If the vector is full, adding a new element will remove
|
||||
//! an old one.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user