mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 01:51:30 +03:00
Component Group Entry with icons and text highlighting. (#3459)
This PR extends the Component Group Entry with icon and option to highlight the text. Here the convert has highlighted "con". https://user-images.githubusercontent.com/3919101/169046537-4f8b823c-322e-40dc-8abb-24d1d7092341.mp4 ### Important Notes Although this PR includes effort for adjusting Component Group style to better reflect the design, it is not entirely finished: the selection still works badly and will be fixed in another PR.
This commit is contained in:
parent
297ef4251c
commit
eef0738f63
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -1467,6 +1467,7 @@ name = "debug-scene-component-group"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-frp",
|
||||
"enso-text",
|
||||
"ensogl-core",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-list-view",
|
||||
@ -1477,6 +1478,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-scene-icons"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ensogl",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ide-view-component-group",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-scene-interface"
|
||||
version = "0.1.0"
|
||||
@ -1811,6 +1822,7 @@ name = "enso-debug-scene"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"debug-scene-component-group",
|
||||
"debug-scene-icons",
|
||||
"debug-scene-interface",
|
||||
"debug-scene-visualization",
|
||||
]
|
||||
@ -3620,13 +3632,14 @@ name = "ide-view-component-group"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-frp",
|
||||
"ensogl-core",
|
||||
"ensogl",
|
||||
"ensogl-gui-component",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-label",
|
||||
"ensogl-list-view",
|
||||
"ensogl-shadow",
|
||||
"ensogl-text",
|
||||
"failure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -9,10 +9,11 @@ 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 = { version = "0.1.0", path = "../../../../../lib/rust/ensogl" }
|
||||
ensogl-gui-component = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/gui" }
|
||||
ensogl-shadow = { version = "0.1.0", path = "../../../../../lib/rust/ensogl/component/shadow" }
|
||||
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" }
|
||||
ensogl-label = { path = "../../../../../lib/rust/ensogl/component/label/" }
|
||||
failure = { version = "0.1.6" }
|
||||
|
@ -2,18 +2,22 @@
|
||||
//!
|
||||
//! The entry data is represented by the [`Model`] and visualized by the [`View`].
|
||||
|
||||
use ensogl_core::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::icon;
|
||||
use crate::Colors;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color::Rgba;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::scene::Layer;
|
||||
use ensogl_core::display::style;
|
||||
use ensogl_core::display::Scene;
|
||||
use ensogl_hardcoded_theme::application::component_browser::component_group::entries as theme;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color;
|
||||
use ensogl::display;
|
||||
use ensogl::display::scene::Layer;
|
||||
use ensogl::display::style;
|
||||
use ensogl::display::Scene;
|
||||
use ensogl_hardcoded_theme::application::component_browser::component_group::entry_list as theme;
|
||||
use ensogl_list_view as list_view;
|
||||
use ensogl_list_view::entry::Label;
|
||||
use ensogl_list_view::entry::GlyphHighlightedLabel;
|
||||
use ensogl_list_view::entry::GlyphHighlightedLabelModel;
|
||||
|
||||
|
||||
|
||||
@ -41,9 +45,19 @@ pub const STYLE_PATH: &str = theme::HERE.str;
|
||||
|
||||
/// Data underlying an entry in a component group view.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, Default, From)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Model {
|
||||
pub label: String,
|
||||
pub icon: icon::Id,
|
||||
pub highlighted_text: GlyphHighlightedLabelModel,
|
||||
}
|
||||
|
||||
impl From<String> for Model {
|
||||
fn from(label: String) -> Self {
|
||||
Model {
|
||||
icon: icon::Id::Star,
|
||||
highlighted_text: GlyphHighlightedLabelModel { label, highlighted: default() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Model {
|
||||
@ -63,17 +77,22 @@ impl From<&str> for Model {
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct Params {
|
||||
pub color: frp::Sampler<Rgba>,
|
||||
pub colors: Colors,
|
||||
}
|
||||
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
let network = frp::Network::new("component_group::Params::default");
|
||||
let network = frp::Network::new("component_browser::entry::Params::default");
|
||||
frp::extend! { network
|
||||
color_source <- source::<Rgba>();
|
||||
color <- color_source.sampler();
|
||||
icon_strong <- source::<color::Rgba>().sampler();
|
||||
icon_weak <- source::<color::Rgba>().sampler();
|
||||
header_text <- source::<color::Rgba>().sampler();
|
||||
entry_text <- source::<color::Rgba>().sampler();
|
||||
background <- source::<color::Rgba>().sampler();
|
||||
}
|
||||
Self { color }
|
||||
|
||||
let colors = Colors { icon_strong, icon_weak, header_text, entry_text, background };
|
||||
Self { colors }
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,43 +102,97 @@ impl Default for Params {
|
||||
// === View ===
|
||||
// ============
|
||||
|
||||
|
||||
// === CurrentIcon ===
|
||||
|
||||
/// The structure keeping a currently displayed incon in Component Group Entry [`View`]. Remembering
|
||||
/// id allows us to skip icon generation when not changed.
|
||||
#[derive(Debug, Default)]
|
||||
struct CurrentIcon {
|
||||
shape: Option<icon::Any>,
|
||||
id: Option<icon::Id>,
|
||||
}
|
||||
|
||||
|
||||
// === View ===
|
||||
|
||||
/// A visual representation of a [`Model`].
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct View {
|
||||
logger: Logger,
|
||||
display_object: display::object::Instance,
|
||||
label: Label,
|
||||
logger: Logger,
|
||||
display_object: display::object::Instance,
|
||||
icon: Rc<RefCell<CurrentIcon>>,
|
||||
max_width_px: frp::Source<f32>,
|
||||
icon_strong_color: frp::Sampler<color::Rgba>,
|
||||
icon_weak_color: frp::Sampler<color::Rgba>,
|
||||
label: GlyphHighlightedLabel,
|
||||
}
|
||||
|
||||
impl list_view::Entry for View {
|
||||
type Model = Model;
|
||||
type Params = Params;
|
||||
|
||||
fn new(app: &Application, style_prefix: &style::Path, params: &Self::Params) -> Self {
|
||||
fn new(app: &Application, style_prefix: &style::Path, Params { colors }: &Params) -> Self {
|
||||
let logger = Logger::new("component-group::Entry");
|
||||
let display_object = display::object::Instance::new(&logger);
|
||||
let label = Label::new(app, style_prefix);
|
||||
let icon: Rc<RefCell<CurrentIcon>> = default();
|
||||
let label = GlyphHighlightedLabel::new(app, style_prefix, &());
|
||||
display_object.add_child(&label);
|
||||
|
||||
let network = &label.network;
|
||||
let color = params.color.clone();
|
||||
let label_frp = &label.label.frp;
|
||||
let network = &label.inner.network;
|
||||
let style = &label.inner.style_watch;
|
||||
let icon_text_gap = style.get_number(theme::icon_text_gap);
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
color <- all(&color, &init)._0();
|
||||
label_frp.set_default_color <+ color;
|
||||
max_width_px <- source::<f32>();
|
||||
icon_text_gap <- all(&icon_text_gap, &init)._0();
|
||||
label_x_position <- icon_text_gap.map(|gap| icon::SIZE + gap);
|
||||
label_max_width <-
|
||||
all_with(&max_width_px, &icon_text_gap, |width,gap| width - icon::SIZE- gap);
|
||||
eval label_x_position ((x) label.set_position_x(*x));
|
||||
eval label_max_width ((width) label.set_max_width(*width));
|
||||
label.inner.label.set_default_color <+ all(&colors.entry_text, &init)._0();
|
||||
eval colors.icon_strong ([icon](color)
|
||||
if let Some(shape) = &icon.borrow().shape {
|
||||
shape.strong_color.set(color.into());
|
||||
}
|
||||
);
|
||||
eval colors.icon_weak ([icon](color)
|
||||
if let Some(shape) = &icon.borrow().shape {
|
||||
shape.weak_color.set(color.into());
|
||||
}
|
||||
);
|
||||
icon_strong_color <- colors.icon_strong.sampler();
|
||||
icon_weak_color <- colors.icon_weak.sampler();
|
||||
}
|
||||
init.emit(());
|
||||
|
||||
Self { logger, display_object, label }
|
||||
Self {
|
||||
logger,
|
||||
display_object,
|
||||
icon,
|
||||
max_width_px,
|
||||
icon_strong_color,
|
||||
icon_weak_color,
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self, model: &Self::Model) {
|
||||
self.label.update(&model.label);
|
||||
self.label.update(&model.highlighted_text);
|
||||
let mut icon = self.icon.borrow_mut();
|
||||
if !icon.id.contains(&model.icon) {
|
||||
icon.id = Some(model.icon);
|
||||
let shape = model.icon.create_shape(&self.logger, Vector2(icon::SIZE, icon::SIZE));
|
||||
shape.strong_color.set(self.icon_strong_color.value().into());
|
||||
shape.weak_color.set(self.icon_weak_color.value().into());
|
||||
shape.set_position_x(icon::SIZE / 2.0);
|
||||
self.display_object.add_child(&shape);
|
||||
icon.shape = Some(shape);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_max_width(&self, max_width_px: f32) {
|
||||
self.label.set_max_width(max_width_px);
|
||||
self.max_width_px.emit(max_width_px);
|
||||
}
|
||||
|
||||
fn set_label_layer(&self, label_layer: &Layer) {
|
||||
|
921
app/gui/view/component-browser/component-group/src/icon.rs
Normal file
921
app/gui/view/component-browser/component-group/src/icon.rs
Normal file
@ -0,0 +1,921 @@
|
||||
//! All icons that are used in the Component Browser.
|
||||
|
||||
|
||||
|
||||
mod common_part;
|
||||
mod define_macro;
|
||||
|
||||
use crate::icon::common_part::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display;
|
||||
use crate::display::Scene;
|
||||
use ensogl::display::object::ObjectOps;
|
||||
use ensogl::display::shape::compound::path::path;
|
||||
use ensogl::display::Attribute;
|
||||
use ensogl_hardcoded_theme::application::searcher::icons as theme;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// The width and height of all icon.
|
||||
pub const SIZE: f32 = 16.0;
|
||||
|
||||
/// This constant exists for development purposes only, and is published for debug scene.
|
||||
/// Due to a rendering error, shapes appear too big when the camera is zoomed in very closely.
|
||||
/// (Documented here: https://github.com/enso-org/ide/issues/1698)
|
||||
/// In the user interface, this is not a big problem, since icon are usually shown at lower zoom
|
||||
/// levels. But it is a problem during development of icon when it becomes necessary to inspect
|
||||
/// them closely. In those situations, one can apply `.shrink(0.35)` to shapes to compensate for the
|
||||
/// bug and make them appear at the correct size while the camera is zoomed in. But that work-around
|
||||
/// will make them appear too thin on the default zoom level.
|
||||
///
|
||||
/// To make it easy to turn this shrinking on and off before and after working on icon, we define
|
||||
/// the constant `SHRINK_AMOUNT` and apply `.shrink(SHRINK_AMOUNT.px())` to all icon. In every
|
||||
/// commit, `SHRINK_AMOUNT` should be set to 0.0 to make icon look best in the user interface. But
|
||||
/// during work on the icon, it can temporarily be set to 0.35.
|
||||
pub const SHRINK_AMOUNT: f32 = 0.0;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
|
||||
/// Error occuring when we try parse string being an invalid icon name to icon Id.
|
||||
#[derive(Clone, Debug, Fail)]
|
||||
#[fail(display = "Unknown icon '{}'.", name)]
|
||||
pub struct UnknownIcon {
|
||||
/// The copied icon name from parsed string.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === AnyIcon ===
|
||||
// ===============
|
||||
|
||||
/// One of the icon generated from the [`define_icons`] macro. Returned from `create_shape` method.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct Any {
|
||||
/// The underlying icon shape.
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub view: Box<dyn display::Object>,
|
||||
/// Strong (darker, or more contrasting) color parameter.
|
||||
pub strong_color: DynamicParam<Attribute<Vector4>>,
|
||||
/// Weak (lighter, or less contrasting) color parameter.
|
||||
pub weak_color: DynamicParam<Attribute<Vector4>>,
|
||||
}
|
||||
|
||||
impl display::Object for Any {
|
||||
fn display_object(&self) -> &display::object::Instance<Scene> {
|
||||
self.view.display_object()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============
|
||||
// === Icons ===
|
||||
// =============
|
||||
|
||||
crate::define_icons! {
|
||||
|
||||
/// A five-pointed star.
|
||||
pub mod star(Star) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let shape = FiveStar(7.0.px(),0.447);
|
||||
let shape = shape.fill(style.get_color(theme::favorites));
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A rounded rectangle with an arrow pointing in from the left.
|
||||
pub mod data_input(DataInput) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
|
||||
// === Border ===
|
||||
|
||||
let border =
|
||||
Rect((10.0.px(),13.0.px())).corners_radius(1.5.px()).translate_x(1.5.px());
|
||||
// Taking just an outline.
|
||||
let border = &border - border.shrink(1.0.px());
|
||||
// Creating a gap for the arrow to pass through.
|
||||
let gap = Rect((2.0.px(),4.0.px())).translate_x((-3.0).px());
|
||||
let border = border - gap;
|
||||
|
||||
|
||||
// === Arrow ===
|
||||
|
||||
let arrow =
|
||||
arrow(11.0,1.0,4.0,5.0).rotate((PI/2.0).radians()).translate_x(4.0.px());
|
||||
|
||||
|
||||
// === Shape ===
|
||||
|
||||
let shape = border + arrow;
|
||||
let shape = shape.fill(strong_color);
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A rounded rectangle with an arrow pointing out to the right.
|
||||
pub mod data_output(DataOutput) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
|
||||
// === Border ===
|
||||
|
||||
let border = Rect((9.0.px(),13.0.px())).corners_radius(1.5.px());
|
||||
let border = border.translate_x((-2.5).px());
|
||||
// Taking just an outline.
|
||||
let border = &border - border.shrink(1.0.px());
|
||||
// Creating a gap for the arrow to pass through.
|
||||
let gap = Rect((2.0.px(),4.0.px())).translate_x((1.5).px());
|
||||
let border = border - gap;
|
||||
|
||||
|
||||
// === Arrow ===
|
||||
|
||||
let arrow =
|
||||
arrow(11.0,1.0,4.0,5.0).rotate((PI/2.0).radians()).translate_x(8.0.px());
|
||||
|
||||
|
||||
// === Shape ===
|
||||
|
||||
let shape = border + arrow;
|
||||
let shape = shape.fill(strong_color);
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A rounded rectangle with the letter "A" and a text cursor.
|
||||
pub mod text_input(TextInput) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
|
||||
// === Border ===
|
||||
|
||||
let border = Rect((16.0.px(),11.0.px())).corners_radius(1.5.px());
|
||||
// Using just the outline.
|
||||
let border = &border - border.shrink(1.0.px());
|
||||
// Creating a gap for the cursor.
|
||||
let gap = Rect((3.0.px(),13.0.px())).translate_x((3.5).px());
|
||||
let border = border - gap;
|
||||
|
||||
|
||||
// === Cursor ===
|
||||
|
||||
let cursor = cursor().translate_x(3.5.px());
|
||||
|
||||
|
||||
// === Letter ===
|
||||
|
||||
// We construct the letter "A", consisting of a diagonal stroke on the left,
|
||||
// a diagonal stroke on the right and a horizontal bar in the middle.
|
||||
let left_stroke = Segment((0.0.px(),2.5.px()),((-2.5).px(),(-2.5).px()),1.0.px());
|
||||
let right_stroke = Segment((0.0.px(),2.5.px()),(2.5.px(),(-2.5).px()),1.0.px());
|
||||
let bar = Rect((4.0.px(),1.0.px())).translate_y((-1.0).px());
|
||||
let letter = left_stroke + right_stroke + bar;
|
||||
let letter = letter.translate_x((-2.5).px());
|
||||
|
||||
|
||||
// === Shape ===
|
||||
|
||||
let shape = border + cursor + letter;
|
||||
let shape = shape.fill(strong_color);
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A rounded rectangle with the number "5" and a text cursor.
|
||||
pub mod number_input(NumberInput) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
|
||||
// === Border ===
|
||||
|
||||
let border = Rect((16.0.px(),11.0.px())).corners_radius(5.5.px());
|
||||
// Using just the outline.
|
||||
let border = &border - border.shrink(1.0.px());
|
||||
// Creating a gap for the cursor.
|
||||
let gap = Rect((3.0.px(),13.0.px())).translate_x((3.5).px());
|
||||
let border = border - gap;
|
||||
|
||||
|
||||
// === Cursor ===
|
||||
|
||||
let cursor = cursor().translate_x(3.5.px());
|
||||
|
||||
|
||||
// === Number 5 ===
|
||||
|
||||
// The number "5" consists of a short horizontal bar at the top, a vertical bar
|
||||
// connected to it on the left and a big arc below, connected to the vertical bar.
|
||||
let top = Rect((3.0.px(),1.0.px()));
|
||||
let left =
|
||||
Rect((1.0.px(),3.0.px())).translate_x((-1.0).px()).translate_y((-1.0).px());
|
||||
|
||||
|
||||
// == Number 5 Arc ==
|
||||
|
||||
let arc_center = Vector2(-0.25_f32,-3.5_f32);
|
||||
// The point where the inner side of the arc connects with the vertical bar.
|
||||
let arc_connection = Vector2(-0.5_f32,-2.5_f32);
|
||||
// Offset from the arc center to the connection.
|
||||
let connection_offset = arc_connection - arc_center;
|
||||
let stroke_width = 1.0;
|
||||
// The outer radius of the arc.
|
||||
let radius: f32 = connection_offset.norm() + stroke_width;
|
||||
let connection_direction = connection_offset.x.atan2(connection_offset.y);
|
||||
|
||||
let arc = arc(radius,stroke_width,connection_direction,228_f32.to_radians());
|
||||
let arc = arc.translate((arc_center.x.px(),arc_center.y.px()));
|
||||
|
||||
let number = (top + left + arc).translate_x((-2.0).px()).translate_y(2.5.px());
|
||||
|
||||
|
||||
// === Shape ===
|
||||
|
||||
let shape = border + cursor + number;
|
||||
let shape = shape.fill(strong_color);
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A table with 4x2 cells and a cursor shape in front of it.
|
||||
pub mod table_edit(TableEdit) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
// We need to create the table in two parts, left and right of the cursor to achieve
|
||||
// the right cell arangement.
|
||||
let left_table = table(2,2).translate(((-8.0).px(),(-4.5).px()));
|
||||
let right_table = table(2,2).translate(((-1.0).px(),(-4.5).px()));
|
||||
let gap = Rect((3.0.px(),13.0.px()));
|
||||
let cursor = cursor();
|
||||
|
||||
let shape = left_table + right_table - gap + cursor;
|
||||
let shape = shape.fill(strong_color);
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An arrow to the left on top and an arrow to the right below.
|
||||
pub mod convert(Convert) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let upper_arrow = arrow(10.0,1.0,4.5,6.0).rotate((-PI/2.0).radians());
|
||||
let upper_arrow = upper_arrow.translate(((-8.0).px(),1.0.px()));
|
||||
let lower_arrow = arrow(10.0,1.0,4.5,6.0).rotate((PI/2.0).radians());
|
||||
let lower_arrow = lower_arrow.translate((8.0.px(),(-1.5).px()));
|
||||
|
||||
let shape = upper_arrow + lower_arrow;
|
||||
let shape = shape.fill(strong_color);
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A table with an eraser in front.
|
||||
pub mod dataframe_clean(DataframeClean) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let table_color = weak_color;
|
||||
let table =
|
||||
table(2,3).translate(((-8.0).px(),(-6.5).px())).fill(table_color.clone());
|
||||
let bottom_line =
|
||||
Rect((13.0.px(),1.0.px())).corners_radius(1.0.px()).fill(table_color);
|
||||
let bottom_line = bottom_line.translate_y((-6.0).px());
|
||||
|
||||
let eraser = Rect((9.0.px(),5.0.px())).corners_radius(1.0.px());
|
||||
let eraser_bg = eraser.grow(1.5.px());
|
||||
let eraser_bg = eraser_bg.rotate((-0.25 * std::f32::consts::PI).radians());
|
||||
let eraser_bg = eraser_bg.translate((3.5.px(),(-1.5).px()));
|
||||
let eraser_inner = Rect((7.0.px(),3.0.px()));
|
||||
let eraser_bar = Rect((1.0.px(),4.0.px())).translate_x((-1.0).px());
|
||||
let eraser = eraser - eraser_inner + eraser_bar;
|
||||
let eraser = eraser.fill(strong_color);
|
||||
let eraser = eraser.rotate((-0.25 * std::f32::consts::PI).radians());
|
||||
let eraser = eraser.translate((3.5.px(),(-1.5).px()));
|
||||
|
||||
let shape = table - eraser_bg + eraser + bottom_line;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A light column on the left, a dark column in the middle and a plus on the right.
|
||||
pub mod add_column(AddColumn) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let old_color = weak_color;
|
||||
let new_color = strong_color;
|
||||
|
||||
let old_column = table(1,3).translate(((-8.0).px(),(-6.5).px())).fill(old_color);
|
||||
let new_column =
|
||||
table(1,3).translate(((-4.0).px(),(-6.5).px())).fill(new_color.clone());
|
||||
let plus = plus(5.0,1.0).fill(new_color).translate_x(5.0.px());
|
||||
|
||||
let shape = old_column + new_column + plus;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A light row at the top, a dark row in the middle and a plus at the bottom.
|
||||
pub mod add_row(AddRow) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let old_color = weak_color;
|
||||
let new_color = strong_color;
|
||||
|
||||
let old_row = table(3,1).translate(((-6.5).px(),3.0.px())).fill(old_color);
|
||||
let new_row =
|
||||
table(3,1).translate(((-6.5).px(),(-1.0).px())).fill(new_color.clone());
|
||||
let plus = plus(5.0,1.0).fill(new_color).translate_y((-5.0).px());
|
||||
|
||||
let shape = old_row + new_row + plus;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two light columns on the left and one dark column detached on the right.
|
||||
pub mod select_column(SelectColumn) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let unselected = table(2,3).translate(((-8.0).px(),(-6.5).px()));
|
||||
let unselected = unselected.fill(weak_color);
|
||||
let selected = table(1,3).translate((3.0.px(),(-6.5).px()));
|
||||
let selected = selected.fill(strong_color);
|
||||
|
||||
let shape = unselected + selected;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two light rows at the top and one dark row detached at the bottom.
|
||||
pub mod select_row(SelectRow) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let unselected = table(3,2).translate(((-6.5).px(),(-1.0).px()));
|
||||
let unselected = unselected.fill(weak_color);
|
||||
let selected = table(3,1).translate(((-6.5).px(),(-8.0).px()));
|
||||
let selected = selected.fill(strong_color);
|
||||
|
||||
let shape = unselected + selected;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A light column, a dark column and a lightning bolt on the right.
|
||||
pub mod dataframe_map_column(DataframeMapColumn) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let weak_color = weak_color;
|
||||
let strong_color = strong_color;
|
||||
|
||||
let weak_column = table(1,3).translate(((-8.0).px(),(-6.5).px())).fill(weak_color);
|
||||
let strong_column =
|
||||
table(1,3).translate(((-4.0).px(),(-6.5).px())).fill(strong_color.clone());
|
||||
let lightning = lightning_bolt().translate_x(5.25.px()).fill(strong_color);
|
||||
|
||||
let shape = weak_column + strong_column + lightning;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A light row, a dark row and a lightning bolt below.
|
||||
pub mod dataframe_map_row(DataframeMapRow) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let weak_color = weak_color;
|
||||
let strong_color = strong_color;
|
||||
|
||||
let weak_row = table(3,1).translate(((-6.5).px(),3.0.px())).fill(weak_color);
|
||||
let strong_row =
|
||||
table(3,1).translate(((-6.5).px(),(-1.0).px())).fill(strong_color.clone());
|
||||
let lightning = lightning_bolt().rotate((PI/2.0).radians());
|
||||
let lightning = lightning.translate_y((-5.25).px()).fill(strong_color);
|
||||
|
||||
let shape = weak_row + strong_row + lightning;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two columns with a plus in-between.
|
||||
pub mod dataframes_join(DataframesJoin) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let column_color = weak_color;
|
||||
let plus_color = strong_color;
|
||||
|
||||
let left_column =
|
||||
table(1,3).translate(((-8.0).px(),(-6.5).px())).fill(column_color.clone());
|
||||
let right_column = table(1,3).translate((3.0.px(),(-6.5).px())).fill(column_color);
|
||||
let plus = plus(5.0,1.0).fill(plus_color);
|
||||
|
||||
let shape = left_column + right_column + plus;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two rows with a plus in-between.
|
||||
pub mod dataframes_union(DataframesUnion) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let row_color = weak_color;
|
||||
let plus_color = strong_color;
|
||||
|
||||
let top_row = table(3,1).translate(((-6.5).px(),3.0.px())).fill(row_color.clone());
|
||||
let bottom_row = table(3,1).translate(((-6.5).px(),(-8.0).px())).fill(row_color);
|
||||
let plus = plus(5.0,1.0).fill(plus_color);
|
||||
|
||||
let shape = top_row + bottom_row + plus;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A capital "Σ".
|
||||
pub mod sigma(Sigma) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let shape = path(2.0,&[
|
||||
( 4.0 , 4.0),
|
||||
( 4.0 , 5.5),
|
||||
(-5.0 , 5.5),
|
||||
( 0.5 , 0.0),
|
||||
(-5.0 , -5.5),
|
||||
( 4.0 , -5.5),
|
||||
( 5.0 , -3.5),
|
||||
]);
|
||||
let shape = shape.fill(style.get_color(theme::transform));
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The shape of a sheet of paper that has been ripped apart with a vertical crack through the
|
||||
/// middle. Both pieces contain two thin rectangles as a simple representation of lines of text.
|
||||
pub mod split_text(SplitText) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
|
||||
// === Page border ===
|
||||
|
||||
let page = Rect((16.0.px(),14.0.px())).corners_radius(2.0.px());
|
||||
let page = &page - page.shrink(1.0.px());
|
||||
let gap = Rect((3.0.px(),15.0.px())).translate_x(0.5.px());
|
||||
let page = page - gap;
|
||||
|
||||
|
||||
// === Lines ===
|
||||
|
||||
let line1 = Rect((3.0.px(),1.0.px())).translate_x((-4.5).px());
|
||||
let line2 = Rect((2.0.px(),1.0.px())).translate(((-5.0).px(),(-3.0).px()));
|
||||
let line3 = Rect((2.0.px(),1.0.px())).translate_x(5.0.px());
|
||||
let line4 = Rect((3.0.px(),1.0.px())).translate((4.5.px(),(-3.0).px()));
|
||||
let page = page + line1 + line2 + line3 + line4;
|
||||
let page = page.fill(weak_color);
|
||||
|
||||
|
||||
// === Crack ===
|
||||
|
||||
let crack = path(1.0,&[
|
||||
( 0.0 , 6.5),
|
||||
(-1.25 , 3.25),
|
||||
( 0.0 , 0.0),
|
||||
(-1.25 , -3.25),
|
||||
( 0.0 , -6.5),
|
||||
]);
|
||||
let crack = crack.fill(strong_color);
|
||||
|
||||
let crack_left = crack.translate_x((-1.0).px());
|
||||
let crack_right = crack.translate_x(2.0.px());
|
||||
|
||||
|
||||
// === Shape ===
|
||||
|
||||
let shape = page + crack_left + crack_right;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Some rectangles and circles in different colors.
|
||||
pub mod data_science(DataScience) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let blue = style.get_color(theme::data_science::blue);
|
||||
let rect1 = Rect((4.0.px(),4.0.px())).translate(((-5.5).px(),3.0.px())).fill(blue);
|
||||
let rect2 = Rect((4.0.px(),4.0.px())).translate_y((-5.5).px()).fill(blue);
|
||||
|
||||
let gray = style.get_color(theme::data_science::gray);
|
||||
let circle1 = Circle(2.0.px()).translate_y(5.5.px()).fill(gray);
|
||||
let circle2 = Circle(2.0.px()).translate(((-5.5).px(),(-3.0).px())).fill(gray);
|
||||
let circle3 = Circle(2.0.px()).translate((5.5.px(),(-3.0).px())).fill(gray);
|
||||
|
||||
let red = style.get_color(theme::data_science::red);
|
||||
let circle4 = Circle(2.0.px()).fill(red);
|
||||
|
||||
let shape = rect1 + rect2 + circle1 + circle2 + circle3 + circle4;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A WiFi symbol, consisting of a small circle and three arcs of increasing size above it.
|
||||
pub mod network(Network) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let circle = Circle(1.0.px())
|
||||
.fill(style.get_color(theme::network::_0));
|
||||
let arc1 = RoundedArc((10.5/3.0*1.0).px(),(PI/2.0).radians(),1.5.px())
|
||||
.fill(style.get_color(theme::network::_1));
|
||||
let arc2 = RoundedArc((10.5/3.0*2.0).px(),(PI/2.0).radians(),1.5.px())
|
||||
.fill(style.get_color(theme::network::_2));
|
||||
let arc3 = RoundedArc((10.5/3.0*3.0).px(),(PI/2.0).radians(),1.5.px())
|
||||
.fill(style.get_color(theme::network::_3));
|
||||
|
||||
let shape = circle + arc1 + arc2 + arc3;
|
||||
let shape = shape.translate_y((-5.5).px());
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A dark rectangle containing the simple terminal prompt ">_".
|
||||
pub mod system(System) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let background = Rect((14.0.px(),14.0.px())).corners_radius(2.0.px());
|
||||
let background = background.translate_y((-0.5).px());
|
||||
let background = background.fill(style.get_color(theme::system::background));
|
||||
let greater = path(1.5,&[
|
||||
(-3.75 , 2.25),
|
||||
(-1.25 , -0.25),
|
||||
(-3.75 , -2.25),
|
||||
]);
|
||||
let bar = Rect((4.0.px(),1.5.px())).translate((2.5.px(),(-2.75).px()));
|
||||
let content = greater + bar;
|
||||
let content = content.fill(style.get_color(theme::system::content));
|
||||
|
||||
let shape = background + content;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Four rounded rectangles in different colors aranged in a grid.
|
||||
pub mod libraries(Libraries) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let rect0 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect0 = rect0.fill(style.get_color(theme::libraries::_0));
|
||||
let rect0 = rect0.translate(((-3.75).px(),3.75.px()));
|
||||
|
||||
let rect1 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect1 = rect1.fill(style.get_color(theme::libraries::_1));
|
||||
let rect1 = rect1.translate(((-3.75).px(),(-3.75).px()));
|
||||
|
||||
let rect2 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect2 = rect2.fill(style.get_color(theme::libraries::_2));
|
||||
let rect2 = rect2.translate((3.75.px(),(-3.75).px()));
|
||||
|
||||
let rect3 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect3 = rect3.fill(style.get_color(theme::libraries::_3));
|
||||
let rect3 = rect3.translate((3.75.px(),3.75.px()));
|
||||
|
||||
let shape = rect0 + rect1 + rect2 + rect3;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A plus and three rounded rectangles in different colors aranged in a grid.
|
||||
pub mod marketplace(Marketplace) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let plus = plus(6.5,1.5);
|
||||
let plus = plus.fill(style.get_color(theme::libraries::_0));
|
||||
let plus = plus.translate(((-3.75).px(),3.75.px()));
|
||||
|
||||
let rect1 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect1 = rect1.fill(style.get_color(theme::libraries::_1));
|
||||
let rect1 = rect1.translate(((-3.75).px(),(-3.75).px()));
|
||||
|
||||
let rect2 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect2 = rect2.fill(style.get_color(theme::libraries::_2));
|
||||
let rect2 = rect2.translate((3.75.px(),(-3.75).px()));
|
||||
|
||||
let rect3 = Rect((6.5.px(),6.5.px())).corners_radius(1.0.px());
|
||||
let rect3 = rect3.fill(style.get_color(theme::libraries::_3));
|
||||
let rect3 = rect3.translate((3.75.px(),3.75.px()));
|
||||
|
||||
let shape = plus + rect1 + rect2 + rect3;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two half arrow, one on top and pointing to the right, one at the bottom and pointing to the
|
||||
/// left. The shape has an outline in a darker color.
|
||||
pub mod io(IO) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let half_arrow = arrow(14.0,5.0,7.0,11.0).rotate((PI/2.0).radians()) - HalfPlane();
|
||||
let upper = half_arrow.translate((7.0.px(),0.5.px()));
|
||||
let lower = half_arrow.rotate(PI.radians()).translate(((-7.0).px(),(-1.0).px()));
|
||||
|
||||
let base = upper + lower;
|
||||
let outer = base.fill(strong_color);
|
||||
let inner = base.shrink(0.5.px()).fill(weak_color);
|
||||
|
||||
let shape = outer + inner;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The shape of a funnel, consisting of a big upside-down triangle at the top connected with
|
||||
/// a thin rectangular tube shape below with a triangular end piece. The whole shape has an
|
||||
/// outline.
|
||||
pub mod preparation(Preparation) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
|
||||
// === Outline ===
|
||||
|
||||
let outline = path(1.0,&[
|
||||
(-6.5 , 6.0),
|
||||
( 6.0 , 6.0),
|
||||
( 6.0 , 5.5),
|
||||
( 1.0 , 0.5),
|
||||
( 1.0 , -7.0),
|
||||
(-1.5 , -4.5),
|
||||
(-1.5 , 0.5),
|
||||
(-6.5 , 5.5),
|
||||
(-6.5 , 6.0),
|
||||
]);
|
||||
let outline = outline.fill(strong_color);
|
||||
|
||||
|
||||
// === Fill ===
|
||||
|
||||
let big_triangle = Triangle(13.5.px(),6.75.px()).rotate(PI.radians());
|
||||
let big_triangle = big_triangle.translate(((-0.25).px(),2.625.px()));
|
||||
let pipe = Rect((2.5.px(),6.0.px())).translate(((-0.25).px(),(-1.5).px()));
|
||||
let small_triangle = Triangle(5.0.px(),2.5.px()).rotate((-PI/2.0).radians());
|
||||
let small_triangle = small_triangle.translate(((-0.25).px(),(-4.5).px()));
|
||||
let fill = big_triangle + pipe + small_triangle;
|
||||
let fill = fill.fill(weak_color);
|
||||
|
||||
|
||||
// === Shape ===
|
||||
|
||||
let shape = fill.shrink(SHRINK_AMOUNT.px()) + outline.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two intersecting circles. The circles, their outlines and the intersection are displayed in
|
||||
/// different colors.
|
||||
pub mod join(Join) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let left_circle = Circle(5.0.px()).translate_x((-3.0).px());
|
||||
let right_circle = Circle(5.0.px()).translate_x(3.0.px());
|
||||
let left_outline = &left_circle - left_circle.shrink(0.5.px());
|
||||
let right_outline = &right_circle - right_circle.shrink(0.5.px());
|
||||
let intersection = &left_circle * &right_circle;
|
||||
|
||||
let left_circle = left_circle.fill(weak_color.clone());
|
||||
let right_circle = right_circle.fill(weak_color);
|
||||
let intersection = intersection.fill(style.get_color(theme::join::medium));
|
||||
let left_outline = left_outline.fill(strong_color.clone());
|
||||
let right_outline = right_outline.fill(strong_color);
|
||||
|
||||
let shape =
|
||||
left_circle + right_circle + intersection + left_outline + right_outline;
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A page with three lines representing text. The upper line is part of an arrow pointing out
|
||||
/// to the right.
|
||||
pub mod text(Text) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let page = Rect((10.0.px(),14.0.px())).corners_radius(2.0.px());
|
||||
let page = page.translate_x((-2.0).px());
|
||||
let page = &page - page.shrink(1.0.px());
|
||||
|
||||
let arrow = arrow(13.0,1.0,3.0,6.0)
|
||||
.rotate((PI/2.0).radians())
|
||||
.translate((8.0.px(),3.0.px()));
|
||||
|
||||
let line1 = Rect((6.0.px(),1.0.px())).translate_x((-2.0).px());
|
||||
let line2 = line1.translate_y((-3.0).px());
|
||||
|
||||
let shape = page + arrow + line1 + line2;
|
||||
let shape = shape.fill(strong_color);
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A clock shape.
|
||||
pub mod date_and_time(DateAndTime) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let circle = Circle(7.75.px());
|
||||
let circle = &circle - circle.shrink(1.0.px());
|
||||
|
||||
let big_hand = Segment((0.0.px(),0.0.px()),(3.0.px(),(-2.0).px()),1.5.px());
|
||||
let small_hand = Segment((0.0.px(),0.0.px()),(0.0.px(),2.5.px()),1.5.px());
|
||||
|
||||
let shape = circle + big_hand + small_hand;
|
||||
let shape = shape.translate((0.25.px(),0.25.px()));
|
||||
let shape = shape.fill(style.get_color(theme::date_and_time));
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The shape location marker. A thick circle outline going over into a triangle that poins
|
||||
/// down. Around the tip there is an ellipse outline.
|
||||
pub mod spatial(Spatial) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let circle = Circle(4.5.px()).translate_y(3.5.px());
|
||||
let circle = &circle - circle.shrink(2.0.px());
|
||||
let triangle = Triangle(7.0,5.75).rotate(PI.radians()).translate_y((-2.125).px());
|
||||
let marker = circle + ▵
|
||||
|
||||
let ellipse = Ellipse(6.5.px(),2.5.px()).translate_y((-5.0).px());
|
||||
let ellipse = &ellipse - ellipse.shrink(1.0.px());
|
||||
// If we used just the triangle for the gap then it would also cut into the lower
|
||||
// part of the ellipse.
|
||||
let ellipse_gap = triangle.grow(1.5.px()) - HalfPlane().translate_y((-5.0).px());
|
||||
let ellipse = ellipse - ellipse_gap;
|
||||
|
||||
let shape = marker + ellipse;
|
||||
let shape = shape.fill(style.get_color(theme::spatial));
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The shape of a christal ball with a bas below.
|
||||
pub mod predictive(Predictive) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let circle = Circle(5.5.px());
|
||||
let sphere = &circle - circle.shrink(1.0.px());
|
||||
|
||||
let reflection1 = arc(3.5,1.0,-114.0_f32.to_radians(),-95.0_f32.to_radians());
|
||||
let reflection2 = arc(3.5,1.0,276.0_f32.to_radians(),13.0_f32.to_radians());
|
||||
let sphere = sphere + reflection1 + reflection2;
|
||||
let sphere = sphere.translate_y(1.5.px());
|
||||
|
||||
let base = Triangle(21.0,8.0).translate_y((-4.0).px());
|
||||
let base = base * Rect((13.0.px(),5.0.px())).translate_y((-5.0).px());
|
||||
let base = base - circle.translate_y(1.5.px()).grow(2.0.px());
|
||||
|
||||
let shape = sphere + base;
|
||||
let shape = shape.fill(style.get_color(theme::predictive));
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The shape of an android.
|
||||
pub mod machine_learning(MachineLearning) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let body = Rect((10.0.px(),15.0.px()))
|
||||
.corners_radiuses(5.0.px(),5.0.px(),2.0.px(),2.0.px())
|
||||
.translate_y((-0.5).px());
|
||||
let body = &body - body.shrink(1.0.px());
|
||||
|
||||
let collar = Rect((9.0.px(),1.0.px()));
|
||||
|
||||
let left_eye = Rect((1.5.px(),1.5.px())).translate(((-1.75).px(),2.75.px()));
|
||||
let right_eye = Rect((1.5.px(),1.5.px())).translate((1.75.px(),2.75.px()));
|
||||
let antenna = Rect((1.0.px(),1.5.px())).translate_y(7.25.px());
|
||||
let left_arm = Rect((1.0.px(),4.5.px())).translate(((-6.5).px(),(-2.75).px()));
|
||||
let right_arm = Rect((1.0.px(),4.5.px())).translate((6.5.px(),(-2.75).px()));
|
||||
|
||||
let shape = body + collar + left_eye + right_eye + antenna + left_arm + right_arm;
|
||||
let shape = shape.fill(style.get_color(theme::machine_learning));
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The simplified shape of a camera. It consists of a small red circle in a bigger circle
|
||||
/// outline, representing the lens and a base above that the camera is mounted on.
|
||||
pub mod computer_vision(ComputerVision) {
|
||||
ensogl::define_shape_system! {
|
||||
above = [crate::background, ensogl_list_view::background, ensogl_list_view::selection];
|
||||
(style: Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
let lens =
|
||||
Circle(2.0.px()).fill(style.get_color(theme::computer_vision::highlight));
|
||||
let outline = Circle(4.5.px()) - Circle(3.5.px());
|
||||
let outline = outline.fill(strong_color);
|
||||
|
||||
let base =
|
||||
Circle(7.0.px()).translate_y(6.0.px()) * HalfPlane().translate_y(7.0.px());
|
||||
let base = base + Rect((14.0.px(),2.0.px())).translate_y(7.0.px());
|
||||
let base = base - Circle(5.5.px());
|
||||
let base = base.fill(weak_color);
|
||||
|
||||
let shape = lens + outline + base;
|
||||
let shape = shape.translate_y((-2.0).px());
|
||||
let shape = shape.shrink(SHRINK_AMOUNT.px());
|
||||
shape.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Id {
|
||||
fn default() -> Self {
|
||||
Self::Star
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Common Shapes ===
|
||||
// =====================
|
||||
|
||||
/// An arrow shape consisting of a straight line and a triangular head. The arrow points upwards and
|
||||
/// the tip is positioned at the origin.
|
||||
pub fn arrow(length: f32, width: f32, head_length: f32, head_width: f32) -> AnyShape {
|
||||
// We overlap the line with the head by this amount to make sure that the renderer does not
|
||||
// display a gap between them.
|
||||
const OVERLAP: f32 = 1.0;
|
||||
let line_length = length - head_length + OVERLAP;
|
||||
let line = Rect((width.px(), line_length.px()));
|
||||
let line = line.translate_y((-line_length / 2.0 - head_length + OVERLAP).px());
|
||||
let head = Triangle(head_width, head_length).translate_y((-head_length / 2.0).px());
|
||||
(line + head).into()
|
||||
}
|
||||
|
||||
/// An infinite grid of horizontal and vertical lines. The width of each line is given by
|
||||
/// `stroke_width`, the distance between horizontal lines and the distance between vertical lines
|
||||
/// are both given by `cell_size`. The origin is at the intersection of a horizontal and a vertical
|
||||
/// line, touching the horizontal line from the top and the vertical line from the left.
|
||||
pub fn grid(stroke_width: f32, cell_size: f32) -> AnyShape {
|
||||
let horizontal = HalfPlane();
|
||||
let horizontal = horizontal.translate_y(stroke_width.px()) - horizontal;
|
||||
let vertical = HalfPlane().rotate((PI / 2.0).radians());
|
||||
let vertical = vertical.translate_x(stroke_width.px()) - vertical;
|
||||
(horizontal + vertical).repeat((cell_size.px(), cell_size.px())).into()
|
||||
}
|
||||
|
||||
/// A cursor shape, looking roughly like a capital "I".
|
||||
pub fn cursor() -> AnyShape {
|
||||
let middle = Rect((1.0.px(), 15.0.px()));
|
||||
let top = Rect((5.0.px(), 1.0.px())).translate_y(7.5.px());
|
||||
let bottom = Rect((5.0.px(), 1.0.px())).translate_y((-7.5).px());
|
||||
(middle + top + bottom).into()
|
||||
}
|
||||
|
||||
/// An arc around the origin. `outer_radius` determines the distance from the origin to the outer
|
||||
/// edge of the arc, `stroke_width` the width of the arc. The arc starts at `start_angle`, relative
|
||||
/// to the origin and ends at `end_angle`. The ends are flat not rounded, as in [`RoundedArc`].
|
||||
pub fn arc(outer_radius: f32, stroke_width: f32, start_angle: f32, end_angle: f32) -> AnyShape {
|
||||
let circle = Circle(outer_radius.px()) - Circle((outer_radius - stroke_width).px());
|
||||
let inner_angle = (end_angle - start_angle).rem_euclid(2.0 * PI);
|
||||
let mid_angle = start_angle + inner_angle / 2.0;
|
||||
let angle = PlaneAngleFast(inner_angle.radians()).rotate(mid_angle.radians());
|
||||
// The implementation of `PlaneAngleFast` adds 0.5 px to the actual distance to avoid artifacts
|
||||
// in corner cases. We apply `grow` to compensate for that and get the shape that we really
|
||||
// want.
|
||||
let angle = angle.grow(0.5.px());
|
||||
(circle * angle).into()
|
||||
}
|
||||
|
||||
/// The shape of a table, given by a grid with size `columns` x `rows`. The stroke width is 1.0 and
|
||||
/// the cell size 4.0. The origin is at the lower left corner.
|
||||
pub fn table(columns: i32, rows: i32) -> AnyShape {
|
||||
const STROKE_WIDTH: f32 = 1.0;
|
||||
const CELL_SIZE: f32 = 4.0;
|
||||
|
||||
let width = columns as f32 * CELL_SIZE + STROKE_WIDTH;
|
||||
let height = rows as f32 * CELL_SIZE + STROKE_WIDTH;
|
||||
let bounds =
|
||||
Rect((width.px(), height.px())).translate(((width / 2.0).px(), (height / 2.0).px()));
|
||||
let grid = grid(STROKE_WIDTH, CELL_SIZE);
|
||||
(grid * bounds).into()
|
||||
}
|
||||
|
||||
/// A plus, consisting of two strokes of length `size` and width `stroke_width`, intersecting at the
|
||||
/// origin.
|
||||
pub fn plus(size: f32, stroke_width: f32) -> AnyShape {
|
||||
let horizontal = Rect((size.px(), stroke_width.px()));
|
||||
let vertical = Rect((stroke_width.px(), size.px()));
|
||||
(horizontal + vertical).into()
|
||||
}
|
||||
|
||||
/// A shape resembling a lightning bolt, centered at the origin.
|
||||
pub fn lightning_bolt() -> AnyShape {
|
||||
let top = Triangle(3.0.px(), 6.0.px()).translate(((-1.0).px(), 1.9.px()));
|
||||
let bottom = Triangle(3.0.px(), 6.0.px()).rotate(PI.radians());
|
||||
let bottom = bottom.translate((1.0.px(), (-1.9).px()));
|
||||
let lightning = (top + bottom).rotate((PI / 6.0).radians());
|
||||
lightning.into()
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
// ==========================
|
||||
// === Define Icons Macro ===
|
||||
// ==========================
|
||||
|
||||
// Lint gives an erroneous warning: the main is actually needed, because we generate modules here.
|
||||
#[allow(clippy::needless_doctest_main)]
|
||||
/// Macro for defining icon set.
|
||||
///
|
||||
/// The macro takes many modules with attached "variant name". Inside the modules, there should
|
||||
/// be icon defined with `ensogl::define_shape_system!` macro. The macro will also generate an
|
||||
/// enum called `Id` gathering all icon' "variant names". The enum will allow for dynamically
|
||||
/// creating given icon shape view (returned as [`crate::icon::AnyIcon`]).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ensogl::prelude::*;
|
||||
/// use ensogl::display::shape::*;
|
||||
/// use ide_view_component_group::icon;
|
||||
/// use ide_view_component_group::define_icons;
|
||||
///
|
||||
/// define_icons! {
|
||||
/// /// The example of icon.
|
||||
/// pub mod icon1(Icon1) {
|
||||
/// // This is a normal module and you may define whatever you want. It must however
|
||||
/// // define shape system with the macro below; otherwise the generated code wont compile.
|
||||
/// //
|
||||
/// // `use super::*` import is added silently.
|
||||
/// ensogl::define_shape_system! {
|
||||
/// (style:Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
/// Plane().into()
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub mod icon2(Icon2) {
|
||||
/// ensogl::define_shape_system! {
|
||||
/// (style:Style, strong_color: Vector4, weak_color: Vector4) {
|
||||
/// Plane().fill(strong_color).into()
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main () {
|
||||
/// let app = ensogl::application::Application::new("root");
|
||||
/// let logger = Logger::new("icon");
|
||||
/// let icon1 = Id::Icon1.create_shape(&logger, Vector2(10.0, 10.0));
|
||||
/// let icon2_id: Id = "Icon2".parse().unwrap();
|
||||
/// assert_eq!(icon2_id, Id::Icon2);
|
||||
/// let icon2 = icon2_id.create_shape(&logger, Vector2(11.0, 11.0));
|
||||
/// app.display.default_scene.add_child(&icon1);
|
||||
/// app.display.default_scene.add_child(&icon2);
|
||||
///
|
||||
/// // Invalid icon
|
||||
/// let icon3 = "Icon3".parse::<Id>();
|
||||
/// assert!(icon3.is_err());
|
||||
/// }
|
||||
#[macro_export]
|
||||
macro_rules! define_icons {
|
||||
($(
|
||||
$(#$meta:tt)*
|
||||
pub mod $name:ident($variant:ident) {
|
||||
$($content:tt)*
|
||||
}
|
||||
)*) => {
|
||||
$(
|
||||
$(#$meta)*
|
||||
pub mod $name {
|
||||
use super::*;
|
||||
$($content)*
|
||||
}
|
||||
)*
|
||||
|
||||
/// An identifier of one of the icons generated by the same `define_icons` macro invocation.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Id {
|
||||
$($variant),*
|
||||
}
|
||||
|
||||
impl Id {
|
||||
/// Create icon's shape with given size.
|
||||
pub fn create_shape(&self, logger: impl AnyLogger, size: Vector2) -> $crate::icon::Any {
|
||||
match self {$(
|
||||
Self::$variant => {
|
||||
let view = $name::View::new(logger);
|
||||
view.size.set(size);
|
||||
let strong_color = view.strong_color.clone_ref();
|
||||
let weak_color = view.weak_color.clone_ref();
|
||||
let view = Box::new(view);
|
||||
$crate::icon::Any {view, strong_color, weak_color}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
/// Call `f` for each possible icon id.
|
||||
pub fn for_each<F: FnMut(Self)>(mut f: F) {
|
||||
$(f(Self::$variant);)*
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Id {
|
||||
type Err = $crate::icon::UnknownIcon;
|
||||
fn from_str(s: &str) -> Result<Id, Self::Err> {
|
||||
match s {
|
||||
$(stringify!($variant) => Ok(Self::$variant),)*
|
||||
name => Err(Self::Err {name: name.to_owned() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@
|
||||
//! component group boundaries. See `Header Backgound` section in the [`Model::resize`] method.
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
// === Features ===
|
||||
#![feature(option_result_contains)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
@ -29,22 +31,22 @@
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use ensogl_core::application::traits::*;
|
||||
use ensogl_core::display::shape::*;
|
||||
use ensogl_core::prelude::*;
|
||||
use crate::prelude::*;
|
||||
use ensogl::application::traits::*;
|
||||
|
||||
use crate::display::scene::layer;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl_core::application::shortcut::Shortcut;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::camera::Camera2d;
|
||||
use ensogl_core::display::scene::layer;
|
||||
use ensogl::application::shortcut::Shortcut;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color;
|
||||
use ensogl::data::text;
|
||||
use ensogl::display;
|
||||
use ensogl::display::camera::Camera2d;
|
||||
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_shadow as shadow;
|
||||
use ensogl_text as text;
|
||||
|
||||
|
||||
// ==============
|
||||
@ -52,12 +54,20 @@ use ensogl_text as text;
|
||||
// ==============
|
||||
|
||||
pub mod entry;
|
||||
pub mod icon;
|
||||
pub mod wide;
|
||||
|
||||
pub use entry::View as Entry;
|
||||
|
||||
|
||||
|
||||
/// A module containing common imports.
|
||||
pub mod prelude {
|
||||
pub use ensogl::application::traits::*;
|
||||
pub use ensogl::display::shape::*;
|
||||
pub use ensogl::prelude::*;
|
||||
}
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
@ -86,7 +96,7 @@ const HEADER_SHADOW_PEAK: f32 = list_view::entry::HEIGHT / 2.0;
|
||||
pub mod background {
|
||||
use super::*;
|
||||
|
||||
ensogl_core::define_shape_system! {
|
||||
ensogl::define_shape_system! {
|
||||
below = [list_view::background];
|
||||
(style:Style, color:Vector4) {
|
||||
let color = Var::<color::Rgba>::from(color);
|
||||
@ -103,7 +113,7 @@ pub mod background {
|
||||
pub mod header_background {
|
||||
use super::*;
|
||||
|
||||
ensogl_core::define_shape_system! {
|
||||
ensogl::define_shape_system! {
|
||||
above = [background, list_view::background];
|
||||
(style:Style, color:Vector4, height: f32, shadow_height_multiplier: f32) {
|
||||
let color = Var::<color::Rgba>::from(color);
|
||||
@ -133,9 +143,9 @@ pub mod header_background {
|
||||
pub mod header_overlay {
|
||||
use super::*;
|
||||
|
||||
use ensogl_core::display::shape::constants::HOVER_COLOR;
|
||||
use ensogl::display::shape::constants::HOVER_COLOR;
|
||||
|
||||
ensogl_core::define_shape_system! {
|
||||
ensogl::define_shape_system! {
|
||||
above = [background];
|
||||
() {
|
||||
let bg_color = HOVER_COLOR;
|
||||
@ -182,11 +192,75 @@ impl HeaderGeometry {
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Colors ===
|
||||
// ==============
|
||||
|
||||
/// Colors used in the Component Group View.
|
||||
///
|
||||
/// This structure, used in both [`ide_component_group::View`] and
|
||||
/// [`ide_component_group::wide::View`] can be created from single "main color" input. Each of
|
||||
/// these colors will be computed by mixing "main color" with application background - for details,
|
||||
/// see [`Colors::from_main_color`].
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct Colors {
|
||||
// Note: The FRP nodes below must be samplers, otherwise their values set during initialization
|
||||
// (by emitting `init` event in `Colors::from_main_color) will be lost - the FRP system does
|
||||
// not keep value of nodes if they are not connected to anything, and those nodes won't be
|
||||
// before returning from `from_main_color`.
|
||||
pub icon_strong: frp::Sampler<color::Rgba>,
|
||||
pub icon_weak: frp::Sampler<color::Rgba>,
|
||||
pub header_text: frp::Sampler<color::Rgba>,
|
||||
pub entry_text: frp::Sampler<color::Rgba>,
|
||||
pub background: frp::Sampler<color::Rgba>,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
/// Constructs [`Colors`] structure, where each variant is based on the "main" `color`
|
||||
/// parameter.
|
||||
pub fn from_main_color(
|
||||
network: &frp::Network,
|
||||
style: &StyleWatchFrp,
|
||||
color: &frp::Stream<color::Rgba>,
|
||||
is_dimmed: &frp::Stream<bool>,
|
||||
) -> Self {
|
||||
fn mix((c1, c2): &(color::Rgba, color::Rgba), coefficient: &f32) -> color::Rgba {
|
||||
color::mix(*c1, *c2, *coefficient)
|
||||
}
|
||||
let app_bg = style.get_color(ensogl_hardcoded_theme::application::background);
|
||||
let header_intensity = style.get_number(theme::header::text::color_intensity);
|
||||
let bg_intensity = style.get_number(theme::background_color_intensity);
|
||||
let dimmed_intensity = style.get_number(theme::dimmed_color_intensity);
|
||||
let icon_weak_intensity = style.get_number(theme::entry_list::icon::weak_color_intensity);
|
||||
let entry_text_ = style.get_color(theme::entry_list::text::color);
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
one <- init.constant(1.0);
|
||||
let is_dimmed = is_dimmed.clone_ref();
|
||||
intensity <- is_dimmed.switch(&one, &dimmed_intensity);
|
||||
app_bg <- all(&app_bg, &init)._0();
|
||||
app_bg_and_input <- all(&app_bg, color);
|
||||
main <- app_bg_and_input.all_with(&intensity, mix);
|
||||
app_bg_and_main <- all(&app_bg, &main);
|
||||
header_text <- app_bg_and_main.all_with(&header_intensity, mix).sampler();
|
||||
bg <- app_bg_and_main.all_with(&bg_intensity, mix).sampler();
|
||||
app_bg_and_entry_text <- all(&app_bg, &entry_text_);
|
||||
entry_text <- app_bg_and_entry_text.all_with(&intensity, mix).sampler();
|
||||
icon_weak <- app_bg_and_main.all_with(&icon_weak_intensity, mix).sampler();
|
||||
icon_strong <- main.sampler();
|
||||
}
|
||||
init.emit(());
|
||||
Self { icon_weak, icon_strong, header_text, entry_text, background: bg }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===========
|
||||
// === FRP ===
|
||||
// ===========
|
||||
|
||||
ensogl_core::define_endpoints_2! {
|
||||
ensogl::define_endpoints_2! {
|
||||
Input {
|
||||
/// Accept the currently selected suggestion. Should be bound to "Suggestion Acceptance Key"
|
||||
/// described in
|
||||
@ -225,19 +299,24 @@ impl component::Frp<Model> for Frp {
|
||||
let out = &api.output;
|
||||
let header_text_font = style.get_text(theme::header::text::font);
|
||||
let header_text_size = style.get_number(theme::header::text::size);
|
||||
let entry_list_padding = style.get_number(theme::entry_list::padding);
|
||||
|
||||
|
||||
// === Geometry ===
|
||||
|
||||
frp::extend! { network
|
||||
let header_geometry = HeaderGeometry::from_style(style, network);
|
||||
height <- all_with(&input.set_entries, &header_geometry, |entries, header_geom| {
|
||||
entries.entry_count() as f32 * list_view::entry::HEIGHT + header_geom.height
|
||||
});
|
||||
height <- all_with3(&input.set_entries, &header_geometry, &entry_list_padding,
|
||||
|entries, header_geom, padding| {
|
||||
let entries_height = entries.entry_count() as f32 * list_view::entry::HEIGHT;
|
||||
entries_height + header_geom.height + padding
|
||||
}
|
||||
);
|
||||
out.size <+ all_with(&input.set_width, &height, |w, h| Vector2(*w, *h));
|
||||
size_and_header_geometry <- all3(&out.size, &header_geometry, &input.set_header_pos);
|
||||
eval size_and_header_geometry(((size, hdr_geom, hdr_pos))
|
||||
model.resize(*size, *hdr_geom, *hdr_pos)
|
||||
size_and_header_geom_and_padding <- all(&size_and_header_geometry, &entry_list_padding);
|
||||
eval size_and_header_geom_and_padding((((size, hdr_geom, hdr_pos), padding))
|
||||
model.resize(*size, *hdr_geom, *hdr_pos, *padding)
|
||||
);
|
||||
|
||||
|
||||
@ -249,36 +328,15 @@ impl component::Frp<Model> for Frp {
|
||||
|
||||
|
||||
// === Colors ===
|
||||
|
||||
fn mix(colors: &(color::Rgba, color::Rgba), coefficient: &f32) -> color::Rgba {
|
||||
color::mix(colors.0, colors.1, *coefficient)
|
||||
}
|
||||
let app_bg_color = style.get_color(ensogl_hardcoded_theme::application::background);
|
||||
let header_intensity = style.get_number(theme::header::text::color_intensity);
|
||||
let bg_intensity = style.get_number(theme::background_color_intensity);
|
||||
let dimmed_intensity = style.get_number(theme::dimmed_color_intensity);
|
||||
let entry_text_color = style.get_color(theme::entries::text::color);
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
one <- init.constant(1.0);
|
||||
intensity <- input.set_dimmed.switch(&one, &dimmed_intensity);
|
||||
app_bg_color <- all(&app_bg_color, &init)._0();
|
||||
app_bg_and_input_color <- all(&app_bg_color, &input.set_color);
|
||||
main_color <- app_bg_and_input_color.all_with(&intensity, mix);
|
||||
app_bg_and_main_color <- all(&app_bg_color, &main_color);
|
||||
header_color <- app_bg_and_main_color.all_with(&header_intensity, mix);
|
||||
bg_color <- app_bg_and_main_color.all_with(&bg_intensity, mix);
|
||||
app_bg_and_entry_text_color <- all(&app_bg_color, &entry_text_color);
|
||||
entry_color_with_intensity <- app_bg_and_entry_text_color.all_with(&intensity, mix);
|
||||
entry_color_sampler <- entry_color_with_intensity.sampler();
|
||||
}
|
||||
let params = entry::Params { color: entry_color_sampler };
|
||||
let colors = Colors::from_main_color(network, style, &input.set_color, &input.set_dimmed);
|
||||
let params = entry::Params { colors: colors.clone_ref() };
|
||||
model.entries.set_entry_params_and_recreate_entries(params);
|
||||
|
||||
|
||||
// === Header ===
|
||||
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
header_text_font <- all(&header_text_font, &init)._0();
|
||||
model.header.set_font <+ header_text_font;
|
||||
header_text_size <- all(&header_text_size, &init)._0();
|
||||
@ -289,9 +347,9 @@ impl component::Frp<Model> for Frp {
|
||||
model.update_header_width(*size, *hdr_geom);
|
||||
})
|
||||
);
|
||||
model.header.set_default_color <+ header_color;
|
||||
eval bg_color((c) model.background.color.set(c.into()));
|
||||
eval bg_color((c) model.header_background.color.set(c.into()));
|
||||
model.header.set_default_color <+ colors.header_text;
|
||||
eval colors.background((c) model.background.color.set(c.into()));
|
||||
eval colors.background((c) model.header_background.color.set(c.into()));
|
||||
}
|
||||
|
||||
|
||||
@ -330,12 +388,11 @@ impl component::Frp<Model> for Frp {
|
||||
out.is_header_selected <+ bool(&deselect_header, &select_header);
|
||||
model.entries.select_entry <+ select_header.constant(None);
|
||||
|
||||
out.selection_position_target <+ all_with4(
|
||||
out.selection_position_target <+ all_with3(
|
||||
&out.is_header_selected,
|
||||
&out.size,
|
||||
&header_geometry,
|
||||
&model.entries.selection_position_target,
|
||||
f!((h_sel, size, h, esp) model.selection_position(*h_sel, *size, *h, *esp))
|
||||
f!((h_sel, size, esp) model.selection_position(*h_sel, *size, *esp))
|
||||
);
|
||||
}
|
||||
|
||||
@ -343,7 +400,7 @@ impl component::Frp<Model> for Frp {
|
||||
}
|
||||
|
||||
fn default_shortcuts() -> Vec<Shortcut> {
|
||||
use ensogl_core::application::shortcut::ActionType::*;
|
||||
use ensogl::application::shortcut::ActionType::*;
|
||||
(&[(Press, "tab", "accept_suggestion")])
|
||||
.iter()
|
||||
.map(|(a, b, c)| View::self_shortcut(*a, *b, *c))
|
||||
@ -462,7 +519,13 @@ impl Model {
|
||||
self.header.add_to_scene_layer(&layers.header_text);
|
||||
}
|
||||
|
||||
fn resize(&self, size: Vector2, header_geometry: HeaderGeometry, header_pos: f32) {
|
||||
fn resize(
|
||||
&self,
|
||||
size: Vector2,
|
||||
header_geometry: HeaderGeometry,
|
||||
header_pos: f32,
|
||||
entry_list_padding: f32,
|
||||
) {
|
||||
// === Background ===
|
||||
|
||||
self.background.size.set(size);
|
||||
@ -511,8 +574,8 @@ impl Model {
|
||||
|
||||
// === Entries ===
|
||||
|
||||
self.entries.resize(size - Vector2(0.0, header_height));
|
||||
self.entries.set_position_y(-header_height / 2.0);
|
||||
self.entries.resize(size - Vector2(0.0, header_height - entry_list_padding));
|
||||
self.entries.set_position_y(-header_height / 2.0 + entry_list_padding / 2.0);
|
||||
}
|
||||
|
||||
fn update_header_width(&self, size: Vector2, header_geometry: HeaderGeometry) {
|
||||
@ -526,11 +589,10 @@ impl Model {
|
||||
&self,
|
||||
is_header_selected: bool,
|
||||
size: Vector2,
|
||||
header_geometry: HeaderGeometry,
|
||||
entries_selection_position: Vector2,
|
||||
) -> Vector2 {
|
||||
if is_header_selected {
|
||||
Vector2(0.0, size.y / 2.0 - header_geometry.height / 2.0)
|
||||
Vector2(0.0, size.y / 2.0 - list_view::entry::HEIGHT / 2.0)
|
||||
} else {
|
||||
self.entries.position().xy() + entries_selection_position
|
||||
}
|
||||
@ -564,7 +626,7 @@ pub type View = component::ComponentView<Model, Frp>;
|
||||
mod tests {
|
||||
use super::*;
|
||||
use enso_frp::future::EventOutputExt;
|
||||
use ensogl_core::control::io::mouse;
|
||||
use ensogl::control::io::mouse;
|
||||
use ensogl_list_view::entry::AnyModelProvider;
|
||||
|
||||
macro_rules! expect_entry_selected {
|
||||
|
@ -11,19 +11,17 @@
|
||||
//!
|
||||
//! [Component Group]: crate::component_group::View
|
||||
|
||||
use ensogl_core::application::traits::*;
|
||||
use ensogl_core::display::shape::*;
|
||||
use ensogl_core::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::entry;
|
||||
use crate::Colors;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl_core::application::shortcut::Shortcut;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color::Rgba;
|
||||
use ensogl_core::display;
|
||||
use ensogl::application::shortcut::Shortcut;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color::Rgba;
|
||||
use ensogl::display;
|
||||
use ensogl_gui_component::component;
|
||||
use ensogl_hardcoded_theme::application::component_browser::component_group as theme;
|
||||
use ensogl_label::Label;
|
||||
use ensogl_list_view as list_view;
|
||||
use list_view::entry::AnyModelProvider;
|
||||
@ -63,7 +61,7 @@ newtype_prim! {
|
||||
pub mod background {
|
||||
use super::*;
|
||||
|
||||
ensogl_core::define_shape_system! {
|
||||
ensogl::define_shape_system! {
|
||||
below = [list_view::background];
|
||||
(style:Style, color:Vector4) {
|
||||
let color = Var::<Rgba>::from(color);
|
||||
@ -118,7 +116,7 @@ impl<const COLUMNS: usize> list_view::entry::ModelProvider<Entry> for ModelProvi
|
||||
// === FRP ===
|
||||
// ===========
|
||||
|
||||
ensogl_core::define_endpoints_2! {
|
||||
ensogl::define_endpoints_2! {
|
||||
Input {
|
||||
/// Accept the currently selected suggestion. Should be bound to "Suggestion Acceptance Key"
|
||||
/// described in
|
||||
@ -126,7 +124,8 @@ ensogl_core::define_endpoints_2! {
|
||||
accept_suggestion(),
|
||||
select_entry(ColumnId, entry::Id),
|
||||
set_entries(AnyModelProvider<Entry>),
|
||||
set_background_color(Rgba),
|
||||
set_color(Rgba),
|
||||
set_dimmed(bool),
|
||||
set_width(f32),
|
||||
set_no_items_label_text(String),
|
||||
}
|
||||
@ -154,13 +153,11 @@ impl<const COLUMNS: usize> component::Frp<Model<COLUMNS>> for Frp {
|
||||
let network = &api.network;
|
||||
let input = &api.input;
|
||||
let out = &api.output;
|
||||
let entry_text_color = style.get_color(theme::entries::text::color);
|
||||
let colors = Colors::from_main_color(network, style, &input.set_color, &input.set_dimmed);
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
entry_count <- input.set_entries.map(|p| p.entry_count());
|
||||
out.entry_count <+ entry_count;
|
||||
entry_text_color <- all(&entry_text_color, &init)._0();
|
||||
entry_color_sampler <- entry_text_color.sampler();
|
||||
|
||||
selected_column_and_entry <- any(...);
|
||||
update_selected_entry <- selected_column_and_entry.sample(&out.size);
|
||||
@ -173,7 +170,7 @@ impl<const COLUMNS: usize> component::Frp<Model<COLUMNS>> for Frp {
|
||||
}
|
||||
});
|
||||
|
||||
eval input.set_background_color((c) model.background.color.set(c.into()));
|
||||
eval colors.background((c) model.background.color.set(c.into()));
|
||||
|
||||
eval input.set_no_items_label_text((text) model.set_no_items_label_text(text));
|
||||
|
||||
@ -231,13 +228,13 @@ impl<const COLUMNS: usize> component::Frp<Model<COLUMNS>> for Frp {
|
||||
eval entries((e) column.set_entries(e));
|
||||
_eval <- all_with(&entries, &out.size, f!((_, size) column.resize_and_place(*size)));
|
||||
}
|
||||
let params = entry::Params { color: entry_color_sampler.clone() };
|
||||
let params = entry::Params { colors: colors.clone_ref() };
|
||||
column.list_view.set_entry_params_and_recreate_entries(params);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_shortcuts() -> Vec<Shortcut> {
|
||||
use ensogl_core::application::shortcut::ActionType::*;
|
||||
use ensogl::application::shortcut::ActionType::*;
|
||||
(&[(Press, "tab", "accept_suggestion")])
|
||||
.iter()
|
||||
.map(|(a, b, c)| View::<COLUMNS>::self_shortcut(*a, *b, *c))
|
||||
|
@ -9,5 +9,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
debug-scene-component-group = { path = "component-group" }
|
||||
debug-scene-icons = { path = "icons" }
|
||||
debug-scene-interface = { path = "interface" }
|
||||
debug-scene-visualization = { path = "visualization" }
|
||||
|
@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
enso-frp = { path = "../../../../../lib/rust/frp" }
|
||||
enso-text = { path = "../../../../../lib/rust/text" }
|
||||
ensogl-core = { path = "../../../../../lib/rust/ensogl/core" }
|
||||
ensogl-selector = { path = "../../../../../lib/rust/ensogl/component/selector" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
|
@ -16,16 +16,21 @@ use ensogl_core::display::shape::*;
|
||||
use ensogl_core::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use enso_text::Bytes;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_core::frp;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_list_view as list_view;
|
||||
use ensogl_list_view::entry::GlyphHighlightedLabelModel;
|
||||
use ensogl_scroll_area::ScrollArea;
|
||||
use ensogl_selector as selector;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ide_view_component_group as component_group;
|
||||
use ide_view_component_group::entry;
|
||||
use ide_view_component_group::icon;
|
||||
use ide_view_component_group::Entry;
|
||||
use list_view::entry::AnyModelProvider;
|
||||
|
||||
|
||||
@ -50,15 +55,15 @@ pub fn main() {
|
||||
// === Mock Entries ===
|
||||
// ====================
|
||||
|
||||
const PREPARED_ITEMS: &[&str; 8] = &[
|
||||
"long sample entry with text overflowing the width",
|
||||
"convert",
|
||||
"table input",
|
||||
"text input",
|
||||
"number input",
|
||||
"table output",
|
||||
"data output",
|
||||
"data input",
|
||||
const PREPARED_ITEMS: &[(&str, icon::Id)] = &[
|
||||
("long sample entry with text overflowing the width", icon::Id::Star),
|
||||
("convert", icon::Id::Convert),
|
||||
("table input", icon::Id::DataInput),
|
||||
("text input", icon::Id::TextInput),
|
||||
("number input", icon::Id::NumberInput),
|
||||
("table output", icon::Id::TableEdit),
|
||||
("dataframe clean", icon::Id::DataframeClean),
|
||||
("data input", icon::Id::DataInput),
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -69,23 +74,40 @@ struct MockEntries {
|
||||
|
||||
impl MockEntries {
|
||||
fn new(count: usize) -> Rc<Self> {
|
||||
const HIGHLIGHTED_ENTRY_NAME: &str = "convert";
|
||||
const HIGHLIGHTED_RANGE: Range<Bytes> = Bytes(0)..Bytes(3);
|
||||
Rc::new(Self {
|
||||
entries: PREPARED_ITEMS.iter().cycle().take(count).map(|&label| label.into()).collect(),
|
||||
entries: PREPARED_ITEMS
|
||||
.iter()
|
||||
.cycle()
|
||||
.take(count)
|
||||
.map(|&(label, icon)| entry::Model {
|
||||
icon,
|
||||
highlighted_text: GlyphHighlightedLabelModel {
|
||||
label: label.to_owned(),
|
||||
highlighted: if label == HIGHLIGHTED_ENTRY_NAME {
|
||||
vec![HIGHLIGHTED_RANGE.into()]
|
||||
} else {
|
||||
default()
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
count: Cell::new(count),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_entry(&self, id: list_view::entry::Id) -> Option<component_group::entry::Model> {
|
||||
fn get_entry(&self, id: list_view::entry::Id) -> Option<entry::Model> {
|
||||
self.entries.get(id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl list_view::entry::ModelProvider<component_group::Entry> for MockEntries {
|
||||
impl list_view::entry::ModelProvider<Entry> for MockEntries {
|
||||
fn entry_count(&self) -> usize {
|
||||
self.count.get()
|
||||
}
|
||||
|
||||
fn get(&self, id: list_view::entry::Id) -> Option<component_group::entry::Model> {
|
||||
fn get(&self, id: list_view::entry::Id) -> Option<entry::Model> {
|
||||
self.get_entry(id)
|
||||
}
|
||||
}
|
||||
@ -162,6 +184,7 @@ fn create_component_group(
|
||||
component_group
|
||||
}
|
||||
|
||||
|
||||
fn color_component_slider(app: &Application, caption: &str) -> selector::NumberPicker {
|
||||
let slider = app.new_view::<selector::NumberPicker>();
|
||||
app.display.add_child(&slider);
|
||||
@ -196,7 +219,6 @@ fn init(app: &Application) {
|
||||
theme::builtin::light::enable(&app);
|
||||
|
||||
let network = frp::Network::new("Component Group Debug Scene");
|
||||
|
||||
let scroll_area = ScrollArea::new(app);
|
||||
scroll_area.set_position_xy(Vector2(0.0, 100.0));
|
||||
scroll_area.resize(Vector2(170.0, 400.0));
|
||||
|
14
app/gui/view/debug_scene/icons/Cargo.toml
Normal file
14
app/gui/view/debug_scene/icons/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "debug-scene-icons"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
ensogl = { path = "../../../../../lib/rust/ensogl" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ide-view-component-group = { path = "../../component-browser/component-group" }
|
||||
wasm-bindgen = { version = "=0.2.78" }
|
90
app/gui/view/debug_scene/icons/src/lib.rs
Normal file
90
app/gui/view/debug_scene/icons/src/lib.rs
Normal file
@ -0,0 +1,90 @@
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
|
||||
use ensogl::system::web::traits::*;
|
||||
use ide_view_component_group::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use ensogl::application::Application;
|
||||
use ensogl::data::color;
|
||||
use ensogl::display::navigation::navigator::Navigator;
|
||||
use ensogl::display::DomSymbol;
|
||||
use ensogl::system::web;
|
||||
use ide_view_component_group::icon;
|
||||
use ide_view_component_group::icon::SHRINK_AMOUNT;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Frame ===
|
||||
// =============
|
||||
|
||||
/// A rectangular frame to mark the edges of icon. It can be displayed under them to see if they
|
||||
/// are centered properly.
|
||||
mod frame {
|
||||
use super::*;
|
||||
|
||||
ensogl::define_shape_system! {
|
||||
(style:Style) {
|
||||
let inner = Rect((icon::SIZE.px(), icon::SIZE.px()));
|
||||
let outer = inner.grow(0.2.px());
|
||||
let shape = (outer - inner).fill(color::Rgba::black());
|
||||
shape.shrink(SHRINK_AMOUNT.px()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Entry Point ===
|
||||
// ===================
|
||||
|
||||
/// An entry point that displays all icon on a grid.
|
||||
#[wasm_bindgen]
|
||||
#[allow(dead_code)]
|
||||
pub fn entry_point_searcher_icons() {
|
||||
let logger = Logger::new("Icons example");
|
||||
let app = Application::new("root");
|
||||
ensogl_hardcoded_theme::builtin::dark::register(&app);
|
||||
ensogl_hardcoded_theme::builtin::light::register(&app);
|
||||
ensogl_hardcoded_theme::builtin::light::enable(&app);
|
||||
let world = app.display.clone();
|
||||
mem::forget(app);
|
||||
let scene = &world.default_scene;
|
||||
mem::forget(Navigator::new(scene, &scene.camera()));
|
||||
|
||||
|
||||
// === Grid ===
|
||||
|
||||
let grid_div = web::document.create_div_or_panic();
|
||||
grid_div.set_style_or_warn("width", "1000px");
|
||||
grid_div.set_style_or_warn("height", "16px");
|
||||
grid_div.set_style_or_warn("background-size", "1.0px 1.0px");
|
||||
grid_div.set_style_or_warn(
|
||||
"background-image",
|
||||
"linear-gradient(to right, grey 0.05px, transparent 0.05px),
|
||||
linear-gradient(to bottom, grey 0.05px, transparent 0.05px)",
|
||||
);
|
||||
|
||||
let grid = DomSymbol::new(&grid_div);
|
||||
scene.dom.layers.back.manage(&grid);
|
||||
world.add_child(&grid);
|
||||
grid.set_size(Vector2(1000.0, icon::SIZE));
|
||||
mem::forget(grid);
|
||||
|
||||
|
||||
// === Icons ===
|
||||
|
||||
let mut x = 0.0;
|
||||
icon::Id::for_each(|id| {
|
||||
let shape = id.create_shape(&logger, Vector2(icon::SIZE, icon::SIZE));
|
||||
shape.weak_color.set(color::Rgba(0.475, 0.494, 0.145, 1.0).into());
|
||||
shape.strong_color.set(color::Rgba(0.612, 0.627, 0.388, 1.0).into());
|
||||
shape.set_position_x(x);
|
||||
x += 20.0;
|
||||
world.add_child(&shape);
|
||||
mem::forget(shape);
|
||||
});
|
||||
}
|
@ -21,5 +21,6 @@
|
||||
// ==============
|
||||
|
||||
pub use debug_scene_component_group as component_group;
|
||||
pub use debug_scene_icons as icons;
|
||||
pub use debug_scene_interface as interface;
|
||||
pub use debug_scene_visualization as visualization;
|
||||
|
@ -22,8 +22,6 @@ use ensogl_component::list_view::ListView;
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod icons;
|
||||
|
||||
pub use ensogl_component::list_view::entry;
|
||||
|
||||
|
||||
@ -38,12 +36,12 @@ pub const SEARCHER_WIDTH: f32 = 480.0;
|
||||
///
|
||||
/// Because we don't implement clipping yet, the best UX is when searcher height is almost multiple
|
||||
/// of entry height + padding.
|
||||
pub const SEARCHER_HEIGHT: f32 = 184.5;
|
||||
pub const SEARCHER_HEIGHT: f32 = 183.5;
|
||||
|
||||
const ACTION_LIST_GAP: f32 = 180.0;
|
||||
const ACTION_LIST_WIDTH: f32 = 180.0;
|
||||
const LIST_DOC_GAP: f32 = 15.0;
|
||||
const DOCUMENTATION_WIDTH: f32 = SEARCHER_WIDTH - ACTION_LIST_GAP - LIST_DOC_GAP;
|
||||
const ACTION_LIST_X: f32 = (ACTION_LIST_GAP - SEARCHER_WIDTH) / 2.0;
|
||||
const DOCUMENTATION_WIDTH: f32 = SEARCHER_WIDTH - ACTION_LIST_WIDTH - LIST_DOC_GAP;
|
||||
const ACTION_LIST_X: f32 = (ACTION_LIST_WIDTH - SEARCHER_WIDTH) / 2.0;
|
||||
const DOCUMENTATION_X: f32 = (SEARCHER_WIDTH - DOCUMENTATION_WIDTH) / 2.0;
|
||||
|
||||
|
||||
@ -150,7 +148,7 @@ impl Model {
|
||||
}
|
||||
|
||||
fn set_height(&self, h: f32) {
|
||||
self.list.resize(Vector2(ACTION_LIST_GAP, h));
|
||||
self.list.resize(Vector2(ACTION_LIST_WIDTH, h));
|
||||
self.documentation.visualization_frp.inputs.set_size.emit(Vector2(DOCUMENTATION_WIDTH, h));
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,11 @@
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
|
||||
use enso_build::prelude::*;
|
||||
|
||||
|
||||
|
||||
fn main() -> Result {
|
||||
enso_build::cli::main::main()
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ define_themes! { [light:0, dark:1]
|
||||
}
|
||||
height = 27.0, 27.0;
|
||||
padding {
|
||||
left = 16.5, 16.5;
|
||||
left = 11.0, 11.0;
|
||||
right = 2.5, 2.5;
|
||||
bottom = 5.0, 5.0;
|
||||
}
|
||||
@ -203,26 +203,33 @@ define_themes! { [light:0, dark:1]
|
||||
offset_y = shadow::offset_y , shadow::offset_y;
|
||||
}
|
||||
}
|
||||
entries {
|
||||
background_color_intensity = 0.2, 0.2;
|
||||
dimmed_color_intensity = 0.5, 0.5;
|
||||
entry_list {
|
||||
text {
|
||||
font = "DejaVuSans", "DejaVuSans";
|
||||
size = 12.0, 12.0;
|
||||
color = Rgba(0.4,0.4,0.4,1.0), Rgba(0.4,0.4,0.4,1.0);
|
||||
highlight_bold = 0.02, 0.02;
|
||||
}
|
||||
icon_text_gap = 5.0, 5.0;
|
||||
icon {
|
||||
size = 16.0, 16.0;
|
||||
weak_color_intensity = 0.5, 0.5;
|
||||
}
|
||||
padding = 4.0, 4.0;
|
||||
entry {
|
||||
padding = 7.0, 7.0;
|
||||
}
|
||||
highlight {
|
||||
height = 30.0, 30.0
|
||||
}
|
||||
}
|
||||
background_color_intensity = 0.2, 0.2;
|
||||
dimmed_color_intensity = 0.5, 0.5;
|
||||
}
|
||||
}
|
||||
searcher {
|
||||
action_list_gap = 10.0, 10.0;
|
||||
padding = 5.0, 5.0;
|
||||
selection {
|
||||
padding {
|
||||
horizontal = 2.0, 2.0;
|
||||
vertical = 2.0, 2.0
|
||||
}
|
||||
}
|
||||
icons {
|
||||
favorites = Rgba(0.98,0.584,0.122,1.0) , Rgba(0.98,0.584,0.122,1.0);
|
||||
io {
|
||||
@ -557,14 +564,19 @@ define_themes! { [light:0, dark:1]
|
||||
highlight = Rgba(0.906,0.914,0.922,1.0) , Lcha(1.0,0.0,0.0,0.15); // rgb(231,233,235)
|
||||
text = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
|
||||
text {
|
||||
highlight = selection, Rgba(0.275,0.549,0.839,1.0); // ... , rgb(70 140 214)
|
||||
selection = Lcha(0.7,0.0,0.125,0.7) , Lcha(0.7,0.0,0.125,0.7);
|
||||
font = "DejaVuSansMono", "DejaVuSansMono";
|
||||
size = 12.0, 12.0;
|
||||
highlight_bold = 0.02, 0.02;
|
||||
}
|
||||
entry {
|
||||
padding = 10.0, 10.0;
|
||||
}
|
||||
highlight {
|
||||
height = 24.0, 24.0;
|
||||
corner_radius = 12.0, 12.0;
|
||||
}
|
||||
padding = 5.0, 5.0;
|
||||
}
|
||||
}
|
||||
colors {
|
||||
|
@ -22,8 +22,6 @@ pub mod list;
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// Padding inside entry in pixels.
|
||||
pub const PADDING: f32 = 14.0;
|
||||
/// The overall entry's height (including padding).
|
||||
pub const HEIGHT: f32 = 30.0;
|
||||
|
||||
@ -48,12 +46,12 @@ pub use list::List;
|
||||
///
|
||||
/// The entries should not assume any padding - it will be granted by ListView itself. The Display
|
||||
/// Object position of this component is docked to the middle of left entry's boundary. It differs
|
||||
/// from usual behaviour of EnsoGl components, but makes the entries alignment much simpler.
|
||||
/// from usual behaviour of EnsoGl components, but makes the entries' alignment much simpler.
|
||||
///
|
||||
/// This trait abstracts over model and its updating in order to support re-using shapes and gui
|
||||
/// components, so they are not deleted and created again. The ListView component does not create
|
||||
/// Entry object for each entry provided, and during scrolling, the instantiated objects will be
|
||||
/// reused: they position will be changed and they will be updated using `update` method.
|
||||
/// reused: they position will be changed, and they will be updated using `update` method.
|
||||
pub trait Entry: CloneRef + Debug + display::Object + 'static {
|
||||
/// The model of this entry. The entry should be a representation of data from the Model.
|
||||
/// For example, the entry being just a caption can have [`String`] as its model - the text to
|
||||
@ -90,16 +88,16 @@ pub trait Entry: CloneRef + Debug + display::Object + 'static {
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct Label {
|
||||
display_object: display::object::Instance,
|
||||
pub label: text::Area,
|
||||
text: Rc<RefCell<String>>,
|
||||
max_width_px: Rc<Cell<f32>>,
|
||||
display_object: display::object::Instance,
|
||||
pub label: text::Area,
|
||||
text: frp::Source<String>,
|
||||
max_width_px: frp::Source<f32>,
|
||||
/// The `network` is public to allow extending it in components based on a [`Label`]. This
|
||||
/// should only be done for components that are small extensions of a Label, where creating a
|
||||
/// separate network for them would be an unnecessary overhead.
|
||||
/// Note: Networks extending this field will not outlive [`Label`].
|
||||
pub network: enso_frp::Network,
|
||||
style_watch: StyleWatchFrp,
|
||||
pub network: enso_frp::Network,
|
||||
pub style_watch: StyleWatchFrp,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
@ -108,8 +106,6 @@ impl Label {
|
||||
let logger = Logger::new("list_view::entry::Label");
|
||||
let display_object = display::object::Instance::new(logger);
|
||||
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 style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||
let text_style = style_prefix.sub("text");
|
||||
@ -120,6 +116,8 @@ impl Label {
|
||||
display_object.add_child(&label);
|
||||
frp::extend! { network
|
||||
init <- source::<()>();
|
||||
text <- source::<String>();
|
||||
max_width_px <- source::<f32>();
|
||||
color <- all(&color,&init)._0();
|
||||
font <- all(&font,&init)._0();
|
||||
size <- all(&size,&init)._0();
|
||||
@ -128,15 +126,12 @@ impl Label {
|
||||
label.set_font <+ font;
|
||||
label.set_default_text_size <+ size.map(|v| text::Size(*v));
|
||||
eval size ((size) label.set_position_y(size/2.0));
|
||||
|
||||
label.set_content_truncated <+ all(&text, &max_width_px);
|
||||
}
|
||||
init.emit(());
|
||||
Self { display_object, label, text, max_width_px, network, style_watch }
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -148,15 +143,11 @@ impl Entry for Label {
|
||||
}
|
||||
|
||||
fn update(&self, model: &Self::Model) {
|
||||
self.text.replace(model.clone());
|
||||
self.update_label_content();
|
||||
self.text.emit(model.clone());
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
self.max_width_px.emit(max_width_px);
|
||||
}
|
||||
|
||||
fn set_label_layer(&self, label_layer: &display::scene::Layer) {
|
||||
@ -184,9 +175,10 @@ pub struct GlyphHighlightedLabelModel {
|
||||
}
|
||||
|
||||
/// The [`Entry`] similar to the [`Label`], but allows highlighting some parts of text.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct GlyphHighlightedLabel {
|
||||
inner: Label,
|
||||
pub inner: Label,
|
||||
highlight: frp::Source<Vec<text::Range<text::Bytes>>>,
|
||||
}
|
||||
|
||||
@ -194,19 +186,20 @@ impl Entry for GlyphHighlightedLabel {
|
||||
type Model = GlyphHighlightedLabelModel;
|
||||
type Params = ();
|
||||
|
||||
fn new(app: &Application, style_prefix: &Path, _params: &Self::Params) -> Self {
|
||||
fn new(app: &Application, style_prefix: &Path, (): &Self::Params) -> Self {
|
||||
let inner = Label::new(app, style_prefix);
|
||||
let network = &inner.network;
|
||||
let text_style = style_prefix.sub("text");
|
||||
let highlight_color = inner.style_watch.get_color(text_style.sub("highlight"));
|
||||
let highlight_bold = inner.style_watch.get_number(text_style.sub("highlight_bold"));
|
||||
let label = &inner.label;
|
||||
|
||||
frp::extend! { network
|
||||
highlight <- source::<Vec<text::Range<text::Bytes>>>();
|
||||
highlight_changed <- all(highlight,highlight_color);
|
||||
eval highlight_changed ([label]((highlight,color)) {
|
||||
content_changed <- label.content.constant(());
|
||||
set_highlight <- all(highlight, highlight_bold, content_changed);
|
||||
eval set_highlight ([label]((highlight, bold, ())) {
|
||||
for range in highlight {
|
||||
label.set_color_bytes(range,color);
|
||||
label.set_sdf_bold(range, text::style::SdfBold::new(*bold));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ use ensogl_core::display::style;
|
||||
/// A displayed entry in select component.
|
||||
///
|
||||
/// The Display Object position of this component is docked to the middle of left entry's boundary.
|
||||
/// It differs from usual behaviour of EnsoGL components, but makes the entries alignment much
|
||||
/// simpler: In vast majority of cases we want to align list elements to the left.
|
||||
/// It differs from usual behaviour of EnsoGL components, but makes the entries' alignment much
|
||||
/// simpler: In the vast majority of cases we want to align list elements to the left.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
#[clone_ref(bound = "E:CloneRef")]
|
||||
@ -165,7 +165,7 @@ impl<E: Entry> ListData<E, E::Params> {
|
||||
&self,
|
||||
mut range: Range<entry::Id>,
|
||||
max_width_px: f32,
|
||||
style_prefix: style::Path,
|
||||
style_prefix: &style::Path,
|
||||
) {
|
||||
range.end = range.end.min(self.provider.get().entry_count());
|
||||
if range != self.entries_range.get() {
|
||||
@ -173,7 +173,7 @@ impl<E: Entry> ListData<E, E::Params> {
|
||||
let provider = self.provider.get();
|
||||
let current_entries: HashSet<entry::Id> =
|
||||
with(self.entries.borrow_mut(), |mut entries| {
|
||||
entries.resize_with(range.len(), || self.create_new_entry(&style_prefix));
|
||||
entries.resize_with(range.len(), || self.create_new_entry(style_prefix));
|
||||
entries.iter().filter_map(|entry| entry.id.get()).collect()
|
||||
});
|
||||
let missing = range.clone().filter(|id| !current_entries.contains(id));
|
||||
@ -271,7 +271,6 @@ impl<E: Entry> ListData<E, E::Params> {
|
||||
let entry = E::new(&self.app, style_prefix, &self.entry_params.borrow());
|
||||
let entry = DisplayedEntry { id: default(), entry };
|
||||
entry.entry.set_label_layer(&layer);
|
||||
entry.entry.set_position_x(entry::PADDING);
|
||||
self.add_child(&entry.entry);
|
||||
entry
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ use ensogl_core::data::color;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::scene::layer::Layer;
|
||||
use ensogl_core::display::shape::*;
|
||||
use ensogl_core::display::style;
|
||||
use ensogl_core::Animation;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_shadow as shadow;
|
||||
@ -67,7 +68,8 @@ const DEFAULT_STYLE_PATH: &str = theme::widget::list_view::HERE.str;
|
||||
|
||||
/// The size of shadow under element. It is not counted in the component width and height.
|
||||
pub const SHADOW_PX: f32 = 10.0;
|
||||
const SHAPE_PADDING: f32 = 5.0;
|
||||
/// The additional padding inside list view background and selection, added for better antialiasing
|
||||
pub const SHAPE_MARGIN: f32 = 5.0;
|
||||
|
||||
|
||||
// === Selection ===
|
||||
@ -83,10 +85,8 @@ pub mod selection {
|
||||
(style: Style, color: Vector4, corner_radius: f32) {
|
||||
let sprite_width : Var<Pixels> = "input_size.x".into();
|
||||
let sprite_height : Var<Pixels> = "input_size.y".into();
|
||||
let padding_inner_x = style.get_number(theme::application::searcher::selection::padding::horizontal);
|
||||
let padding_inner_y = style.get_number(theme::application::searcher::selection::padding::vertical);
|
||||
let width = sprite_width - 2.0.px() * SHAPE_PADDING + 2.0.px() * padding_inner_x;
|
||||
let height = sprite_height - 2.0.px() * SHAPE_PADDING + 2.0.px() * padding_inner_y;
|
||||
let width = sprite_width - 2.0.px() * SHAPE_MARGIN;
|
||||
let height = sprite_height - 2.0.px() * SHAPE_MARGIN;
|
||||
let color = Var::<color::Rgba>::from(color);
|
||||
let rect = Rect((&width,&height)).corners_radius(corner_radius);
|
||||
let shape = rect.fill(color);
|
||||
@ -110,8 +110,8 @@ pub mod background {
|
||||
(style: Style, shadow_alpha: f32, corners_radius_px: f32, color: Vector4) {
|
||||
let sprite_width : Var<Pixels> = "input_size.x".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 height = sprite_height - SHADOW_PX.px() * 2.0 - SHAPE_PADDING.px() * 2.0;
|
||||
let width = sprite_width - SHADOW_PX.px() * 2.0 - SHAPE_MARGIN.px() * 2.0;
|
||||
let height = sprite_height - SHADOW_PX.px() * 2.0 - SHAPE_MARGIN.px() * 2.0;
|
||||
let color = Var::<color::Rgba>::from(color);
|
||||
let rect = Rect((&width,&height)).corners_radius(corners_radius_px);
|
||||
let shape = rect.fill(color);
|
||||
@ -184,27 +184,22 @@ impl<E: Entry> Model<E> {
|
||||
self.background.shadow_alpha.set(alpha);
|
||||
}
|
||||
|
||||
fn padding(&self) -> f32 {
|
||||
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
|
||||
// system (#795)
|
||||
let styles = StyleWatch::new(&self.app.display.default_scene.style_sheet);
|
||||
styles.get_number(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
|
||||
/// resized.
|
||||
fn update_after_view_change(&self, view: &View, style_prefix: display::style::Path) {
|
||||
fn update_after_view_change(
|
||||
&self,
|
||||
view: &View,
|
||||
padding: f32,
|
||||
entry_padding: f32,
|
||||
style_prefix: &display::style::Path,
|
||||
) {
|
||||
let visible_entries = Self::visible_entries(view, self.entries.entry_count());
|
||||
let padding = self.doubled_padding_with_shape_padding();
|
||||
let padding = Vector2(padding, padding);
|
||||
let entry_width = view.size.x - padding.x;
|
||||
let padding = Vector2(2.0 * padding, 2.0 * padding);
|
||||
let margin = Vector2(2.0 * SHAPE_MARGIN, 2.0 * SHAPE_MARGIN);
|
||||
let shadow = Vector2(2.0 * SHADOW_PX, 2.0 * SHADOW_PX);
|
||||
self.entries.set_position_x(-view.size.x / 2.0);
|
||||
self.background.size.set(view.size + padding + shadow);
|
||||
let entry_width = view.size.x - 2.0 * entry_padding;
|
||||
self.entries.set_position_x(-view.size.x / 2.0 + entry_padding);
|
||||
self.background.size.set(view.size + padding + shadow + margin);
|
||||
self.scrolled_area.set_position_y(view.size.y / 2.0 - view.position_y);
|
||||
self.entries.update_entries(visible_entries, entry_width, style_prefix);
|
||||
}
|
||||
@ -216,8 +211,7 @@ impl<E: Entry> Model<E> {
|
||||
style_prefix: display::style::Path,
|
||||
) {
|
||||
let visible_entries = Self::visible_entries(view, provider.entry_count());
|
||||
let padding = self.doubled_padding_with_shape_padding();
|
||||
let entry_width = view.size.x - padding;
|
||||
let entry_width = view.size.x;
|
||||
let entries = &self.entries;
|
||||
entries.update_entries_new_provider(provider, visible_entries, entry_width, style_prefix);
|
||||
}
|
||||
@ -338,6 +332,71 @@ ensogl_core::define_endpoints! {
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure containing FRP nodes connected to appropriate style values.
|
||||
///
|
||||
/// [`ListView`] is a general-use component, and it different places it could be styled differently.
|
||||
/// Therefore the [`ListView`] users (developers) may set the style prefix from where the
|
||||
/// style values will be read. This structure keeps a network connecting a style values from a
|
||||
/// particular prefix with its fields. It allows also reconnecting to another prefix without losing
|
||||
/// the fields (so the connections from them will remain intact).
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
struct StyleFrp {
|
||||
style_connection_network: Rc<CloneRefCell<Option<frp::Network>>>,
|
||||
background_color: frp::Any<color::Rgba>,
|
||||
selection_color: frp::Any<color::Rgba>,
|
||||
selection_corner_radius: frp::Any<f32>,
|
||||
selection_height: frp::Any<f32>,
|
||||
padding: frp::Any<f32>,
|
||||
entry_padding: frp::Any<f32>,
|
||||
}
|
||||
|
||||
impl StyleFrp {
|
||||
fn new(network: &frp::Network) -> Self {
|
||||
let style_connection_network = default();
|
||||
frp::extend! { network
|
||||
background_color <- any(...);
|
||||
selection_color <- any(...);
|
||||
selection_corner_radius <- any(...);
|
||||
selection_height <- any(...);
|
||||
padding <- any(...);
|
||||
entry_padding <- any(...);
|
||||
}
|
||||
Self {
|
||||
style_connection_network,
|
||||
background_color,
|
||||
selection_color,
|
||||
selection_corner_radius,
|
||||
selection_height,
|
||||
padding,
|
||||
entry_padding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect the structure's fields with new style prefix. The bindings with the previous
|
||||
/// prefix will be removed.
|
||||
fn connect_with_prefix(&self, style: &StyleWatchFrp, prefix: &style::Path) {
|
||||
let style_connection_network = frp::Network::new("list_view::StyleFrp");
|
||||
let background_color = style.get_color(prefix.sub("background"));
|
||||
let selection_color = style.get_color(prefix.sub("highlight"));
|
||||
let selection_corner_radius =
|
||||
style.get_number(prefix.sub("highlight").sub("corner_radius"));
|
||||
let selection_height = style.get_number(prefix.sub("highlight").sub("height"));
|
||||
let padding = style.get_number(prefix.sub("padding"));
|
||||
let entry_padding = style.get_number(prefix.sub("entry").sub("padding"));
|
||||
frp::extend! { style_connection_network
|
||||
init <- source_();
|
||||
self.background_color <+ all(&background_color, &init)._0();
|
||||
self.selection_color <+ all(&selection_color, &init)._0();
|
||||
self.selection_corner_radius <+ all(&selection_corner_radius, &init)._0();
|
||||
self.selection_height <+ all(&selection_height, &init)._0();
|
||||
self.padding <+ all(&padding, &init)._0();
|
||||
self.entry_padding <+ all(&entry_padding, &init)._0();
|
||||
}
|
||||
// At this point the old network is dropped, and old connections are removed.
|
||||
self.style_connection_network.set(Some(style_connection_network));
|
||||
init.emit(());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ==========================
|
||||
@ -351,8 +410,9 @@ ensogl_core::define_endpoints! {
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
pub struct ListView<E: Entry> {
|
||||
model: Model<E>,
|
||||
pub frp: Frp<E>,
|
||||
model: Model<E>,
|
||||
pub frp: Frp<E>,
|
||||
style_frp: StyleFrp,
|
||||
}
|
||||
|
||||
impl<E: Entry> Deref for ListView<E> {
|
||||
@ -369,11 +429,11 @@ where E::Model: Default
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let frp = Frp::new();
|
||||
let model = Model::new(app);
|
||||
ListView { model, frp }.init(app)
|
||||
let style_frp = StyleFrp::new(&frp.network);
|
||||
ListView { model, frp, style_frp }.init(app)
|
||||
}
|
||||
|
||||
fn init(self, app: &Application) -> Self {
|
||||
const MAX_SCROLL: f32 = entry::HEIGHT / 2.0;
|
||||
const MOUSE_MOVE_THRESHOLD: f32 = std::f32::EPSILON;
|
||||
|
||||
let frp = &self.frp;
|
||||
@ -384,11 +444,8 @@ where E::Model: Default
|
||||
let view_y = Animation::<f32>::new(network);
|
||||
let selection_y = Animation::<f32>::new(network);
|
||||
let selection_height = 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);
|
||||
let selection_color = style.get_color(list_view_style::highlight);
|
||||
let selection_corner_radius = style.get_number(list_view_style::highlight::corner_radius);
|
||||
let style_watch = StyleWatchFrp::new(&scene.style_sheet);
|
||||
let style = &self.style_frp;
|
||||
|
||||
frp::extend! { network
|
||||
|
||||
@ -403,8 +460,7 @@ where E::Model: Default
|
||||
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);
|
||||
background_color <- any(&style.background_color, &frp.set_background_color);
|
||||
eval background_color ((color) model.background.color.set(color.into()));
|
||||
|
||||
|
||||
@ -490,39 +546,46 @@ where E::Model: Default
|
||||
selection_y.target <+ frp.selected_entry.map(|id|
|
||||
id.map_or(0.0,entry::List::<E>::position_y_of_entry)
|
||||
);
|
||||
selection_height.target <+ frp.selected_entry.map(f!([](id)
|
||||
if id.is_some() {entry::HEIGHT} else {0.0}
|
||||
));
|
||||
selection_height.target <+ all_with(&frp.selected_entry, &style.selection_height, |id, h|
|
||||
if id.is_some() {*h} else {-SHAPE_MARGIN}
|
||||
);
|
||||
selection_y.skip <+ frp.set_entries.constant(());
|
||||
selection_height.skip <+ frp.set_entries.constant(());
|
||||
selection_sprite_y <- all_with(&selection_y.value, &selection_height.value,
|
||||
|y, h| y + (entry::HEIGHT - h) / 2.0
|
||||
selection_sprite_y <- all_with3(&selection_y.value, &selection_height.value, &style.selection_height,
|
||||
|y, h, max_h| y + (max_h - h) / 2.0
|
||||
);
|
||||
eval selection_sprite_y ((y) model.selection.set_position_y(*y));
|
||||
frp.source.selection_size <+ all_with(&frp.size,&selection_height.value,f!([](size,height) {
|
||||
let width = size.x;
|
||||
frp.source.selection_size <+ all_with3(&frp.size, &style.padding, &selection_height.value, f!([](size, padding, height) {
|
||||
let width = size.x - 2.0 * padding;
|
||||
Vector2(width,*height)
|
||||
}));
|
||||
eval frp.selection_size ((size) model.selection.size.set(*size));
|
||||
eval frp.selection_size ([model](size) {
|
||||
let margin = Vector2(SHAPE_MARGIN, SHAPE_MARGIN);
|
||||
model.selection.size.set(*size + 2.0 * margin)
|
||||
});
|
||||
eval_ frp.hide_selection (model.selection.unset_parent());
|
||||
|
||||
|
||||
// === Scrolling ===
|
||||
|
||||
selection_top_after_move_up <- selected_entry_after_move_up.map(|id|
|
||||
id.map(|id| entry::List::<E>::y_range_of_entry(id).end)
|
||||
max_scroll <- style.selection_height.map(|h| *h / 2.0).sampler();
|
||||
selection_top_after_move_up <- selected_entry_after_move_up.map2(&style.selection_height, |id, h|
|
||||
id.map(|id| entry::List::<E>::position_y_of_entry(id) + *h / 2.0)
|
||||
);
|
||||
min_scroll_after_move_up <- selection_top_after_move_up.map(|top|
|
||||
top.unwrap_or(MAX_SCROLL)
|
||||
min_scroll_after_move_up <- selection_top_after_move_up.map2(&max_scroll, |top, max_scroll|
|
||||
top.unwrap_or(*max_scroll)
|
||||
);
|
||||
scroll_after_move_up <- min_scroll_after_move_up.map2(&frp.scroll_position,|min,current|
|
||||
current.max(*min)
|
||||
);
|
||||
selection_bottom_after_move_down <- selected_entry_after_move_down.map(|id|
|
||||
id.map(|id| entry::List::<E>::y_range_of_entry(id).start)
|
||||
selection_bottom_after_move_down <- selected_entry_after_move_down.map2(&style.selection_height, |id, h|
|
||||
id.map(|id| entry::List::<E>::position_y_of_entry(id) - *h / 2.0)
|
||||
);
|
||||
max_scroll_after_move_down <- selection_bottom_after_move_down.map2(&frp.size,
|
||||
|y,size| y.map_or(MAX_SCROLL, |y| y + size.y)
|
||||
max_scroll_after_move_down <- selection_bottom_after_move_down.map4(
|
||||
&frp.size,
|
||||
&style.padding,
|
||||
&max_scroll,
|
||||
|y, size, padding, max_scroll| y.map_or(*max_scroll, |y| y + size.y - 2.0 * padding)
|
||||
);
|
||||
scroll_after_move_down <- max_scroll_after_move_down.map2(&frp.scroll_position,
|
||||
|max_scroll,current| current.min(*max_scroll)
|
||||
@ -530,52 +593,51 @@ where E::Model: Default
|
||||
frp.source.scroll_position <+ scroll_after_move_up;
|
||||
frp.source.scroll_position <+ scroll_after_move_down;
|
||||
frp.source.scroll_position <+ frp.scroll_jump;
|
||||
frp.source.scroll_position <+ frp.set_entries.constant(MAX_SCROLL);
|
||||
frp.source.scroll_position <+ max_scroll.sample(&frp.set_entries);
|
||||
view_y.target <+ frp.scroll_position;
|
||||
view_y.target <+ frp.set_entries.constant(MAX_SCROLL);
|
||||
view_y.target <+ max_scroll.sample(&frp.set_entries);
|
||||
view_y.skip <+ frp.set_entries.constant(());
|
||||
view_y.target <+ init.constant(MAX_SCROLL);
|
||||
view_y.target <+ max_scroll.sample(&init);
|
||||
view_y.skip <+ init;
|
||||
|
||||
|
||||
// === Resize ===
|
||||
frp.source.size <+ frp.resize.map(f!([model](size)
|
||||
size - Vector2(model.padding(),model.padding()))
|
||||
);
|
||||
frp.source.size <+ frp.resize;
|
||||
|
||||
|
||||
// === Update Entries ===
|
||||
|
||||
view_info <- all_with(&view_y.value,&frp.size, |y,size|
|
||||
View{position_y:*y,size:*size}
|
||||
);
|
||||
view_info <- all_with3(&view_y.value, &frp.size, &style.padding, |&y, &size, &padding| {
|
||||
let padding = Vector2(2.0 * padding, 2.0 * padding);
|
||||
View { position_y: y, size: size - padding }
|
||||
});
|
||||
default_style_prefix <- init.constant(DEFAULT_STYLE_PATH.to_string());
|
||||
style_prefix <- any(&default_style_prefix,&frp.set_style_prefix);
|
||||
frp.source.style_prefix <+ style_prefix;
|
||||
eval style_prefix ((path)
|
||||
model.entries.recreate_entries_with_style_prefix(path.into()));
|
||||
view_and_style <- all(&view_info,&style_prefix);
|
||||
eval style_prefix ([model, style, style_watch](path) {
|
||||
style.connect_with_prefix(&style_watch, &path.into());
|
||||
model.entries.recreate_entries_with_style_prefix(path.into());
|
||||
});
|
||||
view_and_style <- all(view_info, style.padding, style.entry_padding, style_prefix);
|
||||
// This should go before handling mouse events to have proper checking of
|
||||
eval view_and_style (((view,style_prefix))
|
||||
model.update_after_view_change(view,style_prefix.into()));
|
||||
_new_entries <- frp.set_entries.map2(&view_and_style, f!((entries,(view,style_prefix))
|
||||
model.set_entries(entries.clone_ref(),view,style_prefix.into()))
|
||||
);
|
||||
eval view_and_style (((view, padding, entry_padding, style))
|
||||
model.update_after_view_change(view, *padding, *entry_padding, &style.into()));
|
||||
_new_entries <- frp.set_entries.map2(&view_and_style, f!((entries, (view, _, _, style))
|
||||
model.set_entries(entries.clone_ref(), view, style.into())
|
||||
));
|
||||
|
||||
frp.source.selection_position_target <+ all_with3(
|
||||
frp.source.selection_position_target <+ all_with4(
|
||||
&selection_y.target,
|
||||
&view_y.target,
|
||||
&frp.size,
|
||||
|selection_y, view_y, size| Vector2(0.0, (size.y / 2.0) - view_y + selection_y)
|
||||
&style.padding,
|
||||
|sel_y, view_y, size, padding| Vector2(0.0, (size.y / 2.0 - padding) - view_y + sel_y)
|
||||
);
|
||||
selection_color <- all(&selection_color, &init)._0();
|
||||
selection_corner_radius <- all(&selection_corner_radius, &init)._0();
|
||||
eval selection_color ((color) model.selection.color.set(color.into()));
|
||||
eval selection_corner_radius ((radius) model.selection.corner_radius.set(*radius));
|
||||
eval style.selection_color ((color) model.selection.color.set(color.into()));
|
||||
eval style.selection_corner_radius ((radius) model.selection.corner_radius.set(*radius));
|
||||
}
|
||||
|
||||
init.emit(());
|
||||
frp.scroll_jump(MAX_SCROLL);
|
||||
frp.scroll_jump(max_scroll.value());
|
||||
|
||||
self
|
||||
}
|
||||
@ -645,6 +707,7 @@ mod tests {
|
||||
|
||||
use approx::assert_relative_eq;
|
||||
use enso_frp::future::EventOutputExt;
|
||||
use ensogl_core::display::style::data::DataMatch;
|
||||
|
||||
#[test]
|
||||
fn navigating_list_view_with_keyboard() {
|
||||
@ -695,11 +758,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn selection_position() {
|
||||
use ensogl_hardcoded_theme::widget::list_view as theme;
|
||||
let app = Application::new("root");
|
||||
ensogl_hardcoded_theme::builtin::light::register(&app);
|
||||
ensogl_hardcoded_theme::builtin::light::enable(&app);
|
||||
let style_sheet = &app.display.default_scene.style_sheet;
|
||||
style_sheet.set(theme::highlight::height, entry::HEIGHT);
|
||||
let padding = style_sheet.value(theme::padding).unwrap().number().unwrap();
|
||||
let list_view = ListView::<entry::Label>::new(&app);
|
||||
let provider =
|
||||
AnyModelProvider::<entry::Label>::new(vec!["Entry 1", "Entry 2", "Entry 3", "Entry 4"]);
|
||||
list_view.resize(Vector2(100.0, entry::HEIGHT * 3.0));
|
||||
list_view.resize(Vector2(100.0, entry::HEIGHT * 3.0 + padding * 2.0));
|
||||
list_view.set_entries(provider);
|
||||
list_view.select_entry(Some(0));
|
||||
assert_relative_eq!(list_view.selection_position_target.value().x, 0.0);
|
||||
|
@ -58,7 +58,7 @@ impl component::Frp<Model> for Frp {
|
||||
fn init(api: &Self::Private, app: &Application, model: &Model, _style: &StyleWatchFrp) {
|
||||
let network = &api.network;
|
||||
let line = &model.line.events;
|
||||
frp::extend! { TRACE_ALL network
|
||||
frp::extend! { network
|
||||
eval api.input.set_size((size) model.set_size(*size));
|
||||
eval api.input.set_color((color) model.set_color(*color));
|
||||
eval api.input.set_cap((direction) model.set_cap(*direction));
|
||||
|
@ -872,7 +872,7 @@ impl<Host, T: Object<Host>> Object<Host> for &T {
|
||||
// === ObjectOps ===
|
||||
// =================
|
||||
|
||||
impl<Host, T: Object<Host>> ObjectOps<Host> for T {}
|
||||
impl<Host, T: Object<Host> + ?Sized> ObjectOps<Host> for T {}
|
||||
|
||||
/// Implementation of operations available for every struct which implements `display::Object`.
|
||||
/// To learn more about the design, please refer to the documentation of [`Instance`].
|
||||
@ -894,7 +894,7 @@ pub trait ObjectOps<Host = Scene>: Object<Host> {
|
||||
|
||||
/// Add another display object as a child to this display object. Children will inherit all
|
||||
/// transformations of their parents.
|
||||
fn add_child<T: Object<Host>>(&self, child: &T) {
|
||||
fn add_child<T: Object<Host> + ?Sized>(&self, child: &T) {
|
||||
self.display_object()._add_child(child.display_object());
|
||||
}
|
||||
|
||||
|
@ -373,7 +373,7 @@ macro_rules! newtype_struct_impls {
|
||||
/// Smart constructor.
|
||||
$(#$meta)*
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $name($field:$field_type) -> $name { $name {$field} }
|
||||
pub const fn $name($field:$field_type) -> $name { $name {$field} }
|
||||
|
||||
impl From<&$name> for $name { fn from(t:&$name) -> Self { *t } }
|
||||
impl From<&&$name> for $name { fn from(t:&&$name) -> Self { **t } }
|
||||
|
Loading…
Reference in New Issue
Block a user