mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 18:01:38 +03:00
Pending nodes (#7541)
Implement *pending* node status (#7435). Design reference: https://user-images.githubusercontent.com/3919101/257222729-838cfe84-7447-40a3-8aa1-5424de66b0b7.png Demo: [vokoscreenNG-2023-08-09_22-29-13.webm](https://github.com/enso-org/enso/assets/1047859/2906599d-920f-44df-9c0f-c617ebbd5ecc) # Important Notes - Reduce alpha value of node backgrounds, label widgets, and method widget icons while the engine reports "pending" state. - Use FRP and themes for method widgets. - Update node input area to use `define_endpoints_2`. - Also remove some code supporting the disused *profiling mode*.
This commit is contained in:
parent
ea6fe3bbef
commit
d15b3db0ac
@ -544,13 +544,14 @@ impl Handle {
|
||||
|
||||
/// Returns information about all the nodes currently present in this graph.
|
||||
pub fn nodes(&self) -> FallibleResult<Vec<Node>> {
|
||||
let node_infos = self.all_node_infos()?;
|
||||
let mut nodes = Vec::new();
|
||||
for info in node_infos {
|
||||
let metadata = self.module.node_metadata(info.id()).ok();
|
||||
nodes.push(Node { info, metadata })
|
||||
}
|
||||
Ok(nodes)
|
||||
Ok(self
|
||||
.all_node_infos()?
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let metadata = self.module.node_metadata(info.id()).ok();
|
||||
Node { info, metadata }
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns information about all the connections between graph's nodes.
|
||||
|
@ -65,14 +65,12 @@ pub struct ComputedValueInfo {
|
||||
|
||||
impl ComputedValueInfo {
|
||||
fn apply_update(&mut self, update: ExpressionUpdate) {
|
||||
// We do not erase method_call information to avoid ports "flickering" on every computation.
|
||||
// the method_call should be updated soon anyway.
|
||||
// The type of the expression could also be kept, but so far we use the "lack of type"
|
||||
// information to inform user the results are recomputed.
|
||||
// We do not erase this information to avoid ports "flickering" on every computation.
|
||||
// The method_call should be updated soon anyway.
|
||||
if !matches!(update.payload, ExpressionUpdatePayload::Pending { .. }) {
|
||||
self.method_call = update.method_call.map(|mc| mc.method_pointer);
|
||||
self.typename = update.typename.map(ImString::new);
|
||||
}
|
||||
self.typename = update.typename.map(ImString::new);
|
||||
self.payload = update.payload
|
||||
}
|
||||
}
|
||||
@ -688,7 +686,7 @@ mod tests {
|
||||
let update1 = value_update_with_dataflow_error(expr2);
|
||||
let update2 = value_update_with_dataflow_panic(expr3, error_msg);
|
||||
registry.apply_updates(vec![update1, update2]);
|
||||
assert_eq!(registry.get(&expr1).unwrap().typename, Some(typename1.into()));
|
||||
assert_eq!(registry.get(&expr1).unwrap().typename, Some(typename1.clone().into()));
|
||||
assert!(matches!(registry.get(&expr1).unwrap().payload, ExpressionUpdatePayload::Value {
|
||||
warnings: None,
|
||||
}));
|
||||
@ -708,10 +706,9 @@ mod tests {
|
||||
// Set pending value
|
||||
let update1 = value_pending_update(expr1);
|
||||
registry.apply_updates(vec![update1]);
|
||||
// Method pointer should not be cleared to avoid port's flickering.
|
||||
// Method pointer and type are not affected; pending state is shown by an opacity change.
|
||||
assert_eq!(registry.get(&expr1).unwrap().method_call, Some(method_ptr));
|
||||
// The type is erased to show invalidated path.
|
||||
assert_eq!(registry.get(&expr1).unwrap().typename, None);
|
||||
assert_eq!(registry.get(&expr1).unwrap().typename, Some(typename1.into()));
|
||||
assert!(matches!(
|
||||
registry.get(&expr1).unwrap().payload,
|
||||
ExpressionUpdatePayload::Pending { message: None, progress: None }
|
||||
|
@ -12,6 +12,7 @@ use crate::presenter::graph::state::State;
|
||||
use double_representation::context_switch::Context;
|
||||
use double_representation::context_switch::ContextSwitch;
|
||||
use double_representation::context_switch::ContextSwitchExpression;
|
||||
use engine_protocol::language_server::ExpressionUpdatePayload;
|
||||
use enso_frp as frp;
|
||||
use futures::future::LocalBoxFuture;
|
||||
use ide_view as view;
|
||||
@ -411,6 +412,15 @@ impl Model {
|
||||
self.state.update_from_controller().set_node_error_from_payload(expression, payload)
|
||||
}
|
||||
|
||||
fn refresh_node_pending(&self, expression: ast::Id) -> Option<(ViewNodeId, bool)> {
|
||||
let registry = self.controller.computed_value_info_registry();
|
||||
let is_pending = registry
|
||||
.get(&expression)
|
||||
.map(|info| matches!(info.payload, ExpressionUpdatePayload::Pending { .. }))
|
||||
.unwrap_or_default();
|
||||
self.state.update_from_controller().set_node_pending(expression, is_pending)
|
||||
}
|
||||
|
||||
/// Extract the expression's current type from controllers.
|
||||
fn expression_type(&self, id: ast::Id) -> Option<view::graph_editor::Type> {
|
||||
let registry = self.controller.computed_value_info_registry();
|
||||
@ -746,6 +756,7 @@ impl Graph {
|
||||
update_expression <= update_expressions;
|
||||
view.set_expression_usage_type <+ update_expression.filter_map(f!((id) model.refresh_expression_type(*id)));
|
||||
view.set_node_error_status <+ update_expression.filter_map(f!((id) model.refresh_node_error(*id)));
|
||||
view.set_node_pending_status <+ update_expression.filter_map(f!((id) model.refresh_node_pending(*id)));
|
||||
|
||||
self.init_widgets(reset_node_types, update_expression.clone_ref());
|
||||
|
||||
|
@ -24,7 +24,7 @@ use ide_view::graph_editor::EdgeEndpoint;
|
||||
|
||||
/// A single node data.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Node {
|
||||
pub view_id: Option<ViewNodeId>,
|
||||
pub position: Vector2,
|
||||
@ -33,27 +33,12 @@ pub struct Node {
|
||||
pub is_frozen: bool,
|
||||
pub context_switch: Option<ContextSwitchExpression>,
|
||||
pub error: Option<node_view::Error>,
|
||||
pub is_pending: bool,
|
||||
pub visualization: Option<visualization_view::Path>,
|
||||
|
||||
/// Indicate whether this node view is updated automatically by changes from the controller
|
||||
/// or view, or will be explicitly updated..
|
||||
expression_auto_update: bool,
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
view_id: None,
|
||||
position: Vector2::default(),
|
||||
expression: node_view::Expression::default(),
|
||||
is_skipped: false,
|
||||
is_frozen: false,
|
||||
context_switch: None,
|
||||
error: None,
|
||||
visualization: None,
|
||||
expression_auto_update: true,
|
||||
}
|
||||
}
|
||||
disable_expression_auto_update: bool,
|
||||
}
|
||||
|
||||
/// The set of node states.
|
||||
@ -293,12 +278,19 @@ impl State {
|
||||
// When node is in process of being created, it is not yet present in the state. In that
|
||||
// case the initial expression update needs to be processed. Otherwise the node would be
|
||||
// created without any expression.
|
||||
self.nodes.borrow().get(node).map_or(true, |node| node.expression_auto_update)
|
||||
let auto_update_disabled = self
|
||||
.nodes
|
||||
.borrow()
|
||||
.get(node)
|
||||
.map_or_default(|node| node.disable_expression_auto_update);
|
||||
!auto_update_disabled
|
||||
}
|
||||
|
||||
/// Set the flag that indicates if the node should be synced with its AST automatically.
|
||||
pub fn allow_expression_auto_updates(&self, node: ast::Id, allow: bool) {
|
||||
self.nodes.borrow_mut().get_mut(node).for_each(|node| node.expression_auto_update = allow);
|
||||
if let Some(node) = self.nodes.borrow_mut().get_mut(node) {
|
||||
node.disable_expression_auto_update = !allow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,6 +444,22 @@ impl<'a> ControllerChange<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether this node is currently awaiting completion of execution.
|
||||
pub fn set_node_pending(
|
||||
&self,
|
||||
node_id: ast::Id,
|
||||
is_pending: bool,
|
||||
) -> Option<(ViewNodeId, bool)> {
|
||||
let mut nodes = self.nodes.borrow_mut();
|
||||
let displayed = nodes.get_mut(node_id)?;
|
||||
if displayed.is_pending != is_pending {
|
||||
displayed.is_pending = is_pending;
|
||||
Some((displayed.view_id?, is_pending))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_payload_to_error(
|
||||
&self,
|
||||
node_id: AstNodeId,
|
||||
|
@ -1,7 +1,3 @@
|
||||
//! NOTE
|
||||
//! This file is under a heavy development. It contains commented lines of code and some code may
|
||||
//! be of poor quality. Expect drastic changes.
|
||||
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
@ -17,7 +13,6 @@
|
||||
|
||||
use ensogl::prelude::*;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display::object::ObjectOps;
|
||||
use ensogl::display::shape::StyleWatch;
|
||||
@ -30,7 +25,6 @@ use ide_view::graph_editor;
|
||||
use ide_view::graph_editor::component::node::vcs;
|
||||
use ide_view::graph_editor::component::node::Expression;
|
||||
use ide_view::graph_editor::GraphEditor;
|
||||
use ide_view::graph_editor::NodeProfilingStatus;
|
||||
use ide_view::graph_editor::Type;
|
||||
use ide_view::project;
|
||||
use ide_view::root;
|
||||
@ -53,26 +47,6 @@ pub fn main() {
|
||||
}
|
||||
|
||||
|
||||
fn _fence<T, Out>(network: &frp::Network, trigger: T) -> (frp::Stream, frp::Stream<bool>)
|
||||
where
|
||||
T: frp::HasOutput<Output = Out>,
|
||||
T: Into<frp::Stream<Out>>,
|
||||
Out: frp::Data, {
|
||||
let trigger = trigger.into();
|
||||
frp::extend! { network
|
||||
def trigger_ = trigger.constant(());
|
||||
def runner = source::<()>();
|
||||
def switch = any_mut();
|
||||
switch.attach(&trigger_);
|
||||
def triggered = trigger.map(f_!(runner.emit(())));
|
||||
switch.attach(&triggered);
|
||||
def condition = switch.toggle_true();
|
||||
}
|
||||
let runner = runner.into();
|
||||
(runner, condition)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Mock Types ===
|
||||
@ -178,18 +152,22 @@ fn init(app: &Application) {
|
||||
let dummy_node_added_id = graph_editor.model.add_node();
|
||||
let dummy_node_edited_id = graph_editor.model.add_node();
|
||||
let dummy_node_unchanged_id = graph_editor.model.add_node();
|
||||
let dummy_node_pending_id = graph_editor.model.add_node();
|
||||
|
||||
graph_editor.frp.set_node_position.emit((dummy_node_added_id, Vector2(-450.0, 50.0)));
|
||||
graph_editor.frp.set_node_position.emit((dummy_node_edited_id, Vector2(-450.0, 125.0)));
|
||||
graph_editor.frp.set_node_position.emit((dummy_node_unchanged_id, Vector2(-450.0, 200.0)));
|
||||
graph_editor.frp.set_node_position.emit((dummy_node_pending_id, Vector2(-450.0, 275.0)));
|
||||
|
||||
let dummy_node_added_expr = expression_mock_string("This node was added.");
|
||||
let dummy_node_edited_expr = expression_mock_string("This node was edited.");
|
||||
let dummy_node_unchanged_expr = expression_mock_string("This node was not changed.");
|
||||
let dummy_node_pending_expr = expression_mock_string("This node is pending.");
|
||||
|
||||
graph_editor.frp.set_node_expression.emit((dummy_node_added_id, dummy_node_added_expr));
|
||||
graph_editor.frp.set_node_expression.emit((dummy_node_edited_id, dummy_node_edited_expr));
|
||||
graph_editor.frp.set_node_expression.emit((dummy_node_unchanged_id, dummy_node_unchanged_expr));
|
||||
graph_editor.frp.set_node_expression.emit((dummy_node_pending_id, dummy_node_pending_expr));
|
||||
|
||||
graph_editor.frp.set_node_vcs_status.emit((dummy_node_added_id, Some(vcs::Status::Edited)));
|
||||
graph_editor.frp.set_node_vcs_status.emit((dummy_node_edited_id, Some(vcs::Status::Added)));
|
||||
@ -197,6 +175,7 @@ fn init(app: &Application) {
|
||||
.frp
|
||||
.set_node_vcs_status
|
||||
.emit((dummy_node_unchanged_id, Some(vcs::Status::Unchanged)));
|
||||
graph_editor.frp.set_node_pending_status.emit((dummy_node_pending_id, true));
|
||||
|
||||
|
||||
// === Types (Port Coloring) ===
|
||||
@ -232,16 +211,6 @@ fn init(app: &Application) {
|
||||
});
|
||||
|
||||
|
||||
// === Profiling ===
|
||||
|
||||
let node1_status = NodeProfilingStatus::Finished { duration: 500.0 };
|
||||
graph_editor.set_node_profiling_status(node1_id, node1_status);
|
||||
let node2_status = NodeProfilingStatus::Finished { duration: 1000.0 };
|
||||
graph_editor.set_node_profiling_status(node2_id, node2_status);
|
||||
let node3_status = NodeProfilingStatus::Finished { duration: 1500.0 };
|
||||
graph_editor.set_node_profiling_status(node3_id, node3_status);
|
||||
|
||||
|
||||
// === Execution Modes ===
|
||||
|
||||
graph_editor.set_available_execution_environments(make_dummy_execution_environments());
|
||||
|
@ -8,8 +8,6 @@
|
||||
pub mod add_node_button;
|
||||
pub mod edge;
|
||||
pub mod node;
|
||||
#[warn(missing_docs)]
|
||||
pub mod profiling;
|
||||
pub mod type_coloring;
|
||||
pub mod visualization;
|
||||
|
||||
|
@ -210,7 +210,8 @@ fn junction_points(
|
||||
// The target attachment will extend as far toward the edge of the node as it can without
|
||||
// rising above the source.
|
||||
let attachment_height = target_max_attachment_height.map(|dy| min(dy, target.y().abs()));
|
||||
let attachment_y = target.y() + attachment_height.unwrap_or_default();
|
||||
let attachment_y =
|
||||
target.y() + attachment_height.unwrap_or_default() + target_size.y() / 2.0;
|
||||
let target_attachment = Vector2(target.x(), attachment_y);
|
||||
(vec![source, target_attachment], max_radius, attachment)
|
||||
} else {
|
||||
|
@ -41,7 +41,7 @@ mod attachment {
|
||||
/// appears to pass through the top of the node. Without this adjustment, inexact
|
||||
/// floating-point math and anti-aliasing would cause a 1-pixel gap artifact right where
|
||||
/// the attachment should meet the corner at the edge of the node.
|
||||
pub(super) const LENGTH_ADJUSTMENT: f32 = 0.1;
|
||||
pub(super) const LENGTH_ADJUSTMENT: f32 = 1.0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,7 +4,6 @@ use crate::prelude::*;
|
||||
use ensogl::display::shape::*;
|
||||
use ensogl::display::traits::*;
|
||||
|
||||
use crate::component::node::profiling::ProfilingLabel;
|
||||
use crate::component::visualization;
|
||||
use crate::selection::BoundingBox;
|
||||
use crate::tooltip;
|
||||
@ -40,8 +39,6 @@ pub mod expression;
|
||||
pub mod growth_animation;
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
#[warn(missing_docs)]
|
||||
pub mod profiling;
|
||||
#[deny(missing_docs)]
|
||||
pub mod vcs;
|
||||
|
||||
@ -213,6 +210,7 @@ ensogl::define_endpoints_2! {
|
||||
disable_visualization (),
|
||||
set_visualization (Option<visualization::Definition>),
|
||||
set_disabled (bool),
|
||||
set_pending (bool),
|
||||
set_connections (HashMap<span_tree::PortId, color::Lcha>),
|
||||
set_expression (Expression),
|
||||
edit_expression (text::Range<text::Byte>, ImString),
|
||||
@ -239,7 +237,6 @@ ensogl::define_endpoints_2! {
|
||||
set_view_mode (view::Mode),
|
||||
set_profiling_min_global_duration (f32),
|
||||
set_profiling_max_global_duration (f32),
|
||||
set_profiling_status (profiling::Status),
|
||||
/// Indicate whether on hover the quick action icons should appear.
|
||||
show_quick_action_bar_on_hover (bool),
|
||||
set_execution_environment (ExecutionEnvironment),
|
||||
@ -378,7 +375,6 @@ pub struct NodeModel {
|
||||
pub display_object: display::object::Instance,
|
||||
pub background: Background,
|
||||
pub error_indicator: Rectangle,
|
||||
pub profiling_label: ProfilingLabel,
|
||||
pub input: input::Area,
|
||||
pub output: output::Area,
|
||||
pub visualization: visualization::Container,
|
||||
@ -403,12 +399,10 @@ impl NodeModel {
|
||||
.set_pointer_events(false)
|
||||
.set_color(color::Rgba::transparent())
|
||||
.set_border_and_inset(ERROR_BORDER_WIDTH);
|
||||
let profiling_label = ProfilingLabel::new(app);
|
||||
let background = Background::new(&style);
|
||||
let vcs_indicator = vcs::StatusIndicator::new(app);
|
||||
let display_object = display::object::Instance::new_named("Node");
|
||||
|
||||
display_object.add_child(&profiling_label);
|
||||
display_object.add_child(&background);
|
||||
display_object.add_child(&vcs_indicator);
|
||||
|
||||
@ -441,7 +435,6 @@ impl NodeModel {
|
||||
display_object,
|
||||
background,
|
||||
error_indicator,
|
||||
profiling_label,
|
||||
input,
|
||||
output,
|
||||
visualization,
|
||||
@ -655,6 +648,7 @@ impl Node {
|
||||
|
||||
model.input.set_connections <+ input.set_connections;
|
||||
model.input.set_disabled <+ input.set_disabled;
|
||||
model.input.set_pending <+ input.set_pending;
|
||||
model.input.update_widgets <+ input.update_widgets;
|
||||
model.output.set_expression_visibility <+ input.set_output_expression_visibility;
|
||||
|
||||
@ -712,7 +706,6 @@ impl Node {
|
||||
|
||||
model.input.set_view_mode <+ input.set_view_mode;
|
||||
model.input.set_edit_ready_mode <+ input.set_edit_ready_mode;
|
||||
model.profiling_label.set_view_mode <+ input.set_view_mode;
|
||||
model.vcs_indicator.set_visibility <+ input.set_view_mode.map(|&mode| {
|
||||
!matches!(mode,view::Mode::Profiling {..})
|
||||
});
|
||||
@ -813,8 +806,8 @@ impl Node {
|
||||
visualization.set_view_state <+ vis_preview_visible.on_false().constant(visualization::ViewState::Disabled);
|
||||
}
|
||||
frp::extend! { network
|
||||
update_error <- all(input.set_error,preview_visible);
|
||||
eval update_error([model]((error,visible)){
|
||||
update_error <- all(input.set_error, preview_visible);
|
||||
eval update_error([model]((error, visible)){
|
||||
if *visible {
|
||||
model.set_error(error.as_ref());
|
||||
} else {
|
||||
@ -830,17 +823,6 @@ impl Node {
|
||||
|
||||
}
|
||||
|
||||
frp::extend! { network
|
||||
// === Profiling Indicator ===
|
||||
|
||||
model.profiling_label.set_min_global_duration
|
||||
<+ input.set_profiling_min_global_duration;
|
||||
model.profiling_label.set_max_global_duration
|
||||
<+ input.set_profiling_max_global_duration;
|
||||
model.profiling_label.set_status <+ input.set_profiling_status;
|
||||
model.input.set_profiling_status <+ input.set_profiling_status;
|
||||
}
|
||||
|
||||
frp::extend! { network
|
||||
// === Tooltip ===
|
||||
|
||||
@ -880,8 +862,19 @@ impl Node {
|
||||
|
||||
let port_color_tint = style_frp.get_color_lcha(theme::graph_editor::node::port_color_tint);
|
||||
let editing_color = style_frp.get_color_lcha(theme::graph_editor::node::background);
|
||||
let pending_alpha_factor =
|
||||
style_frp.get_number(theme::graph_editor::node::pending::alpha_factor);
|
||||
base_color_source <- source();
|
||||
out.base_color <+ base_color_source;
|
||||
adjusted_base_color <- all_with3(
|
||||
&base_color_source, &frp.set_pending, &pending_alpha_factor,
|
||||
|c: &color::Lcha, pending, factor| {
|
||||
match *pending {
|
||||
true => c.multiply_alpha(*factor),
|
||||
false => *c,
|
||||
}
|
||||
}
|
||||
);
|
||||
out.base_color <+ adjusted_base_color;
|
||||
out.port_color <+ out.base_color.all_with(&port_color_tint, |c, tint| tint.over(*c));
|
||||
background_color <- model.input.frp.editing.switch(&frp.base_color, &editing_color);
|
||||
node_colors <- all(background_color, frp.port_color);
|
||||
@ -908,6 +901,7 @@ impl Node {
|
||||
model.error_visualization.set_layer(visualization::Layer::Front);
|
||||
frp.set_error.emit(None);
|
||||
frp.set_disabled.emit(false);
|
||||
frp.set_pending.emit(false);
|
||||
frp.show_quick_action_bar_on_hover.emit(true);
|
||||
|
||||
let widget = gui::Widget::new(app, frp, model);
|
||||
|
@ -8,7 +8,6 @@ use ensogl::display::traits::*;
|
||||
use crate::node;
|
||||
use crate::node::input::widget;
|
||||
use crate::node::input::widget::OverrideKey;
|
||||
use crate::node::profiling;
|
||||
use crate::view;
|
||||
use crate::CallWidgetsConfig;
|
||||
use crate::GraphLayers;
|
||||
@ -306,16 +305,16 @@ impl Model {
|
||||
///
|
||||
/// See also: [`controller::graph::widget`] module of `enso-gui` crate.
|
||||
#[profile(Debug)]
|
||||
fn request_widget_config_overrides(&self, expression: &Expression, area_frp: &FrpEndpoints) {
|
||||
fn request_widget_config_overrides(&self, expression: &Expression, area_frp: &api::Private) {
|
||||
for (call_id, target_id) in expression.target_map.iter() {
|
||||
area_frp.source.requested_widgets.emit((*call_id, *target_id));
|
||||
area_frp.output.requested_widgets.emit((*call_id, *target_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a displayed expression, updating the input ports. `is_editing` indicates whether the
|
||||
/// expression is being edited by the user.
|
||||
#[profile(Debug)]
|
||||
fn set_expression(&self, new_expression: impl Into<node::Expression>, area_frp: &FrpEndpoints) {
|
||||
fn set_expression(&self, new_expression: impl Into<node::Expression>, area_frp: &api::Private) {
|
||||
let new_expression = Expression::from(new_expression.into());
|
||||
debug!("Set expression: \n{:?}", new_expression.tree_pretty_printer());
|
||||
|
||||
@ -346,7 +345,7 @@ impl Model {
|
||||
// === FRP ===
|
||||
// ===========
|
||||
|
||||
ensogl::define_endpoints! {
|
||||
ensogl::define_endpoints_2! {
|
||||
Input {
|
||||
/// Set the node expression.
|
||||
set_expression (node::Expression),
|
||||
@ -372,6 +371,9 @@ ensogl::define_endpoints! {
|
||||
/// Disable the node (aka "skip mode").
|
||||
set_disabled (bool),
|
||||
|
||||
/// Set the node pending (awaiting execution completion).
|
||||
set_pending (bool),
|
||||
|
||||
/// Set read-only mode for input ports.
|
||||
set_read_only (bool),
|
||||
|
||||
@ -385,7 +387,6 @@ ensogl::define_endpoints! {
|
||||
set_ports_active (bool),
|
||||
|
||||
set_view_mode (view::Mode),
|
||||
set_profiling_status (profiling::Status),
|
||||
|
||||
/// Set the expression USAGE type. This is not the definition type, which can be set with
|
||||
/// `set_expression` instead. In case the usage type is set to None, ports still may be
|
||||
@ -457,10 +458,10 @@ impl Area {
|
||||
// This is meant to be on top of FRP network. Read more about `Node` docs to
|
||||
// learn more about the architecture and the importance of the hover
|
||||
// functionality.
|
||||
frp.output.source.on_port_hover <+ model.widget_tree.on_port_hover;
|
||||
frp.output.source.on_port_press <+ model.widget_tree.on_port_press;
|
||||
frp.private.output.on_port_hover <+ model.widget_tree.on_port_hover;
|
||||
frp.private.output.on_port_press <+ model.widget_tree.on_port_press;
|
||||
port_hover <- frp.on_port_hover.map(|t| t.is_on());
|
||||
frp.output.source.body_hover <+ frp.set_hover || port_hover;
|
||||
frp.private.output.body_hover <+ frp.set_hover || port_hover;
|
||||
|
||||
|
||||
// === Cursor setup ===
|
||||
@ -474,12 +475,12 @@ impl Area {
|
||||
edit_or_ready <- frp.set_edit_ready_mode || set_editing;
|
||||
reacts_to_hover <- all_with(&edit_or_ready, ports_active, |e, a| *e && !a);
|
||||
port_vis <- all_with(&set_editing, ports_active, |e, a| !e && *a);
|
||||
frp.output.source.ports_visible <+ port_vis;
|
||||
frp.output.source.editing <+ set_editing;
|
||||
frp.private.output.ports_visible <+ port_vis;
|
||||
frp.private.output.editing <+ set_editing;
|
||||
model.widget_tree.set_ports_visible <+ frp.ports_visible;
|
||||
model.widget_tree.set_edit_ready_mode <+ frp.set_edit_ready_mode;
|
||||
refresh_edges <- model.widget_tree.connected_port_updated.debounce();
|
||||
frp.output.source.input_edges_need_refresh <+ refresh_edges;
|
||||
frp.private.output.input_edges_need_refresh <+ refresh_edges;
|
||||
|
||||
// === Label Hover ===
|
||||
|
||||
@ -497,28 +498,28 @@ impl Area {
|
||||
model.widget_tree.pointer_style,
|
||||
hovered_port_pointer
|
||||
].fold();
|
||||
frp.output.source.pointer_style <+ pointer_style;
|
||||
frp.private.output.pointer_style <+ pointer_style;
|
||||
|
||||
// === Properties ===
|
||||
let widget_tree_object = model.widget_tree.display_object();
|
||||
widget_tree_width <- widget_tree_object.on_resized.map(|size| size.x());
|
||||
edit_label_width <- all(model.edit_mode_label.width, init)._0();
|
||||
padded_edit_label_width <- edit_label_width.map(|t| t + 2.0 * TEXT_OFFSET);
|
||||
frp.output.source.width <+ set_editing.switch(
|
||||
frp.private.output.width <+ set_editing.switch(
|
||||
&widget_tree_width,
|
||||
&padded_edit_label_width
|
||||
);
|
||||
|
||||
// === Expression ===
|
||||
|
||||
let frp_endpoints = &frp.output;
|
||||
let frp_endpoints = &frp.private;
|
||||
eval frp.set_expression([frp_endpoints, model](expr) model.set_expression(expr, &frp_endpoints));
|
||||
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_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(
|
||||
frp.private.output.expression_edit <+ selections_edited.gate(&set_editing).map2(
|
||||
&model.edit_mode_label.content,
|
||||
f!([model](selection, full_content) {
|
||||
let full_content = full_content.into();
|
||||
@ -527,7 +528,7 @@ impl Area {
|
||||
(full_content, selections)
|
||||
})
|
||||
);
|
||||
frp.output.source.on_port_code_update <+ expression_edited.map(|e| {
|
||||
frp.private.output.on_port_code_update <+ expression_edited.map(|e| {
|
||||
// Treat edit mode update as a code modification at the span tree root.
|
||||
(default(), e.into())
|
||||
});
|
||||
@ -537,8 +538,8 @@ impl Area {
|
||||
(crumbs.clone(), expression)
|
||||
});
|
||||
|
||||
frp.output.source.on_port_code_update <+ widget_code_update;
|
||||
frp.output.source.request_import <+ model.widget_tree.request_import;
|
||||
frp.private.output.on_port_code_update <+ widget_code_update;
|
||||
frp.private.output.request_import <+ model.widget_tree.request_import;
|
||||
|
||||
// === Widgets ===
|
||||
|
||||
@ -546,16 +547,16 @@ impl Area {
|
||||
eval frp.set_connections((conn) model.set_connections(conn));
|
||||
eval frp.set_expression_usage_type(((id,tp)) model.set_expression_usage_type(*id,tp.clone()));
|
||||
eval frp.set_disabled ((disabled) model.widget_tree.set_disabled(*disabled));
|
||||
eval frp.set_pending ((pending) model.widget_tree.set_pending(*pending));
|
||||
eval_ model.widget_tree.rebuild_required(model.rebuild_widget_tree_if_dirty());
|
||||
frp.output.source.widget_tree_rebuilt <+ model.widget_tree.on_rebuild_finished;
|
||||
frp.private.output.widget_tree_rebuilt <+ model.widget_tree.on_rebuild_finished;
|
||||
|
||||
|
||||
// === View Mode ===
|
||||
|
||||
frp.output.source.view_mode <+ frp.set_view_mode;
|
||||
frp.private.output.view_mode <+ frp.set_view_mode;
|
||||
model.widget_tree.set_read_only <+ frp.set_read_only;
|
||||
model.widget_tree.set_view_mode <+ frp.set_view_mode;
|
||||
model.widget_tree.set_profiling_status <+ frp.set_profiling_status;
|
||||
model.widget_tree.node_base_color <+ frp.set_node_colors._0();
|
||||
model.widget_tree.node_port_color <+ frp.set_node_colors._1();
|
||||
}
|
||||
|
@ -131,8 +131,8 @@ ensogl::define_endpoints_2! {
|
||||
set_edit_ready_mode (bool),
|
||||
set_read_only (bool),
|
||||
set_view_mode (crate::view::Mode),
|
||||
set_profiling_status (crate::node::profiling::Status),
|
||||
set_disabled (bool),
|
||||
set_pending (bool),
|
||||
node_base_color (color::Lcha),
|
||||
node_port_color (color::Lcha),
|
||||
}
|
||||
@ -579,7 +579,6 @@ pub struct WidgetsFrp {
|
||||
/// combination of `set_read_only`, `set_edit_ready_mode` and `set_ports_visible` signals.
|
||||
pub(super) allow_interaction: frp::Sampler<bool>,
|
||||
pub(super) set_view_mode: frp::Sampler<crate::view::Mode>,
|
||||
pub(super) set_profiling_status: frp::Sampler<crate::node::profiling::Status>,
|
||||
pub(super) hovered_port_children: frp::Sampler<HashSet<WidgetIdentity>>,
|
||||
/// Remove given tree node's reference from the widget tree, and send its only remaining strong
|
||||
/// reference to a new widget owner using [`SpanWidget::receive_ownership`] method. This will
|
||||
@ -647,7 +646,6 @@ impl Tree {
|
||||
set_edit_ready_mode <- frp.set_edit_ready_mode.sampler();
|
||||
set_read_only <- frp.set_read_only.sampler();
|
||||
set_view_mode <- frp.set_view_mode.sampler();
|
||||
set_profiling_status <- frp.set_profiling_status.sampler();
|
||||
node_base_color <- frp.node_base_color.sampler();
|
||||
node_port_color <- frp.node_port_color.sampler();
|
||||
on_port_hover <- any(...);
|
||||
@ -679,7 +677,6 @@ impl Tree {
|
||||
set_read_only,
|
||||
allow_interaction,
|
||||
set_view_mode,
|
||||
set_profiling_status,
|
||||
transfer_ownership,
|
||||
value_changed,
|
||||
request_import,
|
||||
@ -721,10 +718,15 @@ impl Tree {
|
||||
self.notify_dirty(self.model.set_disabled(disabled));
|
||||
}
|
||||
|
||||
/// Set pending status for given span tree node. The pending nodes will be semi-transparent.
|
||||
/// The widgets might change behavior depending on the pending status.
|
||||
pub fn set_pending(&self, pending: bool) {
|
||||
self.notify_dirty(self.model.set_pending(pending));
|
||||
}
|
||||
|
||||
/// Rebuild tree if it has been marked as dirty. The dirty flag is marked whenever more data
|
||||
/// external to the span-tree is provided, using `set_config_override`, `set_usage_type`,
|
||||
/// `set_connections` or `set_disabled` methods of the widget tree.
|
||||
/// `set_connections`, `set_disabled`, or `set_pending` methods of the widget tree.
|
||||
pub fn rebuild_tree_if_dirty(
|
||||
&self,
|
||||
tree: &span_tree::SpanTree,
|
||||
@ -949,6 +951,7 @@ struct TreeModel {
|
||||
connected_map: Rc<RefCell<HashMap<PortId, color::Lcha>>>,
|
||||
usage_type_map: Rc<RefCell<HashMap<ast::Id, crate::Type>>>,
|
||||
node_disabled: Cell<bool>,
|
||||
node_pending: Cell<bool>,
|
||||
tree_dirty: Cell<bool>,
|
||||
}
|
||||
|
||||
@ -968,6 +971,7 @@ impl TreeModel {
|
||||
app,
|
||||
display_object,
|
||||
node_disabled: default(),
|
||||
node_pending: default(),
|
||||
nodes_map: default(),
|
||||
hierarchy: default(),
|
||||
ports_map: default(),
|
||||
@ -1018,6 +1022,12 @@ impl TreeModel {
|
||||
self.mark_dirty_flag(prev_disabled != disabled)
|
||||
}
|
||||
|
||||
/// Set the execution status under given widget. It may cause the tree to be marked as dirty.
|
||||
fn set_pending(&self, pending: bool) -> bool {
|
||||
let prev_pending = self.node_pending.replace(pending);
|
||||
self.mark_dirty_flag(prev_pending != pending)
|
||||
}
|
||||
|
||||
/// Get parent of a node under given pointer, if exists.
|
||||
#[allow(dead_code)]
|
||||
pub fn parent(&self, pointer: WidgetIdentity) -> Option<WidgetIdentity> {
|
||||
@ -1106,6 +1116,7 @@ impl TreeModel {
|
||||
let usage_type_map = self.usage_type_map.borrow();
|
||||
let old_nodes = self.nodes_map.take();
|
||||
let node_disabled = self.node_disabled.get();
|
||||
let node_pending = self.node_pending.get();
|
||||
|
||||
// Old hierarchy is not used during the rebuild, so we might as well reuse the allocation.
|
||||
let mut hierarchy = self.hierarchy.take();
|
||||
@ -1117,6 +1128,7 @@ impl TreeModel {
|
||||
app,
|
||||
frp,
|
||||
node_disabled,
|
||||
node_pending,
|
||||
node_expression,
|
||||
layers,
|
||||
styles,
|
||||
@ -1184,6 +1196,8 @@ pub struct NodeInfo {
|
||||
/// Whether the node is disabled, i.e. its expression is not currently used in the computation.
|
||||
/// Widgets of disabled nodes are usually grayed out.
|
||||
pub disabled: bool,
|
||||
/// Whether the node is awaiting execution completion.
|
||||
pub pending: bool,
|
||||
/// Inferred type of Enso expression at this node's span. May differ from the definition type
|
||||
/// stored in the span tree.
|
||||
pub usage_type: Option<crate::Type>,
|
||||
@ -1524,6 +1538,7 @@ struct TreeBuilder<'a> {
|
||||
app: Application,
|
||||
frp: WidgetsFrp,
|
||||
node_disabled: bool,
|
||||
node_pending: bool,
|
||||
node_expression: &'a str,
|
||||
layers: &'a GraphLayers,
|
||||
styles: &'a StyleWatchFrp,
|
||||
@ -1657,6 +1672,7 @@ impl<'a> TreeBuilder<'a> {
|
||||
});
|
||||
|
||||
let disabled = self.node_disabled;
|
||||
let pending = self.node_pending;
|
||||
|
||||
let info = NodeInfo {
|
||||
identity: widget_id,
|
||||
@ -1665,6 +1681,7 @@ impl<'a> TreeBuilder<'a> {
|
||||
connection,
|
||||
subtree_connection,
|
||||
disabled,
|
||||
pending,
|
||||
usage_type,
|
||||
};
|
||||
|
||||
|
@ -10,9 +10,9 @@ use span_tree::node::Kind;
|
||||
|
||||
|
||||
|
||||
/// =============
|
||||
/// === Style ===
|
||||
/// =============
|
||||
// =============
|
||||
// === Style ===
|
||||
// =============
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, FromTheme)]
|
||||
#[base_path = "theme::widget::label"]
|
||||
@ -25,6 +25,7 @@ struct Style {
|
||||
disabled_weight: f32,
|
||||
placeholder_color: color::Rgba,
|
||||
placeholder_weight: f32,
|
||||
pending_alpha: f32,
|
||||
}
|
||||
|
||||
// ==============
|
||||
@ -39,6 +40,7 @@ ensogl::define_endpoints_2! {
|
||||
Input {
|
||||
content(ImString),
|
||||
text_color(ColorState),
|
||||
text_pending(bool),
|
||||
text_weight(Option<text::Weight>),
|
||||
text_sdf_weight(f32),
|
||||
}
|
||||
@ -90,9 +92,10 @@ impl SpanWidget for Widget {
|
||||
parent_port_hovered <- widgets_frp.hovered_port_children.map(move |h| h.contains(&id));
|
||||
parent_port_hovered <- parent_port_hovered.on_change();
|
||||
text_color <- frp.text_color.on_change();
|
||||
label_color <- all_with3(
|
||||
&style, &text_color, &parent_port_hovered,
|
||||
|style, state, hovered| state.to_color(*hovered, style)
|
||||
text_pending <- frp.text_pending.on_change();
|
||||
label_color <- all_with4(
|
||||
&style, &text_color, &parent_port_hovered, &text_pending,
|
||||
|style, state, hovered, text_pending| state.to_color(*hovered, style, *text_pending)
|
||||
).debounce().on_change();
|
||||
color_anim.target <+ label_color;
|
||||
eval color_anim.value((color) label.set_property_default(color));
|
||||
@ -147,8 +150,9 @@ impl SpanWidget for Widget {
|
||||
let text_weight = bold.then_some(text::Weight::Bold);
|
||||
|
||||
let input = &self.frp.public.input;
|
||||
input.content.emit(content);
|
||||
input.text_color.emit(color_state);
|
||||
input.content(content);
|
||||
input.text_color(color_state);
|
||||
input.text_pending(ctx.info.pending);
|
||||
input.text_weight(text_weight);
|
||||
}
|
||||
}
|
||||
@ -201,14 +205,18 @@ impl ColorState {
|
||||
text::Weight::from(weight_num as u16)
|
||||
})
|
||||
}
|
||||
fn to_color(self, is_hovered: bool, style: &Style) -> color::Lcha {
|
||||
match self {
|
||||
|
||||
fn to_color(self, is_hovered: bool, style: &Style, text_pending: bool) -> color::Lcha {
|
||||
let base_color = color::Lcha::from(match self {
|
||||
_ if is_hovered => style.connected_color,
|
||||
ColorState::Base => style.base_color,
|
||||
ColorState::Connected => style.connected_color,
|
||||
ColorState::Disabled => style.disabled_color,
|
||||
ColorState::Placeholder => style.placeholder_color,
|
||||
});
|
||||
match text_pending {
|
||||
true => base_color.multiply_alpha(style.pending_alpha),
|
||||
false => base_color,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,18 @@ use ensogl_icons::SIZE;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
// =============
|
||||
// === Style ===
|
||||
// =============
|
||||
|
||||
/// Distance between the icon and next widget.
|
||||
const ICON_GAP: f32 = 10.0;
|
||||
#[derive(Clone, Debug, Default, PartialEq, FromTheme)]
|
||||
#[base_path = "theme::widget::method"]
|
||||
struct Style {
|
||||
/// Distance between the icon and next widget.
|
||||
icon_gap: f32,
|
||||
/// Alpha channel value for the icon displayed on a node in the execution-pending state.
|
||||
pending_icon_alpha: f32,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -27,6 +33,13 @@ const ICON_GAP: f32 = 10.0;
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Config;
|
||||
|
||||
ensogl::define_endpoints_2! {
|
||||
Input {
|
||||
set_icon(IconId),
|
||||
set_icon_state(IconState),
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget for selecting a single value from a list of available options. The options can be
|
||||
/// provided as a static list of strings from argument `tag_values`, or as a dynamic expression.
|
||||
#[derive(Debug, display::Object)]
|
||||
@ -34,8 +47,8 @@ pub struct Config;
|
||||
pub struct Widget {
|
||||
display_object: object::Instance,
|
||||
icon_wrapper: object::Instance,
|
||||
icon_id: IconId,
|
||||
icon: AnyIcon,
|
||||
icon_view: Rc<RefCell<AnyIcon>>,
|
||||
frp: Frp,
|
||||
}
|
||||
|
||||
/// Extension which existence in given subtree prevents the widget from being created again.
|
||||
@ -54,7 +67,7 @@ impl SpanWidget for Widget {
|
||||
Score::only_if(matches)
|
||||
}
|
||||
|
||||
fn new(_: &Config, _: &ConfigContext) -> Self {
|
||||
fn new(_: &Config, ctx: &ConfigContext) -> Self {
|
||||
// ╭─display_object──────────────────╮
|
||||
// │ ╭ icon ─╮ ╭ content ──────────╮ │
|
||||
// │ │ │ │ │ │
|
||||
@ -66,15 +79,42 @@ impl SpanWidget for Widget {
|
||||
display_object
|
||||
.use_auto_layout()
|
||||
.set_row_flow()
|
||||
.set_gap_x(ICON_GAP)
|
||||
.set_children_alignment_left_center()
|
||||
.justify_content_center_y();
|
||||
|
||||
let icon_wrapper = object::Instance::new_named("icon_wrapper");
|
||||
icon_wrapper.set_size((SIZE, SIZE));
|
||||
let mut this = Self { display_object, icon_wrapper, icon_id: default(), icon: default() };
|
||||
this.set_icon(IconId::default());
|
||||
this
|
||||
let icon_view = Rc::new(RefCell::new(AnyIcon::default()));
|
||||
|
||||
let frp = Frp::new();
|
||||
let network = &frp.network;
|
||||
let style = ctx.cached_style::<Style>(network);
|
||||
frp::extend! { network
|
||||
icon_gap <- style.on_change().map(|style| style.icon_gap);
|
||||
eval icon_gap((gap) display_object.set_gap_x(*gap).void());
|
||||
|
||||
set_icon <- frp.set_icon.on_change();
|
||||
eval set_icon([icon_view, icon_wrapper] (icon_id) {
|
||||
let icon = icon_id.cached_view();
|
||||
icon.set_size((SIZE, SIZE));
|
||||
icon_wrapper.replace_children(&[icon.display_object()]);
|
||||
icon_view.replace(icon);
|
||||
});
|
||||
|
||||
icon_state_dirty <- any(...);
|
||||
icon_state_dirty <+ frp.set_icon_state.on_change();
|
||||
icon_state_dirty <+ frp.set_icon_state.sample(&set_icon);
|
||||
icon_alpha <- all_with(&icon_state_dirty, &style, |state, style| state.alpha(style));
|
||||
eval icon_alpha((alpha)
|
||||
icon_view.borrow().r_component.set(Vector4(1.0, 1.0, 1.0, *alpha)));
|
||||
}
|
||||
frp.set_icon(IconId::default());
|
||||
frp.set_icon_state(IconState::Ready);
|
||||
Self {
|
||||
display_object,
|
||||
icon_wrapper,
|
||||
icon_view,
|
||||
frp,
|
||||
}
|
||||
}
|
||||
|
||||
fn configure(&mut self, _: &Config, mut ctx: ConfigContext) {
|
||||
@ -86,21 +126,34 @@ impl SpanWidget for Widget {
|
||||
.and_then(|app| app.icon_name.as_ref())
|
||||
.and_then(|name| name.parse::<IconId>().ok())
|
||||
.unwrap_or(IconId::Method);
|
||||
if icon_id != self.icon_id {
|
||||
self.set_icon(icon_id);
|
||||
}
|
||||
|
||||
self.frp.set_icon(icon_id);
|
||||
self.frp.set_icon_state(match ctx.info.pending {
|
||||
false => IconState::Ready,
|
||||
true => IconState::Pending,
|
||||
});
|
||||
let child = ctx.builder.child_widget(ctx.span_node, ctx.info.nesting_level);
|
||||
self.display_object.replace_children(&[&self.icon_wrapper, &child.root_object]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget {
|
||||
fn set_icon(&mut self, icon_id: IconId) {
|
||||
self.icon_id = icon_id;
|
||||
self.icon = icon_id.cached_view();
|
||||
self.icon.set_size((SIZE, SIZE));
|
||||
self.icon.r_component.set(Vector4(1.0, 1.0, 1.0, 1.0));
|
||||
self.icon_wrapper.replace_children(&[self.icon.display_object()]);
|
||||
|
||||
// === IconState ===
|
||||
|
||||
/// The display state of the method's icon.
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum IconState {
|
||||
/// The normal state.
|
||||
#[default]
|
||||
Ready,
|
||||
/// The state when awaiting a pending computation.
|
||||
Pending,
|
||||
}
|
||||
|
||||
impl IconState {
|
||||
fn alpha(self, style: &Style) -> f32 {
|
||||
match self {
|
||||
IconState::Ready => 1.0,
|
||||
IconState::Pending => style.pending_icon_alpha,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,240 +0,0 @@
|
||||
//! Provides [`Status`] to represent a node's execution status, [`Status::display_color`] to express
|
||||
//! that status as a color and [`ProfilingLabel`] to display a node's execution status.
|
||||
|
||||
use crate::prelude::*;
|
||||
use ensogl::display::shape::*;
|
||||
|
||||
use crate::view;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color;
|
||||
use ensogl::display;
|
||||
use ensogl::gui::text;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Status ===
|
||||
// ==============
|
||||
|
||||
/// Describes whether the source code in a node is currently running or already finished. If it is
|
||||
/// finished then the status contains the number of milliseconds that it took to run the code.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Status {
|
||||
/// The node's code is still running.
|
||||
Running,
|
||||
/// The node finished execution.
|
||||
Finished {
|
||||
/// How many milliseconds the node took to execute.
|
||||
duration: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Status {
|
||||
/// Returns `true` if the node is still running.
|
||||
pub fn is_running(self) -> bool {
|
||||
matches!(self, Status::Running)
|
||||
}
|
||||
|
||||
/// Returns `true` if the node finished execution.
|
||||
pub fn is_finished(self) -> bool {
|
||||
matches!(self, Status::Finished { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
Status::Running
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Status {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Status::Running => {
|
||||
write!(f, "")
|
||||
}
|
||||
Status::Finished { duration } => {
|
||||
let milliseconds = duration;
|
||||
let seconds = milliseconds / 1000.0;
|
||||
let minutes = seconds / 60.0;
|
||||
let hours = minutes / 60.0;
|
||||
if hours >= 1.0 {
|
||||
write!(f, "{hours:.1} h")
|
||||
} else if minutes >= 1.0 {
|
||||
write!(f, "{minutes:.1} m")
|
||||
} else if seconds >= 1.0 {
|
||||
write!(f, "{seconds:.1} s")
|
||||
} else {
|
||||
write!(f, "{milliseconds:.0} ms")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Color ===
|
||||
// =============
|
||||
|
||||
/// A theme that determines how we express the running time of a node (compared to other nodes on
|
||||
/// the stage) in a color. The color's lightness and chroma in LCh color space are directly taken
|
||||
/// from the theme. The chroma will be `min_time_hue` for the node with the shortest running time,
|
||||
/// `max_time_hue` for the node with the longest running time and linearly interpolated in-between
|
||||
/// depending on the relative running, time for all other nodes.
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Theme {
|
||||
/// The lightness for all running times.
|
||||
pub lightness: f32,
|
||||
/// The chroma for all running times.
|
||||
pub chroma: f32,
|
||||
/// The hue for the minimum running time.
|
||||
pub min_time_hue: f32,
|
||||
/// The hue for the maximum running time.
|
||||
pub max_time_hue: f32,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// Returns a new `Sampler` exposing the profiling theme, as it is defined in `styles` at path
|
||||
/// `ensogl_hardcoded_theme::graph_editor::node::profiling`. The sampler is registered under
|
||||
/// `network`.
|
||||
pub fn from_styles(styles: &StyleWatchFrp, network: &frp::Network) -> frp::Sampler<Theme> {
|
||||
use ensogl_hardcoded_theme::graph_editor::node::profiling as theme_path;
|
||||
let lightness = styles.get_number_or(theme_path::lightness, 0.5);
|
||||
let chroma = styles.get_number_or(theme_path::chroma, 1.0);
|
||||
let min_time_hue = styles.get_number_or(theme_path::min_time_hue, 0.4);
|
||||
let max_time_hue = styles.get_number_or(theme_path::max_time_hue, 0.1);
|
||||
|
||||
frp::extend! { network
|
||||
init_theme <- source::<()>();
|
||||
theme <- all_with5(&lightness,&chroma,&min_time_hue,&max_time_hue,&init_theme
|
||||
,|&lightness,&chroma,&min_time_hue,&max_time_hue,_|
|
||||
Theme {lightness,chroma,min_time_hue,max_time_hue});
|
||||
theme_sampler <- theme.sampler();
|
||||
}
|
||||
|
||||
init_theme.emit(());
|
||||
theme_sampler
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
/// Expresses the profiling status as a color, depending on the minimum and maximum running
|
||||
/// time of any node on the stage and a [`Theme`] that allows to tweak how the colors are
|
||||
/// chosen. A node that is still running will be treated like finished node with the current
|
||||
/// maximum execution time.
|
||||
pub fn display_color(
|
||||
self,
|
||||
min_global_duration: f32,
|
||||
max_global_duration: f32,
|
||||
theme: Theme,
|
||||
) -> color::Lch {
|
||||
let duration = match self {
|
||||
Status::Running => max_global_duration,
|
||||
Status::Finished { duration } => duration,
|
||||
};
|
||||
let duration_delta = max_global_duration - min_global_duration;
|
||||
let hue_delta = theme.max_time_hue - theme.min_time_hue;
|
||||
let relative_duration = if duration_delta != 0.0 && !duration_delta.is_nan() {
|
||||
(duration - min_global_duration) / duration_delta
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let relative_hue = relative_duration;
|
||||
let hue = theme.min_time_hue + relative_hue * hue_delta;
|
||||
color::Lch::new(theme.lightness, theme.chroma, hue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Frp ===
|
||||
// ============
|
||||
|
||||
ensogl::define_endpoints! {
|
||||
Input {
|
||||
set_status (Status),
|
||||
set_min_global_duration (f32),
|
||||
set_max_global_duration (f32),
|
||||
set_view_mode (view::Mode),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === Running Time Label ===
|
||||
// ==========================
|
||||
|
||||
/// A `display::Object` providing a label for nodes that displays the node's running time in
|
||||
/// profiling mode after the node finished execution. The node's execution status has to be provided
|
||||
/// through `set_status`, the view mode through `set_view_mode`, the minimum and maximum running
|
||||
/// time of any node on the stage through `set_min_global_duration` and `set_max_global_duration`.
|
||||
/// The color of the label will reflect the status and be determined by [`Status::display_color`].
|
||||
/// The necessary theme will be taken from the application's style sheet. The origin of the label,
|
||||
/// as a `display::Object` should be placed on the node's center.
|
||||
#[derive(Clone, CloneRef, Debug, Deref, display::Object)]
|
||||
pub struct ProfilingLabel {
|
||||
display_object: display::object::Instance,
|
||||
label: text::Text,
|
||||
#[deref]
|
||||
frp: Frp,
|
||||
styles: StyleWatchFrp,
|
||||
}
|
||||
|
||||
impl ProfilingLabel {
|
||||
/// Constructs a `ProfilingLabel` for the given application.
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let scene = &app.display.default_scene;
|
||||
let display_object = display::object::Instance::new();
|
||||
|
||||
let label = text::Text::new(app);
|
||||
display_object.add_child(&label);
|
||||
label.set_y(crate::component::node::input::area::TEXT_SIZE / 2.0);
|
||||
|
||||
let frp = Frp::new();
|
||||
let network = &frp.network;
|
||||
let color = color::Animation::new(network);
|
||||
|
||||
frp::extend! { network
|
||||
|
||||
// === Visibility ===
|
||||
|
||||
visibility <- all_with(&frp.set_view_mode,&frp.set_status,|mode,status| {
|
||||
matches!((mode,status),(view::Mode::Profiling,Status::Finished {..}))
|
||||
});
|
||||
|
||||
color.target_alpha <+ visibility.map(|&is_visible| {
|
||||
if is_visible { 1.0 } else { 0.0 }
|
||||
});
|
||||
|
||||
|
||||
// === Color ===
|
||||
|
||||
let styles = StyleWatchFrp::new(&scene.style_sheet);
|
||||
let theme = Theme::from_styles(&styles,network);
|
||||
color.target_color <+ all_with4
|
||||
(&frp.set_status,&frp.set_min_global_duration,&frp.set_max_global_duration,&theme,
|
||||
|&status,&min,&max,&theme| status.display_color(min,max,theme)
|
||||
);
|
||||
label.set_property_default <+ color.value.ref_into_some();
|
||||
|
||||
|
||||
// === Position ===
|
||||
|
||||
let x_offset = crate::component::node::input::area::TEXT_OFFSET;
|
||||
eval label.width((&width) label.set_x(-width-x_offset));
|
||||
|
||||
|
||||
// === Content ===
|
||||
|
||||
label.set_content <+ frp.set_status.map(|status| status.to_im_string());
|
||||
}
|
||||
|
||||
ProfilingLabel { display_object, label, frp, styles }
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
//! Provides a button that can be used to toggle the editor's profiling mode.
|
||||
|
||||
use crate::prelude::*;
|
||||
use ensogl::display::shape::*;
|
||||
|
||||
use crate::view;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl::application::tooltip;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color;
|
||||
use ensogl::display;
|
||||
use ensogl_component::toggle_button;
|
||||
use ensogl_component::toggle_button::ToggleButton;
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Icon ===
|
||||
// ============
|
||||
|
||||
/// Defines an icon as described here:
|
||||
/// https://github.com/enso-org/ide/pull/1546#issuecomment-838169795
|
||||
///
|
||||
/// It consists of a *circle outline* with an *aperture* in the lower right quadrant. The edges
|
||||
/// of the aperture have rounded *caps*. In the center is an *inner circle* with a *needle*
|
||||
/// pointing to the lower right.
|
||||
mod icon {
|
||||
use super::*;
|
||||
use ensogl_component::toggle_button::ColorableShape;
|
||||
|
||||
ensogl::shape! {
|
||||
alignment = center;
|
||||
(style: Style, color_rgba: Vector4<f32>) {
|
||||
let fill_color = Var::<color::Rgba>::from(color_rgba);
|
||||
let width = Var::<Pixels>::from("input_size.x");
|
||||
let height = Var::<Pixels>::from("input_size.y");
|
||||
|
||||
|
||||
// === Measurements ===
|
||||
|
||||
let unit = &width * 0.3;
|
||||
let outer_circle_radius = &unit * 1.0;
|
||||
let outer_circle_thickness = &unit * 0.33;
|
||||
let inner_circle_radius = &unit * 0.2;
|
||||
let needle_angle = (135.0_f32).to_radians().radians();
|
||||
let needle_radius_inner = &unit * 0.14;
|
||||
let needle_radius_outer = &unit * 0.09;
|
||||
let needle_length = &outer_circle_radius-&needle_radius_outer;
|
||||
let aperture_cap_1_x = &outer_circle_radius-&outer_circle_thickness*0.5;
|
||||
let aperture_cap_2_y = -(&outer_circle_radius-&outer_circle_thickness*0.5);
|
||||
|
||||
|
||||
// === Circle Outline ===
|
||||
|
||||
let circle = Circle(&outer_circle_radius);
|
||||
let gap = Circle(&outer_circle_radius-&outer_circle_thickness);
|
||||
let circle_outline = circle - gap;
|
||||
|
||||
|
||||
// === Aperture ===
|
||||
|
||||
// To produce the aperture, we cut a triangular gap from the outline and use small
|
||||
// circular caps to round off the edges.
|
||||
|
||||
// We make the gap a little bit larger than the circle to be sure that we really cover
|
||||
// everything that we want to cut, even if there are rounding errors or other
|
||||
// imprecisions.
|
||||
let aperture_gap_size = &outer_circle_radius * 1.1;
|
||||
let aperture_gap_angle = needle_angle+180.0_f32.to_radians().radians();
|
||||
|
||||
let aperture_gap = Triangle(&aperture_gap_size*2.0,aperture_gap_size.clone());
|
||||
let aperture_gap = aperture_gap.rotate(aperture_gap_angle);
|
||||
let aperture_gap = aperture_gap.translate_x(&aperture_gap_size*2.0.sqrt()*0.25);
|
||||
let aperture_gap = aperture_gap.translate_y(-&aperture_gap_size*2.0.sqrt()*0.25);
|
||||
|
||||
let aperture_cap_1 = Circle(&outer_circle_thickness*0.5);
|
||||
let aperture_cap_1 = aperture_cap_1.translate_x(aperture_cap_1_x);
|
||||
let aperture_cap_2 = Circle(&outer_circle_thickness*0.5);
|
||||
let aperture_cap_2 = aperture_cap_2.translate_y(aperture_cap_2_y);
|
||||
|
||||
let circle_outline = circle_outline - aperture_gap + aperture_cap_1 + aperture_cap_2;
|
||||
|
||||
|
||||
// === Needle ===
|
||||
|
||||
let needle = UnevenCapsule(needle_radius_outer,needle_radius_inner,needle_length);
|
||||
let needle = needle.rotate(needle_angle);
|
||||
let inner_circle = Circle(inner_circle_radius);
|
||||
|
||||
|
||||
// === Composition ===
|
||||
|
||||
let shape = (circle_outline + needle + inner_circle).fill(fill_color);
|
||||
let hover_area = Rect((&width,&height)).fill(INVISIBLE_HOVER_COLOR);
|
||||
(shape + hover_area).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorableShape for Shape {
|
||||
fn set_color(&self, color: color::Rgba) {
|
||||
self.color_rgba.set(Vector4::new(color.red, color.green, color.blue, color.alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Frp ===
|
||||
// ============
|
||||
|
||||
ensogl::define_endpoints! {
|
||||
Input {
|
||||
set_view_mode (view::Mode),
|
||||
}
|
||||
Output {
|
||||
view_mode (view::Mode),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === ProfilingButton ===
|
||||
// =======================
|
||||
|
||||
/// A toggle button that can be used to toggle the graph editor's view mode. It positions itself in
|
||||
/// the upper right corner of the scene.
|
||||
#[derive(Debug, Clone, CloneRef, Deref, display::Object)]
|
||||
pub struct Button {
|
||||
#[deref]
|
||||
frp: Frp,
|
||||
#[display_object]
|
||||
button: ToggleButton<icon::Shape>,
|
||||
styles: StyleWatchFrp,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
/// Constructs a new button for toggling the editor's view mode.
|
||||
pub fn new(app: &Application) -> Button {
|
||||
let scene = &app.display.default_scene;
|
||||
let style_sheet = &scene.style_sheet;
|
||||
let styles = StyleWatchFrp::new(style_sheet);
|
||||
let frp = Frp::new();
|
||||
let network = &frp.network;
|
||||
|
||||
let tooltip_style = tooltip::Style::set_label("Profile".to_owned())
|
||||
.with_placement(tooltip::Placement::Left);
|
||||
let button = ToggleButton::<icon::Shape>::new(app, tooltip_style);
|
||||
scene.layers.panel.add(&button);
|
||||
button.set_size(Vector2(32.0, 32.0));
|
||||
|
||||
frp::extend! { network
|
||||
|
||||
// === State ===
|
||||
|
||||
frp.source.view_mode <+ button.state.map(|&toggled| {
|
||||
if toggled { view::Mode::Profiling } else { view::Mode::Normal }
|
||||
});
|
||||
button.set_state <+ frp.set_view_mode.map(|&mode| mode.is_profiling());
|
||||
|
||||
|
||||
// === Position ===
|
||||
|
||||
eval scene.frp.camera_changed([button,scene](_) {
|
||||
let screen = scene.camera().screen();
|
||||
button.set_x(screen.width/2.0 - 16.0);
|
||||
button.set_y(screen.height/2.0 - 16.0);
|
||||
});
|
||||
|
||||
|
||||
// === Color ===
|
||||
|
||||
use ensogl_hardcoded_theme::graph_editor::profiling_button as button_theme;
|
||||
let toggled_color = styles.get_color(button_theme::toggled);
|
||||
let toggled_hovered_color = styles.get_color(button_theme::toggled_hovered);
|
||||
init_color_scheme <- source::<()>();
|
||||
button.set_color_scheme <+ all_with3(
|
||||
&toggled_color,
|
||||
&toggled_hovered_color,
|
||||
&init_color_scheme,
|
||||
f!([style_sheet] (&toggled, &toggled_hovered, _)
|
||||
toggle_button::ColorScheme {
|
||||
toggled : Some(toggled.into()),
|
||||
toggled_hovered : Some(toggled_hovered.into()),
|
||||
..toggle_button::default_color_scheme(&style_sheet)
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
init_color_scheme.emit(());
|
||||
Button { frp, button, styles }
|
||||
}
|
||||
}
|
@ -43,8 +43,6 @@ pub mod data;
|
||||
pub mod execution_environment;
|
||||
pub mod new_node_position;
|
||||
#[warn(missing_docs)]
|
||||
pub mod profiling;
|
||||
#[warn(missing_docs)]
|
||||
pub mod view;
|
||||
|
||||
mod layers;
|
||||
@ -88,7 +86,6 @@ use span_tree::PortId;
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use crate::node::profiling::Status as NodeProfilingStatus;
|
||||
pub use layers::GraphLayers;
|
||||
|
||||
|
||||
@ -552,9 +549,9 @@ ensogl::define_endpoints_2! {
|
||||
/// Collapse the selected nodes into a new node.
|
||||
collapse_selected_nodes(),
|
||||
/// Indicate whether this node had an error or not.
|
||||
set_node_error_status(NodeId,Option<node::error::Error>),
|
||||
set_node_error_status(NodeId, Option<node::error::Error>),
|
||||
/// Indicate whether this node has finished execution.
|
||||
set_node_profiling_status(NodeId,node::profiling::Status),
|
||||
set_node_pending_status(NodeId, bool),
|
||||
|
||||
|
||||
// === Visualization ===
|
||||
@ -1759,16 +1756,6 @@ impl GraphEditorModel {
|
||||
node.set_read_only <+ self.frp.input.set_read_only;
|
||||
|
||||
|
||||
// === Profiling ===
|
||||
|
||||
let profiling_min_duration = &self.profiling_statuses.min_duration;
|
||||
node.set_profiling_min_global_duration <+ self.profiling_statuses.min_duration;
|
||||
node.set_profiling_min_global_duration(profiling_min_duration.value());
|
||||
let profiling_max_duration = &self.profiling_statuses.max_duration;
|
||||
node.set_profiling_max_global_duration <+ self.profiling_statuses.max_duration;
|
||||
node.set_profiling_max_global_duration(profiling_max_duration.value());
|
||||
|
||||
|
||||
// === Execution Environment ===
|
||||
|
||||
node.set_execution_environment <+ self.frp.output.execution_environment;
|
||||
@ -1809,7 +1796,6 @@ pub struct GraphEditorModel {
|
||||
visualizations: Visualizations,
|
||||
frp: api::Private,
|
||||
frp_public: api::Public,
|
||||
profiling_statuses: profiling::Statuses,
|
||||
styles_frp: StyleWatchFrp,
|
||||
selection_controller: selection::Controller,
|
||||
}
|
||||
@ -1831,7 +1817,6 @@ impl GraphEditorModel {
|
||||
let app = app.clone_ref();
|
||||
let navigator = Navigator::new(scene, &scene.camera());
|
||||
let tooltip = Tooltip::new(&app);
|
||||
let profiling_statuses = profiling::Statuses::new();
|
||||
let add_node_button = Rc::new(component::add_node_button::AddNodeButton::new(&app));
|
||||
let drop_manager =
|
||||
ensogl_drop_manager::Manager::new(&scene.dom.root.clone_ref().into(), scene);
|
||||
@ -1858,7 +1843,6 @@ impl GraphEditorModel {
|
||||
touch_state,
|
||||
visualizations,
|
||||
navigator,
|
||||
profiling_statuses,
|
||||
add_node_button,
|
||||
frp: frp.private.clone_ref(),
|
||||
frp_public: frp.public.clone_ref(),
|
||||
@ -3020,17 +3004,12 @@ fn init_remaining_graph_editor_frp(
|
||||
|
||||
}
|
||||
|
||||
|
||||
// === Profiling ===
|
||||
|
||||
// === Set Node Pending ===
|
||||
frp::extend! { network
|
||||
|
||||
eval inputs.set_node_profiling_status([model]((node_id,status)) {
|
||||
model.with_node(*node_id, |node| {
|
||||
model.profiling_statuses.set(*node_id,*status);
|
||||
node.set_profiling_status(status);
|
||||
})
|
||||
});
|
||||
eval inputs.set_node_pending_status([model]((node_id, is_pending)) {
|
||||
model.with_node(*node_id, |n| n.set_pending.emit(is_pending))
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -3287,7 +3266,6 @@ fn init_remaining_graph_editor_frp(
|
||||
eval out.node_selected ((id) model.nodes.select(id));
|
||||
eval out.node_deselected ((id) model.nodes.deselect(id));
|
||||
eval out.node_removed ((id) model.remove_node(*id));
|
||||
model.profiling_statuses.remove <+ out.node_removed;
|
||||
out.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id));
|
||||
|
||||
// === Remove implementation ===
|
||||
|
@ -1,104 +0,0 @@
|
||||
//! [`ProfilingStatuses`] can be used to collect the profiling statuses of all nodes in a graph. It
|
||||
//! exposes their minimum and maximum running times through its FRP endpoints. The structure needs
|
||||
//! to be updated whenever a node is added or deleted or changes its profiling status.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::node;
|
||||
use crate::NodeId;
|
||||
|
||||
use bimap::BiBTreeMap;
|
||||
use enso_frp as frp;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === FRP Endpoints ===
|
||||
// =====================
|
||||
|
||||
ensogl::define_endpoints! {
|
||||
Input {
|
||||
/// Informs the `Statuses` collection about the profiling status of a node.
|
||||
set(NodeId,node::profiling::Status),
|
||||
|
||||
/// Removes a node's information from the collection.
|
||||
remove (NodeId)
|
||||
}
|
||||
Output {
|
||||
/// The minimum running time of any node in milliseconds. Is positive infinity if no status
|
||||
/// was registered.
|
||||
min_duration (f32),
|
||||
|
||||
/// The maximum running time of any node in milliseconds. Is 0.0 if no status was
|
||||
/// registered.
|
||||
max_duration (f32),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Statuses ===
|
||||
// ================
|
||||
|
||||
/// Can be used to track the execution statuses of all nodes in the graph editor. Exposes the
|
||||
/// minimum and maximum running time through FRP endpoints.
|
||||
#[derive(Debug, Clone, CloneRef, Default)]
|
||||
pub struct Statuses {
|
||||
frp: Frp,
|
||||
durations: Rc<RefCell<BiBTreeMap<NodeId, OrderedFloat<f32>>>>,
|
||||
}
|
||||
|
||||
impl Deref for Statuses {
|
||||
type Target = Frp;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.frp
|
||||
}
|
||||
}
|
||||
|
||||
impl Statuses {
|
||||
/// Creates a new `Statuses` collection.
|
||||
pub fn new() -> Self {
|
||||
let frp = Frp::new();
|
||||
let durations = Rc::new(RefCell::new(BiBTreeMap::<NodeId, OrderedFloat<f32>>::new()));
|
||||
let network = &frp.network;
|
||||
|
||||
frp::extend! { network
|
||||
min_and_max_from_set <- frp.set.map(f!([durations]((node,status)) {
|
||||
match status {
|
||||
node::profiling::Status::Finished {duration} => {
|
||||
durations.borrow_mut().insert(*node,OrderedFloat(*duration));
|
||||
},
|
||||
_ => {
|
||||
durations.borrow_mut().remove_by_left(node);
|
||||
},
|
||||
};
|
||||
Self::min_and_max(&durations.borrow())
|
||||
}));
|
||||
|
||||
min_and_max_from_remove <- frp.remove.map(f!([durations](node) {
|
||||
durations.borrow_mut().remove_by_left(node);
|
||||
Self::min_and_max(&durations.borrow())
|
||||
}));
|
||||
|
||||
min_and_max <- any(&min_and_max_from_set,&min_and_max_from_remove);
|
||||
frp.source.min_duration <+ min_and_max._0().on_change();
|
||||
frp.source.max_duration <+ min_and_max._1().on_change();
|
||||
}
|
||||
|
||||
frp.source.min_duration.emit(Self::min_and_max(durations.borrow().deref()).0);
|
||||
frp.source.max_duration.emit(Self::min_and_max(durations.borrow().deref()).1);
|
||||
|
||||
Self { frp, durations }
|
||||
}
|
||||
|
||||
fn min_and_max(durations: &BiBTreeMap<NodeId, OrderedFloat<f32>>) -> (f32, f32) {
|
||||
let mut durations = durations.right_values().copied();
|
||||
|
||||
let min = durations.next().map(OrderedFloat::into_inner).unwrap_or(f32::INFINITY);
|
||||
let max = durations.last().map(OrderedFloat::into_inner).unwrap_or(0.0);
|
||||
(min, max)
|
||||
}
|
||||
}
|
@ -560,11 +560,8 @@ define_themes! { [light:0, dark:1]
|
||||
stripe_gap = 20.0 , 20.0;
|
||||
stripe_angle = 135.0 , 135.0;
|
||||
}
|
||||
profiling {
|
||||
lightness = code::types::lightness , code::types::lightness;
|
||||
chroma = code::types::chroma , code::types::chroma;
|
||||
min_time_hue = 0.38 , 0.38;
|
||||
max_time_hue = 0.07 , 0.07;
|
||||
pending {
|
||||
alpha_factor = 0.5;
|
||||
}
|
||||
type_label {
|
||||
offset_y = -23.0, -23.0;
|
||||
@ -614,10 +611,6 @@ define_themes! { [light:0, dark:1]
|
||||
chroma_factor = 0.8 , 1.0;
|
||||
}
|
||||
}
|
||||
profiling_button {
|
||||
toggled = Lcha(0.7,0.5,0.12,1.0) , Lcha(0.7,0.5,0.12,1.0);
|
||||
toggled_hovered = Lcha(0.55,0.5,0.12,1.0) , Lcha(0.85,0.5,0.12,1.0);
|
||||
}
|
||||
add_node_button {
|
||||
margin = 14.0, 14.0;
|
||||
size = 60.0, 60.0;
|
||||
@ -710,6 +703,7 @@ define_themes! { [light:0, dark:1]
|
||||
/// this style.
|
||||
connected_color = Lcha(1.0,0.0,0.0,1.0);
|
||||
connected_weight = 400.0;
|
||||
pending_alpha = graph_editor::node::pending::alpha_factor;
|
||||
}
|
||||
separator {
|
||||
color = Rgba(0.0, 0.0, 0.0, 0.12);
|
||||
@ -722,6 +716,10 @@ define_themes! { [light:0, dark:1]
|
||||
margin = widget::separator::margin;
|
||||
weight = 400.0;
|
||||
}
|
||||
method {
|
||||
icon_gap = 10.0;
|
||||
pending_icon_alpha = graph_editor::node::pending::alpha_factor;
|
||||
}
|
||||
}
|
||||
colors {
|
||||
dimming {
|
||||
|
Loading…
Reference in New Issue
Block a user