mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 15:52:05 +03:00
Component Group View with static header and without icons (#3373)
Add an initial version of the visual component for displaying the Component Group View. The component contains a header (for displaying the Group Name) and a list of labels (for displaying the component names). https://www.pivotaltracker.com/story/show/181724889 #### Visuals A screenshot from a debug scene demonstrating the component: <img width="251" alt="Screenshot 2022-04-13 at 20 07 56" src="https://user-images.githubusercontent.com/273837/163243304-21c3ad78-4813-4368-b3bb-844d979da699.png"> Screenshots from other debug scenes (`list_view` and `text_area`), demonstrating that the other components still display correctly: <img width="202" alt="Screenshot 2022-04-13 at 20 08 56" src="https://user-images.githubusercontent.com/273837/163243428-de9dc1c7-5a9f-45e0-9325-db60cece9768.png"> <img width="403" alt="Screenshot 2022-04-13 at 20 08 48" src="https://user-images.githubusercontent.com/273837/163243432-895061d9-5bd9-4349-8679-eb63b0f6724d.png"> A screenshot of the Node Searcher's list, showing that long entries in a ListView are now truncated, and an ellipsis character is added in place of removed characters: <img width="651" alt="Screenshot 2022-04-13 at 20 10 16" src="https://user-images.githubusercontent.com/273837/163243664-5b671969-7aa0-4bef-8fd2-825602d85848.png"> # Important Notes - Adding support for the text truncation feature in `ListView` required some changes in the`list_view::Entry`-related APIs. - An embedded font was added (DejaVuSans-Bold) for use in the Component Group View debug scene, and 5 unused embedded fonts were removed.
This commit is contained in:
parent
0ea5dc2a6f
commit
e75df61b2c
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#### Visual Environment
|
#### Visual Environment
|
||||||
|
|
||||||
|
- [Long names on the Node Searcher's list are truncated.][3373] The part of the
|
||||||
|
name that doesn't fit in the Searcher's window is replaced with an ellipsis
|
||||||
|
character ("…").
|
||||||
- [Magnet Alignment algorithm is used while placing new nodes][3366]. When we
|
- [Magnet Alignment algorithm is used while placing new nodes][3366]. When we
|
||||||
find an available free space for a new node, the node gets aligned with the
|
find an available free space for a new node, the node gets aligned with the
|
||||||
surrounding nodes horizontally and vertically. This helps to preserve a nice
|
surrounding nodes horizontally and vertically. This helps to preserve a nice
|
||||||
@ -154,6 +157,7 @@
|
|||||||
[3349]: https://github.com/enso-org/enso/pull/3349
|
[3349]: https://github.com/enso-org/enso/pull/3349
|
||||||
[3361]: https://github.com/enso-org/enso/pull/3361
|
[3361]: https://github.com/enso-org/enso/pull/3361
|
||||||
[3364]: https://github.com/enso-org/enso/pull/3364
|
[3364]: https://github.com/enso-org/enso/pull/3364
|
||||||
|
[3373]: https://github.com/enso-org/enso/pull/3373
|
||||||
[3377]: https://github.com/enso-org/enso/pull/3377
|
[3377]: https://github.com/enso-org/enso/pull/3377
|
||||||
[3366]: https://github.com/enso-org/enso/pull/3366
|
[3366]: https://github.com/enso-org/enso/pull/3366
|
||||||
[3379]: https://github.com/enso-org/enso/pull/3379
|
[3379]: https://github.com/enso-org/enso/pull/3379
|
||||||
|
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -739,6 +739,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "debug-scene-component-group"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"enso-frp",
|
||||||
|
"ensogl-core",
|
||||||
|
"ensogl-hardcoded-theme",
|
||||||
|
"ensogl-list-view",
|
||||||
|
"ensogl-text-msdf-sys",
|
||||||
|
"ide-view-component-group",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "debug-scene-interface"
|
name = "debug-scene-interface"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -941,6 +954,7 @@ dependencies = [
|
|||||||
name = "enso-debug-scene"
|
name = "enso-debug-scene"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"debug-scene-component-group",
|
||||||
"debug-scene-interface",
|
"debug-scene-interface",
|
||||||
"debug-scene-visualization",
|
"debug-scene-visualization",
|
||||||
]
|
]
|
||||||
@ -1706,7 +1720,6 @@ dependencies = [
|
|||||||
"enso-text",
|
"enso-text",
|
||||||
"enso-types",
|
"enso-types",
|
||||||
"ensogl-core",
|
"ensogl-core",
|
||||||
"ensogl-hardcoded-theme",
|
|
||||||
"ensogl-text-embedded-fonts",
|
"ensogl-text-embedded-fonts",
|
||||||
"ensogl-text-msdf-sys",
|
"ensogl-text-msdf-sys",
|
||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
@ -2257,6 +2270,18 @@ dependencies = [
|
|||||||
"welcome-screen",
|
"welcome-screen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ide-view-component-group"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"enso-frp",
|
||||||
|
"ensogl-core",
|
||||||
|
"ensogl-gui-component",
|
||||||
|
"ensogl-hardcoded-theme",
|
||||||
|
"ensogl-list-view",
|
||||||
|
"ensogl-text",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ide-view-graph-editor"
|
name = "ide-view-graph-editor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
11
app/gui/view/component-browser/Cargo.toml
Normal file
11
app/gui/view/component-browser/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "ide-view-component-browser"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Enso Team <contact@enso.org>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ide-view-component-group = { path = "component-group" }
|
17
app/gui/view/component-browser/component-group/Cargo.toml
Normal file
17
app/gui/view/component-browser/component-group/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "ide-view-component-group"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Enso Team <contact@enso.org>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
enso-frp = { version = "0.1.0", path = "../../../../../lib/rust/frp" }
|
||||||
|
ensogl-core = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/core" }
|
||||||
|
ensogl-gui-component = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/gui" }
|
||||||
|
ensogl-hardcoded-theme = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||||
|
ensogl-list-view = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/list-view" }
|
||||||
|
ensogl-text = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/text" }
|
||||||
|
|
250
app/gui/view/component-browser/component-group/src/lib.rs
Normal file
250
app/gui/view/component-browser/component-group/src/lib.rs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
//! This module defines a widget for displaying a list of entries of a component group and the name
|
||||||
|
//! of the component group.
|
||||||
|
//!
|
||||||
|
//! The widget is defined by the [`View`].
|
||||||
|
//!
|
||||||
|
//! To learn more about component groups, see the [Component Browser Design
|
||||||
|
//! Document](https://github.com/enso-org/design/blob/e6cffec2dd6d16688164f04a4ef0d9dff998c3e7/epics/component-browser/design.md).
|
||||||
|
|
||||||
|
// === Standard Linter Configuration ===
|
||||||
|
#![deny(non_ascii_idents)]
|
||||||
|
#![warn(unsafe_code)]
|
||||||
|
// === Non-Standard Linter Configuration ===
|
||||||
|
#![warn(missing_copy_implementations)]
|
||||||
|
#![warn(missing_debug_implementations)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(trivial_casts)]
|
||||||
|
#![warn(trivial_numeric_casts)]
|
||||||
|
#![warn(unused_import_braces)]
|
||||||
|
#![warn(unused_qualifications)]
|
||||||
|
|
||||||
|
use ensogl_core::prelude::*;
|
||||||
|
|
||||||
|
use enso_frp as frp;
|
||||||
|
use ensogl_core::application::Application;
|
||||||
|
use ensogl_core::data::color::Rgba;
|
||||||
|
use ensogl_core::display;
|
||||||
|
use ensogl_core::display::shape::*;
|
||||||
|
use ensogl_gui_component::component;
|
||||||
|
use ensogl_hardcoded_theme::application::component_browser::component_group as theme;
|
||||||
|
use ensogl_list_view as list_view;
|
||||||
|
use ensogl_text as text;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// === Constants ===
|
||||||
|
// =================
|
||||||
|
|
||||||
|
const HEADER_FONT: &str = "DejaVuSans-Bold";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// === Shapes Definitions ===
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
|
||||||
|
// === Background ===
|
||||||
|
|
||||||
|
/// The background of the [`View`].
|
||||||
|
pub mod background {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
ensogl_core::define_shape_system! {
|
||||||
|
below = [list_view::background];
|
||||||
|
(style:Style, color:Vector4) {
|
||||||
|
let sprite_width: Var<Pixels> = "input_size.x".into();
|
||||||
|
let sprite_height: Var<Pixels> = "input_size.y".into();
|
||||||
|
let color = Var::<Rgba>::from(color);
|
||||||
|
// TODO[MC,WD]: We should use Plane here, but it has a bug - renders wrong color. See:
|
||||||
|
// https://github.com/enso-org/enso/pull/3373#discussion_r849054476
|
||||||
|
let shape = Rect((&sprite_width, &sprite_height)).fill(color);
|
||||||
|
shape.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =======================
|
||||||
|
// === Header Geometry ===
|
||||||
|
// =======================
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
struct HeaderGeometry {
|
||||||
|
height: f32,
|
||||||
|
padding_left: f32,
|
||||||
|
padding_right: f32,
|
||||||
|
padding_bottom: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeaderGeometry {
|
||||||
|
fn from_style(style: &StyleWatchFrp, network: &frp::Network) -> frp::Sampler<Self> {
|
||||||
|
let height = style.get_number(theme::header::height);
|
||||||
|
let padding_left = style.get_number(theme::header::padding::left);
|
||||||
|
let padding_right = style.get_number(theme::header::padding::right);
|
||||||
|
let padding_bottom = style.get_number(theme::header::padding::bottom);
|
||||||
|
|
||||||
|
frp::extend! { network
|
||||||
|
init <- source_();
|
||||||
|
theme <- all_with5(&init,&height,&padding_left,&padding_right,&padding_bottom,
|
||||||
|
|_,&height,&padding_left,&padding_right,&padding_bottom|
|
||||||
|
Self{height,padding_left,padding_right,padding_bottom}
|
||||||
|
);
|
||||||
|
theme_sampler <- theme.sampler();
|
||||||
|
}
|
||||||
|
init.emit(());
|
||||||
|
theme_sampler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===========
|
||||||
|
// === FRP ===
|
||||||
|
// ===========
|
||||||
|
|
||||||
|
ensogl_core::define_endpoints_2! {
|
||||||
|
Input {
|
||||||
|
set_header(String),
|
||||||
|
set_entries(list_view::entry::AnyModelProvider<list_view::entry::Label>),
|
||||||
|
set_background_color(Rgba),
|
||||||
|
set_size(Vector2),
|
||||||
|
}
|
||||||
|
Output {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl component::Frp<Model> for Frp {
|
||||||
|
fn init(api: &Self::Private, _app: &Application, model: &Model, style: &StyleWatchFrp) {
|
||||||
|
let network = &api.network;
|
||||||
|
let input = &api.input;
|
||||||
|
let header_text_size = style.get_number(theme::header::text::size);
|
||||||
|
frp::extend! { network
|
||||||
|
|
||||||
|
// === Geometry ===
|
||||||
|
|
||||||
|
let header_geometry = HeaderGeometry::from_style(style, network);
|
||||||
|
size_and_header_geometry <- all(&input.set_size, &header_geometry);
|
||||||
|
eval size_and_header_geometry(((size, hdr_geom)) model.resize(*size, *hdr_geom));
|
||||||
|
|
||||||
|
|
||||||
|
// === Header ===
|
||||||
|
|
||||||
|
init <- source_();
|
||||||
|
header_text_size <- all(&header_text_size, &init)._0();
|
||||||
|
model.header.set_default_text_size <+ header_text_size.map(|v| text::Size(*v));
|
||||||
|
_set_header <- input.set_header.map2(&size_and_header_geometry, f!(
|
||||||
|
(text, (size, hdr_geom)) {
|
||||||
|
model.header_text.replace(text.clone());
|
||||||
|
model.update_header_width(*size, *hdr_geom);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
eval input.set_background_color((c)
|
||||||
|
model.background.color.set(c.into()));
|
||||||
|
|
||||||
|
|
||||||
|
// === Entries ===
|
||||||
|
|
||||||
|
model.entries.set_background_color(HOVER_COLOR);
|
||||||
|
model.entries.show_background_shadow(false);
|
||||||
|
model.entries.set_background_corners_radius(0.0);
|
||||||
|
model.entries.set_background_color <+ input.set_background_color;
|
||||||
|
model.entries.set_entries <+ input.set_entries;
|
||||||
|
}
|
||||||
|
init.emit(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// === Model ===
|
||||||
|
// =============
|
||||||
|
|
||||||
|
/// The Model of the [`View`] component.
|
||||||
|
#[derive(Clone, CloneRef, Debug)]
|
||||||
|
pub struct Model {
|
||||||
|
display_object: display::object::Instance,
|
||||||
|
header: text::Area,
|
||||||
|
header_text: Rc<RefCell<String>>,
|
||||||
|
background: background::View,
|
||||||
|
entries: list_view::ListView<list_view::entry::Label>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl display::Object for Model {
|
||||||
|
fn display_object(&self) -> &display::object::Instance {
|
||||||
|
&self.display_object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl component::Model for Model {
|
||||||
|
fn label() -> &'static str {
|
||||||
|
"ComponentGroup"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(app: &Application, logger: &Logger) -> Self {
|
||||||
|
let header_text = default();
|
||||||
|
let display_object = display::object::Instance::new(&logger);
|
||||||
|
let background = background::View::new(&logger);
|
||||||
|
let header = text::Area::new(app);
|
||||||
|
let entries = list_view::ListView::new(app);
|
||||||
|
display_object.add_child(&background);
|
||||||
|
display_object.add_child(&header);
|
||||||
|
display_object.add_child(&entries);
|
||||||
|
|
||||||
|
header.set_font(HEADER_FONT);
|
||||||
|
let label_layer = &app.display.default_scene.layers.label;
|
||||||
|
header.add_to_scene_layer(label_layer);
|
||||||
|
|
||||||
|
Model { display_object, header, header_text, background, entries }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
fn resize(&self, size: Vector2, header_geometry: HeaderGeometry) {
|
||||||
|
// === Background ===
|
||||||
|
|
||||||
|
self.background.size.set(size);
|
||||||
|
|
||||||
|
|
||||||
|
// === Header Text ===
|
||||||
|
|
||||||
|
let header_padding_left = header_geometry.padding_left;
|
||||||
|
let header_text_x = -size.x / 2.0 + header_padding_left;
|
||||||
|
let header_text_height = self.header.height.value();
|
||||||
|
let header_padding_bottom = header_geometry.padding_bottom;
|
||||||
|
let header_height = header_geometry.height;
|
||||||
|
let header_bottom_y = size.y / 2.0 - header_height;
|
||||||
|
let header_text_y = header_bottom_y + header_text_height + header_padding_bottom;
|
||||||
|
self.header.set_position_xy(Vector2(header_text_x, header_text_y));
|
||||||
|
self.update_header_width(size, header_geometry);
|
||||||
|
|
||||||
|
|
||||||
|
// === Entries ===
|
||||||
|
|
||||||
|
self.entries.resize(size - Vector2(0.0, header_height));
|
||||||
|
self.entries.set_position_y(-header_height / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_header_width(&self, size: Vector2, header_geometry: HeaderGeometry) {
|
||||||
|
let header_padding_left = header_geometry.padding_left;
|
||||||
|
let header_padding_right = header_geometry.padding_right;
|
||||||
|
let max_text_width = size.x - header_padding_left - header_padding_right;
|
||||||
|
self.header.set_content_truncated(self.header_text.borrow().clone(), max_text_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ============
|
||||||
|
// === View ===
|
||||||
|
// ============
|
||||||
|
|
||||||
|
/// A widget for displaying the entries and name of a component group.
|
||||||
|
///
|
||||||
|
/// The widget is rendered as a header label, a list of entries below it, and a colored background.
|
||||||
|
///
|
||||||
|
/// To learn more about component groups, see the [Component Browser Design
|
||||||
|
/// Document](https://github.com/enso-org/design/blob/e6cffec2dd6d16688164f04a4ef0d9dff998c3e7/epics/component-browser/design.md).
|
||||||
|
pub type View = component::ComponentView<Model, Frp>;
|
19
app/gui/view/component-browser/src/lib.rs
Normal file
19
app/gui/view/component-browser/src/lib.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// === Standard Linter Configuration ===
|
||||||
|
#![deny(non_ascii_idents)]
|
||||||
|
#![warn(unsafe_code)]
|
||||||
|
// === Non-Standard Linter Configuration ===
|
||||||
|
#![warn(missing_copy_implementations)]
|
||||||
|
#![warn(missing_debug_implementations)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(trivial_casts)]
|
||||||
|
#![warn(trivial_numeric_casts)]
|
||||||
|
#![warn(unused_import_braces)]
|
||||||
|
#![warn(unused_qualifications)]
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Export ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
pub use ide_view_component_group as component_group;
|
||||||
|
|
@ -8,5 +8,6 @@ edition = "2021"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
debug-scene-component-group = { path = "component-group" }
|
||||||
debug-scene-interface = { path = "interface" }
|
debug-scene-interface = { path = "interface" }
|
||||||
debug-scene-visualization = { path = "visualization" }
|
debug-scene-visualization = { path = "visualization" }
|
||||||
|
17
app/gui/view/debug_scene/component-group/Cargo.toml
Normal file
17
app/gui/view/debug_scene/component-group/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "debug-scene-component-group"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Enso Team <contact@enso.org>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
enso-frp = { path = "../../../../../lib/rust/frp" }
|
||||||
|
ensogl-core = { path = "../../../../../lib/rust/ensogl/core" }
|
||||||
|
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||||
|
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
|
||||||
|
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||||
|
ide-view-component-group = { path = "../../component-browser/component-group" }
|
||||||
|
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
106
app/gui/view/debug_scene/component-group/src/lib.rs
Normal file
106
app/gui/view/debug_scene/component-group/src/lib.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
//! A debug scene which shows the Component Group visual component.
|
||||||
|
|
||||||
|
// === Standard Linter Configuration ===
|
||||||
|
#![deny(non_ascii_idents)]
|
||||||
|
#![warn(unsafe_code)]
|
||||||
|
// === Non-Standard Linter Configuration ===
|
||||||
|
#![warn(missing_copy_implementations)]
|
||||||
|
#![warn(missing_debug_implementations)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(trivial_casts)]
|
||||||
|
#![warn(trivial_numeric_casts)]
|
||||||
|
#![warn(unused_import_braces)]
|
||||||
|
#![warn(unused_qualifications)]
|
||||||
|
|
||||||
|
use ensogl_core::prelude::*;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use ensogl_core::application::Application;
|
||||||
|
use ensogl_core::data::color;
|
||||||
|
use ensogl_core::display::object::ObjectOps;
|
||||||
|
use ensogl_hardcoded_theme as theme;
|
||||||
|
use ensogl_list_view as list_view;
|
||||||
|
use ensogl_text_msdf_sys::run_once_initialized;
|
||||||
|
use ide_view_component_group as component_group;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===================
|
||||||
|
// === Entry Point ===
|
||||||
|
// ===================
|
||||||
|
|
||||||
|
/// An entry point.
|
||||||
|
#[entry_point]
|
||||||
|
pub fn main() {
|
||||||
|
run_once_initialized(|| {
|
||||||
|
let app = Application::new("root");
|
||||||
|
init(&app);
|
||||||
|
mem::forget(app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// === Mock Entries ===
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct MockEntries {
|
||||||
|
entries: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockEntries {
|
||||||
|
fn new(entries: Vec<String>) -> Self {
|
||||||
|
Self { entries }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entry(&self, i: usize) -> Option<String> {
|
||||||
|
self.entries.get(i).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl list_view::entry::ModelProvider<list_view::entry::Label> for MockEntries {
|
||||||
|
fn entry_count(&self) -> usize {
|
||||||
|
self.entries.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, id: usize) -> Option<String> {
|
||||||
|
self.get_entry(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// === Init Application ===
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
fn init(app: &Application) {
|
||||||
|
theme::builtin::dark::register(&app);
|
||||||
|
theme::builtin::light::register(&app);
|
||||||
|
theme::builtin::light::enable(&app);
|
||||||
|
|
||||||
|
let mock_entries = MockEntries::new(vec![
|
||||||
|
"long sample entry with text overflowing the width".into(),
|
||||||
|
"convert".into(),
|
||||||
|
"table input".into(),
|
||||||
|
"text input".into(),
|
||||||
|
"number input".into(),
|
||||||
|
"table input".into(),
|
||||||
|
"data output".into(),
|
||||||
|
"data input".into(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
let component_group = app.new_view::<component_group::View>();
|
||||||
|
let provider = list_view::entry::AnyModelProvider::new(mock_entries);
|
||||||
|
let group_name = "Long group name with text overflowing the width";
|
||||||
|
component_group.set_header(group_name.to_string());
|
||||||
|
component_group.set_entries(provider);
|
||||||
|
component_group.set_size(Vector2(150.0, 200.0));
|
||||||
|
component_group.set_background_color(color::Rgba(0.927, 0.937, 0.913, 1.0));
|
||||||
|
app.display.add_child(&component_group);
|
||||||
|
|
||||||
|
std::mem::forget(component_group);
|
||||||
|
}
|
@ -45,9 +45,9 @@ use uuid::Uuid;
|
|||||||
const STUB_MODULE: &str = "from Base import all\n\nmain = IO.println \"Hello\"\n";
|
const STUB_MODULE: &str = "from Base import all\n\nmain = IO.println \"Hello\"\n";
|
||||||
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[entry_point]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn entry_point_interface() {
|
pub fn main() {
|
||||||
run_once_initialized(|| {
|
run_once_initialized(|| {
|
||||||
let app = Application::new("root");
|
let app = Application::new("root");
|
||||||
init(&app);
|
init(&app);
|
||||||
|
@ -20,5 +20,6 @@
|
|||||||
// === Export ===
|
// === Export ===
|
||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
|
pub use debug_scene_component_group as component_group;
|
||||||
pub use debug_scene_interface as interface;
|
pub use debug_scene_interface as interface;
|
||||||
pub use debug_scene_visualization as visualization;
|
pub use debug_scene_visualization as visualization;
|
||||||
|
@ -92,9 +92,9 @@ fn constructor_graph() -> visualization::java_script::Definition {
|
|||||||
visualization::java_script::Definition::new_builtin(sources).unwrap()
|
visualization::java_script::Definition::new_builtin(sources).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[entry_point]
|
||||||
#[allow(dead_code, missing_docs)]
|
#[allow(dead_code, missing_docs)]
|
||||||
pub fn entry_point_visualization() {
|
pub fn main() {
|
||||||
run_once_initialized(|| {
|
run_once_initialized(|| {
|
||||||
let app = Application::new("root");
|
let app = Application::new("root");
|
||||||
init(&app);
|
init(&app);
|
||||||
|
@ -198,7 +198,7 @@ commands.build.rust = async function (argv) {
|
|||||||
console.log('Minimizing the WASM binary.')
|
console.log('Minimizing the WASM binary.')
|
||||||
await gzip(paths.wasm.main, paths.wasm.mainGz)
|
await gzip(paths.wasm.main, paths.wasm.mainGz)
|
||||||
|
|
||||||
const limitMb = 4.67
|
const limitMb = 4.05
|
||||||
await checkWasmSize(paths.wasm.mainGz, limitMb)
|
await checkWasmSize(paths.wasm.mainGz, limitMb)
|
||||||
}
|
}
|
||||||
// Copy WASM files from temporary directory to Webpack's `dist` directory.
|
// Copy WASM files from temporary directory to Webpack's `dist` directory.
|
||||||
|
@ -179,6 +179,21 @@ define_themes! { [light:0, dark:1]
|
|||||||
hide_delay_duration_ms = 150.0, 150.0;
|
hide_delay_duration_ms = 150.0, 150.0;
|
||||||
show_delay_duration_ms = 150.0, 150.0;
|
show_delay_duration_ms = 150.0, 150.0;
|
||||||
}
|
}
|
||||||
|
component_browser {
|
||||||
|
component_group {
|
||||||
|
header {
|
||||||
|
text {
|
||||||
|
size = 12.0, 12.0;
|
||||||
|
}
|
||||||
|
height = 30.0, 30.0;
|
||||||
|
padding {
|
||||||
|
left = 16.5, 16.5;
|
||||||
|
right = 2.5, 2.5;
|
||||||
|
bottom = 5.0, 5.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
searcher {
|
searcher {
|
||||||
action_list_gap = 10.0, 10.0;
|
action_list_gap = 10.0, 10.0;
|
||||||
padding = 5.0, 5.0;
|
padding = 5.0, 5.0;
|
||||||
|
@ -66,6 +66,9 @@ pub trait Entry: CloneRef + Debug + display::Object + 'static {
|
|||||||
/// Update content with new model.
|
/// Update content with new model.
|
||||||
fn update(&self, model: &Self::Model);
|
fn update(&self, model: &Self::Model);
|
||||||
|
|
||||||
|
/// Resize the entry's view to fit a new width.
|
||||||
|
fn set_max_width(&self, max_width_px: f32);
|
||||||
|
|
||||||
/// Set the layer of all [`text::Area`] components inside. The [`text::Area`] component is
|
/// Set the layer of all [`text::Area`] components inside. The [`text::Area`] component is
|
||||||
/// handled in a special way, and is often in different layer than shapes. See TODO comment
|
/// handled in a special way, and is often in different layer than shapes. See TODO comment
|
||||||
/// in [`text::Area::add_to_scene_layer`] method.
|
/// in [`text::Area::add_to_scene_layer`] method.
|
||||||
@ -84,10 +87,19 @@ pub trait Entry: CloneRef + Debug + display::Object + 'static {
|
|||||||
pub struct Label {
|
pub struct Label {
|
||||||
display_object: display::object::Instance,
|
display_object: display::object::Instance,
|
||||||
label: text::Area,
|
label: text::Area,
|
||||||
|
text: Rc<RefCell<String>>,
|
||||||
|
max_width_px: Rc<Cell<f32>>,
|
||||||
network: enso_frp::Network,
|
network: enso_frp::Network,
|
||||||
style_watch: StyleWatchFrp,
|
style_watch: StyleWatchFrp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
fn update_label_content(&self) {
|
||||||
|
let text = self.text.borrow().clone();
|
||||||
|
self.label.set_content_truncated(text, self.max_width_px.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Entry for Label {
|
impl Entry for Label {
|
||||||
type Model = String;
|
type Model = String;
|
||||||
|
|
||||||
@ -95,6 +107,8 @@ impl Entry for Label {
|
|||||||
let logger = Logger::new("list_view::entry::Label");
|
let logger = Logger::new("list_view::entry::Label");
|
||||||
let display_object = display::object::Instance::new(logger);
|
let display_object = display::object::Instance::new(logger);
|
||||||
let label = app.new_view::<ensogl_text::Area>();
|
let label = app.new_view::<ensogl_text::Area>();
|
||||||
|
let text = default();
|
||||||
|
let max_width_px = default();
|
||||||
let network = frp::Network::new("list_view::entry::Label");
|
let network = frp::Network::new("list_view::entry::Label");
|
||||||
let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||||
let color = style_watch.get_color(theme::widget::list_view::text);
|
let color = style_watch.get_color(theme::widget::list_view::text);
|
||||||
@ -111,11 +125,19 @@ impl Entry for Label {
|
|||||||
eval size ((size) label.set_position_y(size/2.0));
|
eval size ((size) label.set_position_y(size/2.0));
|
||||||
}
|
}
|
||||||
init.emit(());
|
init.emit(());
|
||||||
Self { display_object, label, network, style_watch }
|
Self { display_object, label, text, max_width_px, network, style_watch }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, model: &Self::Model) {
|
fn update(&self, model: &Self::Model) {
|
||||||
self.label.set_content(model);
|
self.text.replace(model.clone());
|
||||||
|
self.update_label_content();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_max_width(&self, max_width_px: f32) {
|
||||||
|
if self.max_width_px.get() != max_width_px {
|
||||||
|
self.max_width_px.set(max_width_px);
|
||||||
|
self.update_label_content();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_label_layer(&self, label_layer: &display::scene::Layer) {
|
fn set_label_layer(&self, label_layer: &display::scene::Layer) {
|
||||||
@ -176,6 +198,10 @@ impl Entry for GlyphHighlightedLabel {
|
|||||||
self.highlight.emit(&model.highlighted);
|
self.highlight.emit(&model.highlighted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_max_width(&self, max_width_px: f32) {
|
||||||
|
self.inner.set_max_width(max_width_px);
|
||||||
|
}
|
||||||
|
|
||||||
fn set_label_layer(&self, layer: &display::scene::Layer) {
|
fn set_label_layer(&self, layer: &display::scene::Layer) {
|
||||||
self.inner.set_label_layer(layer);
|
self.inner.set_label_layer(layer);
|
||||||
}
|
}
|
||||||
|
@ -127,8 +127,9 @@ where E::Model: Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update displayed entries to show the given range.
|
/// Update displayed entries to show the given range and limit their display width to at most
|
||||||
pub fn update_entries(&self, mut range: Range<entry::Id>) {
|
/// `max_width_px`.
|
||||||
|
pub fn update_entries(&self, mut range: Range<entry::Id>, max_width_px: f32) {
|
||||||
range.end = range.end.min(self.provider.get().entry_count());
|
range.end = range.end.min(self.provider.get().entry_count());
|
||||||
if range != self.entries_range.get() {
|
if range != self.entries_range.get() {
|
||||||
debug!(self.logger, "Update entries for {range:?}");
|
debug!(self.logger, "Update entries for {range:?}");
|
||||||
@ -152,13 +153,18 @@ where E::Model: Default
|
|||||||
});
|
});
|
||||||
self.entries_range.set(range);
|
self.entries_range.set(range);
|
||||||
}
|
}
|
||||||
|
for entry in self.entries.borrow().iter() {
|
||||||
|
entry.entry.set_max_width(max_width_px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update displayed entries, giving new provider.
|
/// Update displayed entries, giving new provider. New entries created by the function have
|
||||||
|
/// their maximum width set to `max_width_px`.
|
||||||
pub fn update_entries_new_provider(
|
pub fn update_entries_new_provider(
|
||||||
&self,
|
&self,
|
||||||
provider: impl Into<entry::AnyModelProvider<E>> + 'static,
|
provider: impl Into<entry::AnyModelProvider<E>> + 'static,
|
||||||
mut range: Range<entry::Id>,
|
mut range: Range<entry::Id>,
|
||||||
|
max_width_px: f32,
|
||||||
) {
|
) {
|
||||||
const MAX_SAFE_ENTRIES_COUNT: usize = 1000;
|
const MAX_SAFE_ENTRIES_COUNT: usize = 1000;
|
||||||
let provider = provider.into();
|
let provider = provider.into();
|
||||||
@ -173,7 +179,12 @@ where E::Model: Default
|
|||||||
range.end = range.end.min(provider.entry_count());
|
range.end = range.end.min(provider.entry_count());
|
||||||
let models = range.clone().map(|id| (id, provider.get(id)));
|
let models = range.clone().map(|id| (id, provider.get(id)));
|
||||||
let mut entries = self.entries.borrow_mut();
|
let mut entries = self.entries.borrow_mut();
|
||||||
entries.resize_with(range.len(), || self.create_new_entry());
|
let create_new_entry_with_max_width = || {
|
||||||
|
let entry = self.create_new_entry();
|
||||||
|
entry.entry.set_max_width(max_width_px);
|
||||||
|
entry
|
||||||
|
};
|
||||||
|
entries.resize_with(range.len(), create_new_entry_with_max_width);
|
||||||
for (entry, (id, model)) in entries.iter().zip(models) {
|
for (entry, (id, model)) in entries.iter().zip(models) {
|
||||||
Self::update_entry(&self.logger, entry, id, &model);
|
Self::update_entry(&self.logger, entry, id, &model);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ use enso_frp as frp;
|
|||||||
use ensogl_core::application;
|
use ensogl_core::application;
|
||||||
use ensogl_core::application::shortcut;
|
use ensogl_core::application::shortcut;
|
||||||
use ensogl_core::application::Application;
|
use ensogl_core::application::Application;
|
||||||
|
use ensogl_core::data::color;
|
||||||
use ensogl_core::display;
|
use ensogl_core::display;
|
||||||
use ensogl_core::display::scene::layer::LayerId;
|
use ensogl_core::display::scene::layer::LayerId;
|
||||||
use ensogl_core::display::shape::*;
|
use ensogl_core::display::shape::*;
|
||||||
@ -98,16 +99,16 @@ pub mod background {
|
|||||||
|
|
||||||
ensogl_core::define_shape_system! {
|
ensogl_core::define_shape_system! {
|
||||||
below = [selection];
|
below = [selection];
|
||||||
(style:Style) {
|
(style: Style, shadow_alpha: f32, corners_radius_px: f32, color: Vector4) {
|
||||||
let sprite_width : Var<Pixels> = "input_size.x".into();
|
let sprite_width : Var<Pixels> = "input_size.x".into();
|
||||||
let sprite_height : Var<Pixels> = "input_size.y".into();
|
let sprite_height : Var<Pixels> = "input_size.y".into();
|
||||||
let width = sprite_width - SHADOW_PX.px() * 2.0 - SHAPE_PADDING.px() * 2.0;
|
let width = sprite_width - SHADOW_PX.px() * 2.0 - SHAPE_PADDING.px() * 2.0;
|
||||||
let height = sprite_height - SHADOW_PX.px() * 2.0 - SHAPE_PADDING.px() * 2.0;
|
let height = sprite_height - SHADOW_PX.px() * 2.0 - SHAPE_PADDING.px() * 2.0;
|
||||||
let color = style.get_color(theme::widget::list_view::background);
|
let color = Var::<color::Rgba>::from(color);
|
||||||
let rect = Rect((&width,&height)).corners_radius(CORNER_RADIUS_PX.px());
|
let rect = Rect((&width,&height)).corners_radius(corners_radius_px);
|
||||||
let shape = rect.fill(color);
|
let shape = rect.fill(color);
|
||||||
|
|
||||||
let shadow = shadow::from_shape(rect.into(),style);
|
let shadow = shadow::from_shape_with_alpha(rect.into(), &shadow_alpha, style);
|
||||||
|
|
||||||
(shadow + shape).into()
|
(shadow + shape).into()
|
||||||
}
|
}
|
||||||
@ -154,6 +155,11 @@ impl<E: Entry> Model<E> {
|
|||||||
Model { app, entries, selection, background, scrolled_area, display_object }
|
Model { app, entries, selection, background, scrolled_area, display_object }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_background_shadow(&self, value: bool) {
|
||||||
|
let alpha = if value { 1.0 } else { 0.0 };
|
||||||
|
self.background.shadow_alpha.set(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
fn padding(&self) -> f32 {
|
fn padding(&self) -> f32 {
|
||||||
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
|
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
|
||||||
// system (#795)
|
// system (#795)
|
||||||
@ -161,23 +167,29 @@ impl<E: Entry> Model<E> {
|
|||||||
styles.get_number(ensogl_hardcoded_theme::application::searcher::padding)
|
styles.get_number(ensogl_hardcoded_theme::application::searcher::padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn doubled_padding_with_shape_padding(&self) -> f32 {
|
||||||
|
2.0 * self.padding() + SHAPE_PADDING
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the displayed entries list when _view_ has changed - the list was scrolled or
|
/// Update the displayed entries list when _view_ has changed - the list was scrolled or
|
||||||
/// resized.
|
/// resized.
|
||||||
fn update_after_view_change(&self, view: &View) {
|
fn update_after_view_change(&self, view: &View) {
|
||||||
let visible_entries = Self::visible_entries(view, self.entries.entry_count());
|
let visible_entries = Self::visible_entries(view, self.entries.entry_count());
|
||||||
let padding_px = self.padding();
|
let padding = self.doubled_padding_with_shape_padding();
|
||||||
let padding = 2.0 * padding_px + SHAPE_PADDING;
|
|
||||||
let padding = Vector2(padding, padding);
|
let padding = Vector2(padding, padding);
|
||||||
|
let entry_width = view.size.x - padding.x;
|
||||||
let shadow = Vector2(2.0 * SHADOW_PX, 2.0 * SHADOW_PX);
|
let shadow = Vector2(2.0 * SHADOW_PX, 2.0 * SHADOW_PX);
|
||||||
self.entries.set_position_x(-view.size.x / 2.0);
|
self.entries.set_position_x(-view.size.x / 2.0);
|
||||||
self.background.size.set(view.size + padding + shadow);
|
self.background.size.set(view.size + padding + shadow);
|
||||||
self.scrolled_area.set_position_y(view.size.y / 2.0 - view.position_y);
|
self.scrolled_area.set_position_y(view.size.y / 2.0 - view.position_y);
|
||||||
self.entries.update_entries(visible_entries);
|
self.entries.update_entries(visible_entries, entry_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_entries(&self, provider: entry::AnyModelProvider<E>, view: &View) {
|
fn set_entries(&self, provider: entry::AnyModelProvider<E>, view: &View) {
|
||||||
let visible_entries = Self::visible_entries(view, provider.entry_count());
|
let visible_entries = Self::visible_entries(view, provider.entry_count());
|
||||||
self.entries.update_entries_new_provider(provider, visible_entries);
|
let padding = self.doubled_padding_with_shape_padding();
|
||||||
|
let entry_width = view.size.x - padding;
|
||||||
|
self.entries.update_entries_new_provider(provider, visible_entries, entry_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visible_entries(View { position_y, size }: &View, entry_count: usize) -> Range<entry::Id> {
|
fn visible_entries(View { position_y, size }: &View, entry_count: usize) -> Range<entry::Id> {
|
||||||
@ -255,6 +267,9 @@ ensogl_core::define_endpoints! {
|
|||||||
set_entries(entry::AnyModelProvider<E>),
|
set_entries(entry::AnyModelProvider<E>),
|
||||||
select_entry(entry::Id),
|
select_entry(entry::Id),
|
||||||
chose_entry(entry::Id),
|
chose_entry(entry::Id),
|
||||||
|
show_background_shadow(bool),
|
||||||
|
set_background_corners_radius(f32),
|
||||||
|
set_background_color(color::Rgba),
|
||||||
}
|
}
|
||||||
|
|
||||||
Output {
|
Output {
|
||||||
@ -311,9 +326,28 @@ where E::Model: Default
|
|||||||
let view_y = DEPRECATED_Animation::<f32>::new(network);
|
let view_y = DEPRECATED_Animation::<f32>::new(network);
|
||||||
let selection_y = DEPRECATED_Animation::<f32>::new(network);
|
let selection_y = DEPRECATED_Animation::<f32>::new(network);
|
||||||
let selection_height = DEPRECATED_Animation::<f32>::new(network);
|
let selection_height = DEPRECATED_Animation::<f32>::new(network);
|
||||||
|
let style = StyleWatchFrp::new(&scene.style_sheet);
|
||||||
|
use theme::widget::list_view as list_view_style;
|
||||||
|
let default_background_color = style.get_color(list_view_style::background);
|
||||||
|
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
|
|
||||||
|
// === Background ===
|
||||||
|
|
||||||
|
init <- source_();
|
||||||
|
default_show_background_shadow <- init.constant(true);
|
||||||
|
show_background_shadow <- any(
|
||||||
|
&default_show_background_shadow,&frp.show_background_shadow);
|
||||||
|
eval show_background_shadow ((t) model.show_background_shadow(*t));
|
||||||
|
default_background_corners_radius <- init.constant(background::CORNER_RADIUS_PX);
|
||||||
|
background_corners_radius <- any(
|
||||||
|
&default_background_corners_radius,&frp.set_background_corners_radius);
|
||||||
|
eval background_corners_radius ((px) model.background.corners_radius_px.set(*px));
|
||||||
|
default_background_color <- all(&default_background_color,&init)._0();
|
||||||
|
background_color <- any(&default_background_color,&frp.set_background_color);
|
||||||
|
eval background_color ((color) model.background.color.set(color.into()));
|
||||||
|
|
||||||
|
|
||||||
// === Mouse Position ===
|
// === Mouse Position ===
|
||||||
|
|
||||||
mouse_in <- all_with(&mouse.position,&frp.size,f!((pos,size)
|
mouse_in <- all_with(&mouse.position,&frp.size,f!((pos,size)
|
||||||
@ -329,6 +363,7 @@ where E::Model: Default
|
|||||||
|
|
||||||
|
|
||||||
// === Selected Entry ===
|
// === Selected Entry ===
|
||||||
|
|
||||||
frp.source.selected_entry <+ frp.select_entry.map(|id| Some(*id));
|
frp.source.selected_entry <+ frp.select_entry.map(|id| Some(*id));
|
||||||
|
|
||||||
selection_jump_on_one_up <- frp.move_selection_up.constant(-1);
|
selection_jump_on_one_up <- frp.move_selection_up.constant(-1);
|
||||||
@ -452,6 +487,7 @@ where E::Model: Default
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init.emit(());
|
||||||
view_y.set_target_value(MAX_SCROLL);
|
view_y.set_target_value(MAX_SCROLL);
|
||||||
view_y.skip();
|
view_y.skip();
|
||||||
frp.scroll_jump(MAX_SCROLL);
|
frp.scroll_jump(MAX_SCROLL);
|
||||||
|
@ -16,7 +16,6 @@ enso-types = { path = "../../../types" }
|
|||||||
ensogl-core = { path = "../../core" }
|
ensogl-core = { path = "../../core" }
|
||||||
ensogl-text-embedded-fonts = { path = "embedded-fonts" }
|
ensogl-text-embedded-fonts = { path = "embedded-fonts" }
|
||||||
ensogl-text-msdf-sys = { path = "msdf-sys" }
|
ensogl-text-msdf-sys = { path = "msdf-sys" }
|
||||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
|
||||||
const_format = "0.2.22"
|
const_format = "0.2.22"
|
||||||
xi-rope = { version = "0.3.0" }
|
xi-rope = { version = "0.3.0" }
|
||||||
|
|
||||||
|
@ -75,16 +75,8 @@ mod deja_vu {
|
|||||||
std::io::copy(&mut input_stream, &mut output_stream).unwrap();
|
std::io::copy(&mut input_stream, &mut output_stream).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const FONTS_TO_EXTRACT: &[&str] = &[
|
pub const FONTS_TO_EXTRACT: &[&str] =
|
||||||
"DejaVuSans",
|
&["DejaVuSans", "DejaVuSans-Bold", "DejaVuSansMono", "DejaVuSansMono-Bold"];
|
||||||
"DejaVuSans-ExtraLight",
|
|
||||||
"DejaVuSansMono",
|
|
||||||
"DejaVuSansMono-Bold",
|
|
||||||
"DejaVuSansMono-Oblique",
|
|
||||||
"DejaVuSansCondensed",
|
|
||||||
"DejaVuSerif",
|
|
||||||
"DejaVuSerifCondensed",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn extract_all_fonts(package_path: &path::Path) {
|
pub fn extract_all_fonts(package_path: &path::Path) {
|
||||||
for font_name in FONTS_TO_EXTRACT {
|
for font_name in FONTS_TO_EXTRACT {
|
||||||
|
@ -271,6 +271,12 @@ ensogl_core::define_endpoints! {
|
|||||||
/// MSDF texture, etc.).
|
/// MSDF texture, etc.).
|
||||||
set_font (String),
|
set_font (String),
|
||||||
set_content (String),
|
set_content (String),
|
||||||
|
/// Set content, truncating the trailing characters on every line to fit a width in pixels
|
||||||
|
/// when rendered with current font and font size. The truncated substrings are replaced
|
||||||
|
/// with an ellipsis character ("…").
|
||||||
|
///
|
||||||
|
/// Unix (`\n`) and MS-DOS (`\r\n`) style line endings are recognized.
|
||||||
|
set_content_truncated (String, f32),
|
||||||
}
|
}
|
||||||
Output {
|
Output {
|
||||||
pointer_style (cursor::Style),
|
pointer_style (cursor::Style),
|
||||||
@ -480,6 +486,10 @@ impl Area {
|
|||||||
input.insert(s);
|
input.insert(s);
|
||||||
input.remove_all_cursors();
|
input.remove_all_cursors();
|
||||||
});
|
});
|
||||||
|
input.set_content <+ input.set_content_truncated.map(f!(((text, max_width_px)) {
|
||||||
|
m.text_truncated_with_ellipsis(text.clone(), m.default_font_size(), *max_width_px)
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
// === Font ===
|
// === Font ===
|
||||||
|
|
||||||
@ -871,6 +881,86 @@ impl AreaModel {
|
|||||||
last_offset - cursor_offset
|
last_offset - cursor_offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn line_truncated_with_ellipsis(&self, line: &str, _: style::Size, _: f32) -> String {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Truncate a `line` of text if its length on screen exceeds `max_width_px` when rendered
|
||||||
|
/// using the current font at `font_size`. Return the truncated string with an ellipsis ("…")
|
||||||
|
/// character appended, or `content` if not truncated.
|
||||||
|
///
|
||||||
|
/// The truncation point is chosen such that the resulting string with ellipsis will fit in
|
||||||
|
/// `max_width_px` if possible. The `line` must not contain newline characters.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn line_truncated_with_ellipsis(
|
||||||
|
&self,
|
||||||
|
line: &str,
|
||||||
|
font_size: style::Size,
|
||||||
|
max_width_px: f32,
|
||||||
|
) -> String {
|
||||||
|
const ELLIPSIS: char = '\u{2026}';
|
||||||
|
let mut pen = pen::Pen::new(&self.glyph_system.borrow().font);
|
||||||
|
let mut truncation_point = 0.bytes();
|
||||||
|
let truncate = line.char_indices().any(|(i, ch)| {
|
||||||
|
let char_info = pen::CharInfo::new(ch, font_size.raw);
|
||||||
|
let pen_info = pen.advance(Some(char_info));
|
||||||
|
let next_width = pen_info.offset + char_info.size;
|
||||||
|
if next_width > max_width_px {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let width_of_ellipsis = pen::CharInfo::new(ELLIPSIS, font_size.raw).size;
|
||||||
|
let char_length: Bytes = ch.len_utf8().into();
|
||||||
|
if next_width + width_of_ellipsis <= max_width_px {
|
||||||
|
truncation_point = Bytes::from(i) + char_length;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
});
|
||||||
|
if truncate {
|
||||||
|
let truncated_content = line[..truncation_point.as_usize()].to_string();
|
||||||
|
truncated_content + String::from(ELLIPSIS).as_str()
|
||||||
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Truncate trailing characters on every line of `text` that exceeds `max_width_px` when
|
||||||
|
/// rendered using the current font at `font_size`. Return `text` with every truncated
|
||||||
|
/// substring replaced with an ellipsis character ("…").
|
||||||
|
///
|
||||||
|
/// The truncation point of every line is chosen such that the truncated string with ellipsis
|
||||||
|
/// will fit in `max_width_px` if possible. Unix (`\n`) and MS-DOS (`\r\n`) style line endings
|
||||||
|
/// are recognized and preserved in the returned string.
|
||||||
|
fn text_truncated_with_ellipsis(
|
||||||
|
&self,
|
||||||
|
text: String,
|
||||||
|
font_size: style::Size,
|
||||||
|
max_width_px: f32,
|
||||||
|
) -> String {
|
||||||
|
let lines = text.split_inclusive('\n');
|
||||||
|
/// Return the length of a trailing Unix (`\n`) or MS-DOS (`\r\n`) style line ending in
|
||||||
|
/// `s`, or 0 if not found.
|
||||||
|
fn length_of_trailing_line_ending(s: &str) -> usize {
|
||||||
|
if s.ends_with("\r\n") {
|
||||||
|
2
|
||||||
|
} else if s.ends_with('\n') {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tuples_of_lines_and_endings =
|
||||||
|
lines.map(|line| line.split_at(line.len() - length_of_trailing_line_ending(line)));
|
||||||
|
let lines_truncated_with_ellipsis = tuples_of_lines_and_endings.map(|(line, ending)| {
|
||||||
|
self.line_truncated_with_ellipsis(line, font_size, max_width_px) + ending
|
||||||
|
});
|
||||||
|
lines_truncated_with_ellipsis.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_font_size(&self) -> style::Size {
|
||||||
|
*self.buffer.style.get().size.default()
|
||||||
|
}
|
||||||
|
|
||||||
fn new_line(&self, index: usize) -> Line {
|
fn new_line(&self, index: usize) -> Line {
|
||||||
let line = Line::new(&self.logger);
|
let line = Line::new(&self.logger);
|
||||||
let y_offset = -((index + 1) as f32) * LINE_HEIGHT + LINE_VERTICAL_OFFSET;
|
let y_offset = -((index + 1) as f32) * LINE_HEIGHT + LINE_VERTICAL_OFFSET;
|
||||||
|
@ -1097,6 +1097,9 @@ impl<T> ShapeSystemInfoTemplate<T> {
|
|||||||
/// scene.layers.add_shapes_order_dependency::<shape::View, input::port::hover::View>();
|
/// scene.layers.add_shapes_order_dependency::<shape::View, input::port::hover::View>();
|
||||||
/// scene.layers.add_shapes_order_dependency::<input::port::hover::View, input::port::viz::View>();
|
/// scene.layers.add_shapes_order_dependency::<input::port::hover::View, input::port::viz::View>();
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// A shape listed on the left side of an arrow (`->`) will be ordered below the shape listed on
|
||||||
|
/// the right side of the arrow.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! shapes_order_dependencies {
|
macro_rules! shapes_order_dependencies {
|
||||||
($scene:expr => {
|
($scene:expr => {
|
||||||
|
@ -7,6 +7,7 @@ use crate::prelude::*;
|
|||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
fn crate_name_to_fn_name(name: &str) -> String {
|
fn crate_name_to_fn_name(name: &str) -> String {
|
||||||
|
let name = name.replace("debug-scene-", "");
|
||||||
let name = name.replace("ensogl-example-", "");
|
let name = name.replace("ensogl-example-", "");
|
||||||
let name = name.replace("enso-example-", "");
|
let name = name.replace("enso-example-", "");
|
||||||
let name = name.replace("enso-", "");
|
let name = name.replace("enso-", "");
|
||||||
|
Loading…
Reference in New Issue
Block a user